first commit
This commit is contained in:
146
src/main/java/com/xiang/common/utils/HttpService.java
Normal file
146
src/main/java/com/xiang/common/utils/HttpService.java
Normal file
@@ -0,0 +1,146 @@
|
||||
package com.xiang.common.utils;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @Author: xiang
|
||||
* @Date: 2025-05-08 14:39
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpService {
|
||||
|
||||
private static final int socketTimeOut = 10000;
|
||||
private static final int connectTimeout = 10000;
|
||||
private static final int connectionRequestTimeout = 3000;
|
||||
private static final int defaultMaxPerRoute = 100;
|
||||
private static final int maxTotal = 200;
|
||||
|
||||
private static final int LIVE_TIME = 5000;
|
||||
|
||||
private static final int ALIVE_STRATEGY = 30000;
|
||||
|
||||
private static final RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(connectTimeout)
|
||||
.setSocketTimeout(socketTimeOut)
|
||||
.setConnectionRequestTimeout(connectionRequestTimeout)
|
||||
.build();
|
||||
// 使用连接池
|
||||
private static final PoolingHttpClientConnectionManager connectionManager;
|
||||
|
||||
private static final CloseableHttpClient httpClient;
|
||||
|
||||
static {
|
||||
// 确保使用 TLSv1.2
|
||||
System.setProperty("https.protocols", "TLSv1.2");
|
||||
}
|
||||
|
||||
static {
|
||||
connectionManager = new PoolingHttpClientConnectionManager();
|
||||
// 最大连接数
|
||||
connectionManager.setMaxTotal(maxTotal);
|
||||
// 每个主机的最大连接数
|
||||
connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
|
||||
connectionManager.setValidateAfterInactivity(LIVE_TIME);
|
||||
|
||||
|
||||
httpClient = HttpClients.custom()
|
||||
.setConnectionManager(connectionManager)
|
||||
.setDefaultRequestConfig(requestConfig)
|
||||
// 清理空闲连接
|
||||
.evictIdleConnections(30, TimeUnit.SECONDS)
|
||||
.setKeepAliveStrategy((response, context) -> ALIVE_STRATEGY)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static String doPost(String url, Map<String, String> header, String jsonParams) {
|
||||
CloseableHttpResponse response = null;
|
||||
String result = "";
|
||||
try {
|
||||
log.info("HTTP请求,请求地址===>{}, 请求头===>{}, 请求参数===>{}", url, JSON.toJSONString(header), jsonParams);
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.addHeader("Content-Type", "application/json");
|
||||
// 创建请求内容
|
||||
StringEntity entity = new StringEntity(jsonParams, "utf-8");
|
||||
httpPost.setEntity(entity);
|
||||
// 设置请求头
|
||||
if (null != header && !header.isEmpty()) {
|
||||
Set<Map.Entry<String, String>> entries = header.entrySet();
|
||||
for (Map.Entry<String, String> e : entries) {
|
||||
httpPost.setHeader(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
response = httpClient.execute(httpPost);
|
||||
result = EntityUtils.toString(response.getEntity(), "utf-8");
|
||||
log.info("【POST请求】 请求地址===>{}, 响应结果==={}", url, result);
|
||||
} catch (Exception e) {
|
||||
log.error("doPost异常", e);
|
||||
} finally {
|
||||
// 不关闭 httpClient
|
||||
closeResource(response);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String doGet(String url, Map<String, String> header, Map<String, String> param) {
|
||||
CloseableHttpResponse response = null;
|
||||
String result = "";
|
||||
try {
|
||||
String request = "";
|
||||
if (MapUtils.isNotEmpty(param)) {
|
||||
StringBuilder req = new StringBuilder("?");
|
||||
for (Map.Entry<String, String> entry : param.entrySet()) {
|
||||
req.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
|
||||
}
|
||||
request = req.substring(0, req.length() - 1);
|
||||
}
|
||||
|
||||
HttpGet httpGet = new HttpGet(url + request);
|
||||
if (MapUtils.isNotEmpty(header)) {
|
||||
for (Map.Entry<String, String> entry : header.entrySet()) {
|
||||
httpGet.setHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
log.info("doGet请求:请求头:{},请求地址:{}", header, url + request);
|
||||
response = httpClient.execute(httpGet);
|
||||
result = EntityUtils.toString(response.getEntity(), "utf-8");
|
||||
log.info("【GET请求】, 请求地址===>{}, 响应结果===>{}", url + request, result);
|
||||
} catch (Exception e) {
|
||||
log.error("doGet异常:", e);
|
||||
} finally {
|
||||
closeResource(response);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Description 关闭资源
|
||||
*/
|
||||
private static void closeResource(Closeable... resources) {
|
||||
try {
|
||||
for (Closeable resource : resources) {
|
||||
if (resource != null) {
|
||||
resource.close();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
190
src/main/java/com/xiang/common/utils/RedisService.java
Normal file
190
src/main/java/com/xiang/common/utils/RedisService.java
Normal file
@@ -0,0 +1,190 @@
|
||||
package com.xiang.common.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RedisService {
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Value("${spring.application.name}")
|
||||
private String group;
|
||||
|
||||
public void set(String key, String value) {
|
||||
redisTemplate.opsForValue().set(buildKey(group, key), value);
|
||||
}
|
||||
|
||||
public void set(String key, String value, long timeout, TimeUnit unit) {
|
||||
redisTemplate.opsForValue().set(buildKey(group, key), value, timeout, unit);
|
||||
}
|
||||
|
||||
public void set(String group, String key, Object value) {
|
||||
redisTemplate.opsForValue().set(buildKey(group, key), value);
|
||||
}
|
||||
|
||||
public void set(String group, String key, String value, long timeout, TimeUnit unit) {
|
||||
redisTemplate.opsForValue().set(buildKey(group, key), value, timeout, unit);
|
||||
}
|
||||
|
||||
public Object get(String key) {
|
||||
return redisTemplate.opsForValue().get(buildKey(group, key));
|
||||
}
|
||||
|
||||
public Object get(String group, String key) {
|
||||
return redisTemplate.opsForValue().get(buildKey(group, key));
|
||||
}
|
||||
|
||||
public Boolean delKey(String group, String key) {
|
||||
return redisTemplate.delete(buildKey(group, key));
|
||||
}
|
||||
|
||||
public Boolean delKey(String key) {
|
||||
return delKey(group, key);
|
||||
}
|
||||
|
||||
public Boolean deleteObject(Collection collection) {
|
||||
return redisTemplate.delete(collection) > 0;
|
||||
}
|
||||
|
||||
public Boolean hasKey(String key) {
|
||||
return hasKey(buildKey(group, key));
|
||||
}
|
||||
|
||||
public Boolean hasKey(String group, String key) {
|
||||
return redisTemplate.hasKey(buildKey(group, key));
|
||||
}
|
||||
|
||||
public Boolean expire(String key, long timeout, TimeUnit unit) {
|
||||
return expire(group, key, timeout, unit);
|
||||
}
|
||||
|
||||
public Boolean expire(String group, String key, long timeout, TimeUnit unit) {
|
||||
return redisTemplate.expire(buildKey(group, key), timeout, unit);
|
||||
}
|
||||
|
||||
public void hSet(String key, String field, Object value) {
|
||||
redisTemplate.opsForHash().put(buildKey(group, key), field, value);
|
||||
}
|
||||
|
||||
public void hSet(String group, String key, String field, Object value) {
|
||||
redisTemplate.opsForHash().put(buildKey(group, key), field, value);
|
||||
}
|
||||
|
||||
public Object hGet(String key, String field) {
|
||||
return redisTemplate.opsForHash().get(buildKey(group, key), field);
|
||||
}
|
||||
|
||||
public Object hGet(String group, String key, String field) {
|
||||
return redisTemplate.opsForHash().get(buildKey(group, key), field);
|
||||
}
|
||||
|
||||
public Map<Object, Object> hGetAll(String key) {
|
||||
return redisTemplate.opsForHash().entries(buildKey(group, key));
|
||||
}
|
||||
|
||||
public Map<Object, Object> hGetAll(String group, String key) {
|
||||
return redisTemplate.opsForHash().entries(buildKey(group, key));
|
||||
}
|
||||
|
||||
public Boolean hDel(String key, Object... fields) {
|
||||
return redisTemplate.opsForHash().delete(buildKey(group, key), fields) > 0;
|
||||
}
|
||||
|
||||
public Boolean hDel(String group, String key, Object... fields) {
|
||||
return redisTemplate.opsForHash().delete(buildKey(group, key), fields) > 0;
|
||||
}
|
||||
|
||||
public Boolean hHasKey(String key, String field) {
|
||||
return redisTemplate.opsForHash().hasKey(buildKey(group, key), field);
|
||||
}
|
||||
|
||||
public Boolean hHasKey(String group, String key, String field) {
|
||||
return redisTemplate.opsForHash().hasKey(buildKey(group, key), field);
|
||||
}
|
||||
|
||||
public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
|
||||
return tryLock(group, key, value, timeout, unit);
|
||||
}
|
||||
|
||||
public Boolean tryLock(String group, String key, String value, long timeout, TimeUnit unit) {
|
||||
String redisKey = buildKey(group, key);
|
||||
Boolean success = redisTemplate.opsForValue()
|
||||
.setIfAbsent(redisKey, value, timeout, unit);
|
||||
return Boolean.TRUE.equals(success);
|
||||
}
|
||||
|
||||
public Boolean unlock(String key, String value) {
|
||||
return unlock(group, key, value);
|
||||
}
|
||||
|
||||
public Boolean unlock(String group, String key, String value) {
|
||||
if (hasKey(buildKey(group, key), value)) {
|
||||
String val = (String) get(buildKey(group, key), value);
|
||||
if (StringUtils.equals(val, value)) {
|
||||
return delKey(group, key);
|
||||
}
|
||||
}
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
public Boolean unlockLura(String key, String value) {
|
||||
return unlockLura(group, key, value);
|
||||
}
|
||||
|
||||
public Boolean unlockLura(String group, String key, String value) {
|
||||
String redisKey = buildKey(group, key);
|
||||
|
||||
String luaScript =
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] " +
|
||||
"then return redis.call('del', KEYS[1]) " +
|
||||
"else return 0 end";
|
||||
|
||||
Long result = executeLua(
|
||||
luaScript,
|
||||
Collections.singletonList(redisKey),
|
||||
Collections.singletonList(value),
|
||||
Long.class
|
||||
);
|
||||
|
||||
return Objects.nonNull(result) && result > 0;
|
||||
}
|
||||
|
||||
public <T> T executeLua(String script, List<String> keys, List<Object> args, Class<T> resultType) {
|
||||
DefaultRedisScript<T> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptText(script);
|
||||
redisScript.setResultType(resultType);
|
||||
|
||||
return redisTemplate.execute(redisScript, keys, args.toArray());
|
||||
}
|
||||
|
||||
private String buildKey(String group, String key) {
|
||||
return String.format("%s:%s", group, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的基本对象列表
|
||||
*
|
||||
* @param pattern 字符串前缀
|
||||
*
|
||||
* @return 对象列表
|
||||
*/
|
||||
public Collection<String> keys(String pattern) {
|
||||
return keys(group, pattern);
|
||||
}
|
||||
|
||||
public Collection<String> keys(String group, String pattern) {
|
||||
return redisTemplate.keys(buildKey(group, pattern));
|
||||
}
|
||||
}
|
||||
75
src/main/java/com/xiang/common/utils/RedissonService.java
Normal file
75
src/main/java/com/xiang/common/utils/RedissonService.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package com.xiang.common.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @Author: xiang
|
||||
* @Date: 2025-12-08 14:44
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RedissonService {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(RedissonService.class);
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
public <T> T tryLock(String key, long waitTime, long leaseTime, TimeUnit unit, Supplier<T> supplier) {
|
||||
RLock lock = redissonClient.getLock(key);
|
||||
boolean flag = false;
|
||||
try {
|
||||
flag = lock.tryLock(waitTime, leaseTime, unit);
|
||||
if (!flag) {
|
||||
log.info("key:{}未拿到锁", key);
|
||||
return null;
|
||||
}
|
||||
return supplier.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} finally {
|
||||
if (flag && lock.isHeldByCurrentThread()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T tryLock(String key, long waitTime, long leaseTime, TimeUnit unit, Supplier<T> supplier, Supplier<T> fallback) {
|
||||
RLock lock = redissonClient.getLock(key);
|
||||
boolean locked = false;
|
||||
|
||||
try {
|
||||
locked = lock.tryLock(waitTime, leaseTime, unit);
|
||||
if (!locked) {
|
||||
// 拿不到锁 = fallback
|
||||
return fallback.get();
|
||||
}
|
||||
return supplier.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return fallback.get();
|
||||
} finally {
|
||||
if (locked && lock.isHeldByCurrentThread()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T lock(String key, Supplier<T> supplier) {
|
||||
RLock lock = redissonClient.getLock(key);
|
||||
lock.lock();
|
||||
try {
|
||||
return supplier.get();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.xiang.common.utils.dingTalk;
|
||||
|
||||
import com.xiang.common.config.DingTalkRobotProperties;
|
||||
import com.xiang.common.config.RobotConfig;
|
||||
import com.xiang.common.enums.BaseDingTalkBizType;
|
||||
import com.xiang.common.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author: xiang
|
||||
* @Date: 2026-01-04 16:11
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractDingTalkFactory {
|
||||
|
||||
private final DingTalkRobotProperties dingTalkRobotProperties;
|
||||
private final DingTalkSender dingTalkSender;
|
||||
|
||||
public abstract void sendMsg(String msg);
|
||||
|
||||
protected BizDingTalkClient getClient(BaseDingTalkBizType dingTalkBizTypeEnum) {
|
||||
Map<String, RobotConfig> properties = dingTalkRobotProperties.getProperties();
|
||||
if (MapUtils.isEmpty(properties)) {
|
||||
throw new BusinessException("钉钉群聊配置信息错误");
|
||||
}
|
||||
RobotConfig robotConfig = properties.get(dingTalkBizTypeEnum.getBizName());
|
||||
return new RobotBizDingTalkClient(robotConfig, dingTalkSender);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.xiang.common.utils.dingTalk;
|
||||
|
||||
/**
|
||||
* @Author: xiang
|
||||
* @Date: 2026-01-04 15:28
|
||||
*/
|
||||
public interface BizDingTalkClient {
|
||||
|
||||
void sendDingTalkMsg(String msg);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.xiang.common.utils.dingTalk;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.dingtalk.api.DefaultDingTalkClient;
|
||||
import com.dingtalk.api.DingTalkClient;
|
||||
import com.dingtalk.api.request.OapiRobotSendRequest;
|
||||
import com.dingtalk.api.response.OapiRobotSendResponse;
|
||||
import com.xiang.common.config.RobotConfig;
|
||||
import com.xiang.common.exception.BusinessException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 钉钉消息工具类
|
||||
* @Author: xiang
|
||||
* @Date: 2026-01-04 15:16
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DingTalkSender {
|
||||
|
||||
private static final String MSG_TYPE_TEXT = "text";
|
||||
|
||||
/**
|
||||
* 发送机器人消息到指定的群
|
||||
* @param robotConfig 机器人配置文件
|
||||
* @param msg 消息内容
|
||||
* @return
|
||||
*/
|
||||
public String sendRobotMessage(RobotConfig robotConfig, String msg) {
|
||||
try {
|
||||
return doSendMsg(robotConfig, msg);
|
||||
} catch (Exception e) {
|
||||
log.info("钉钉机器人消息发送失败,业务线===>{}", robotConfig.getName());
|
||||
throw new BusinessException("钉钉消息发送失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息发送
|
||||
* @param robotConfig 机器人配置信息
|
||||
* @param msg 发送的消息
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
private String doSendMsg(RobotConfig robotConfig, String msg) throws Exception {
|
||||
String robotSecret = robotConfig.getSecret().trim();
|
||||
String robotToken = robotConfig.getToken().trim();
|
||||
List<String> userIds = robotConfig.getUsers();
|
||||
Long timestamp = System.currentTimeMillis();
|
||||
String stringToSign = timestamp + "\n" + robotSecret;
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(robotSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||||
byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
|
||||
String sign = URLEncoder.encode(Base64.getEncoder().encodeToString(signData), StandardCharsets.UTF_8);
|
||||
|
||||
//sign字段和timestamp字段必须拼接到请求URL上,否则会出现 310000 的错误信息
|
||||
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/robot/send?sign=" + sign + "×tamp=" + timestamp);
|
||||
OapiRobotSendRequest req = new OapiRobotSendRequest();
|
||||
|
||||
//定义文本内容
|
||||
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
|
||||
text.setContent(msg);
|
||||
//定义 @ 对象
|
||||
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
|
||||
at.setAtUserIds(userIds);
|
||||
//设置消息类型
|
||||
req.setMsgtype(MSG_TYPE_TEXT);
|
||||
req.setText(text);
|
||||
req.setAt(at);
|
||||
OapiRobotSendResponse rsp = client.execute(req, robotToken);
|
||||
return rsp.getBody();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.xiang.common.utils.dingTalk;
|
||||
|
||||
import com.xiang.common.config.RobotConfig;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @Author: xiang
|
||||
* @Date: 2026-01-04 15:28
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RobotBizDingTalkClient implements BizDingTalkClient {
|
||||
|
||||
private final RobotConfig robotConfig;
|
||||
private final DingTalkSender dingTalkSender;
|
||||
@Override
|
||||
public void sendDingTalkMsg(String msg) {
|
||||
try {
|
||||
dingTalkSender.sendRobotMessage(robotConfig, msg);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("钉钉消息发送失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user