1
This commit is contained in:
+86
-12
@@ -12,7 +12,10 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -29,8 +32,8 @@ public class WeComApplicationTextPushService {
|
|||||||
private static final String GET_TOKEN = QYWX_ORIGIN + "/cgi-bin/gettoken";
|
private static final String GET_TOKEN = QYWX_ORIGIN + "/cgi-bin/gettoken";
|
||||||
private static final String SEND = QYWX_ORIGIN + "/cgi-bin/message/send?access_token=";
|
private static final String SEND = QYWX_ORIGIN + "/cgi-bin/message/send?access_token=";
|
||||||
private static final String WX_ACCESS_TOKEN = "WECOM_APP_ACTIVE_PUSH_TOKEN:";
|
private static final String WX_ACCESS_TOKEN = "WECOM_APP_ACTIVE_PUSH_TOKEN:";
|
||||||
/** 企微文本消息 content 上限约 2048,预留余量 */
|
/** 企微文本 content 上限 2048 字节;按 UTF-8 拆条,禁止按字符数截断丢尾部 */
|
||||||
private static final int CONTENT_MAX = 2000;
|
private static final int CONTENT_MAX_BYTES = 1800;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RedisCache redisCache;
|
private RedisCache redisCache;
|
||||||
@@ -51,6 +54,13 @@ public class WeComApplicationTextPushService {
|
|||||||
@Value("${qywx.app.goofishSecret:}")
|
@Value("${qywx.app.goofishSecret:}")
|
||||||
private String goofishSecret;
|
private String goofishSecret;
|
||||||
|
|
||||||
|
/** 慢单利润日报等:独立自建应用 */
|
||||||
|
@Value("${qywx.app.reportAgentId:}")
|
||||||
|
private String reportAgentId;
|
||||||
|
|
||||||
|
@Value("${qywx.app.reportSecret:}")
|
||||||
|
private String reportSecret;
|
||||||
|
|
||||||
public void sendTextToUser(String toUser, String content) throws Exception {
|
public void sendTextToUser(String toUser, String content) throws Exception {
|
||||||
sendTextToUser(toUser, content, null);
|
sendTextToUser(toUser, content, null);
|
||||||
}
|
}
|
||||||
@@ -92,8 +102,17 @@ public class WeComApplicationTextPushService {
|
|||||||
sendTextWithAgent(toUser, content, fishAid, goofishSecret);
|
sendTextWithAgent(toUser, content, fishAid, goofishSecret);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
String reportAid = StrUtil.trimToEmpty(reportAgentId);
|
||||||
|
if (StrUtil.isNotBlank(reportAid) && rid.equals(reportAid)) {
|
||||||
|
if (StrUtil.isBlank(reportSecret)) {
|
||||||
|
throw new IllegalStateException("未配置 qywx.app.reportSecret(与 reportAgentId 对应)");
|
||||||
|
}
|
||||||
|
sendTextWithAgent(toUser, content, reportAid, reportSecret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"active-push 未识别的 agentId=" + rid + ",与 qywx.app.agentId / goofishAgentId 均不一致;请补配置或传空使用默认应用");
|
"active-push 未识别的 agentId=" + rid
|
||||||
|
+ ",与 qywx.app.agentId / goofishAgentId / reportAgentId 均不一致;请补配置或传空使用默认应用");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,24 +132,35 @@ public class WeComApplicationTextPushService {
|
|||||||
if (StrUtil.isBlank(toUser)) {
|
if (StrUtil.isBlank(toUser)) {
|
||||||
throw new IllegalArgumentException("toUser 不能为空");
|
throw new IllegalArgumentException("toUser 不能为空");
|
||||||
}
|
}
|
||||||
String text = content != null ? content : "";
|
|
||||||
if (text.length() > CONTENT_MAX) {
|
|
||||||
text = text.substring(0, CONTENT_MAX) + "…";
|
|
||||||
}
|
|
||||||
|
|
||||||
String token = getAccessToken(useCorpSecret);
|
String token = getAccessToken(useCorpSecret);
|
||||||
if (StrUtil.isBlank(token)) {
|
if (StrUtil.isBlank(token)) {
|
||||||
throw new IllegalStateException("获取企微 access_token 失败");
|
throw new IllegalStateException("获取企微 access_token 失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> jsonMap = new HashMap<>();
|
|
||||||
jsonMap.put("touser", toUser.trim());
|
|
||||||
int agentIdInt;
|
int agentIdInt;
|
||||||
try {
|
try {
|
||||||
agentIdInt = Integer.parseInt(useAgentId.trim());
|
agentIdInt = Integer.parseInt(useAgentId.trim());
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IllegalStateException("agentId 非法: " + useAgentId);
|
throw new IllegalStateException("agentId 非法: " + useAgentId);
|
||||||
}
|
}
|
||||||
|
List<String> parts = splitUtf8Chunks(content != null ? content : "", CONTENT_MAX_BYTES);
|
||||||
|
if (parts.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("推送正文为空");
|
||||||
|
}
|
||||||
|
if (parts.size() > 1) {
|
||||||
|
log.info("企微文本超长,拆成 {} 条下发 agentId={} toUser={}", parts.size(), useAgentId, toUser);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < parts.size(); i++) {
|
||||||
|
postTextMessage(token, toUser.trim(), agentIdInt, parts.get(i), useAgentId, toUser);
|
||||||
|
if (i + 1 < parts.size()) {
|
||||||
|
sleep(120);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postTextMessage(String token, String toUser, int agentIdInt, String text,
|
||||||
|
String useAgentId, String logUser) throws Exception {
|
||||||
|
Map<String, Object> jsonMap = new HashMap<>();
|
||||||
|
jsonMap.put("touser", toUser);
|
||||||
jsonMap.put("agentid", agentIdInt);
|
jsonMap.put("agentid", agentIdInt);
|
||||||
jsonMap.put("safe", 0);
|
jsonMap.put("safe", 0);
|
||||||
jsonMap.put("msgtype", "text");
|
jsonMap.put("msgtype", "text");
|
||||||
@@ -144,7 +174,7 @@ public class WeComApplicationTextPushService {
|
|||||||
.timeout(30_000)
|
.timeout(30_000)
|
||||||
.execute()
|
.execute()
|
||||||
.body();
|
.body();
|
||||||
log.info("企微主动文本下发 agentId={} toUser={} respSnippet={}", useAgentId, toUser,
|
log.info("企微主动文本下发 agentId={} toUser={} respSnippet={}", useAgentId, logUser,
|
||||||
resp != null && resp.length() > 200 ? resp.substring(0, 200) + "..." : resp);
|
resp != null && resp.length() > 200 ? resp.substring(0, 200) + "..." : resp);
|
||||||
|
|
||||||
SendRespones sendResp = JSON.parseObject(resp, SendRespones.class);
|
SendRespones sendResp = JSON.parseObject(resp, SendRespones.class);
|
||||||
@@ -154,6 +184,50 @@ public class WeComApplicationTextPushService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String> splitUtf8Chunks(String text, int maxBytes) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
if (text == null) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
String s = text.trim();
|
||||||
|
if (s.isEmpty()) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
while (i < s.length()) {
|
||||||
|
int end = i;
|
||||||
|
int bytes = 0;
|
||||||
|
int lastBreak = i;
|
||||||
|
while (end < s.length()) {
|
||||||
|
int cp = s.codePointAt(end);
|
||||||
|
int charLen = Character.charCount(cp);
|
||||||
|
int b = new String(Character.toChars(cp)).getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
if (bytes + b > maxBytes) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bytes += b;
|
||||||
|
end += charLen;
|
||||||
|
if (end > 0 && s.charAt(end - 1) == '\n') {
|
||||||
|
lastBreak = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (end == i) {
|
||||||
|
end = i + Character.charCount(s.codePointAt(i));
|
||||||
|
} else if (lastBreak > i && end < s.length()) {
|
||||||
|
end = lastBreak;
|
||||||
|
}
|
||||||
|
String piece = s.substring(i, end).trim();
|
||||||
|
if (!piece.isEmpty()) {
|
||||||
|
out.add(piece);
|
||||||
|
}
|
||||||
|
i = end;
|
||||||
|
while (i < s.length() && s.charAt(i) == '\n') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
private String getAccessToken(String corpSecretForApp) {
|
private String getAccessToken(String corpSecretForApp) {
|
||||||
if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecretForApp)) {
|
if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecretForApp)) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -88,5 +88,8 @@ qywx:
|
|||||||
# 生产建议仅通过环境变量注入,勿将真实 Secret 提交仓库
|
# 生产建议仅通过环境变量注入,勿将真实 Secret 提交仓库
|
||||||
goofishAgentId: "1000013"
|
goofishAgentId: "1000013"
|
||||||
goofishSecret: "${QYWX_GOOFISH_SECRET:hINY8boNFjY3lypOKbcukE8wYnNS9wnXeonB4iTO1rA}"
|
goofishSecret: "${QYWX_GOOFISH_SECRET:hINY8boNFjY3lypOKbcukE8wYnNS9wnXeonB4iTO1rA}"
|
||||||
|
# 慢单利润日报/周报/月报:POST /wecom/active-push 传入 agentId=1000014
|
||||||
|
reportAgentId: "1000014"
|
||||||
|
reportSecret: "${QYWX_REPORT_SECRET:pCrqYLR4VODNyVyWbqP9IRUodzsOY7JrPWlKvoeIxNY}"
|
||||||
token: "34NXCtEjkl"
|
token: "34NXCtEjkl"
|
||||||
encodingAESKey: "bNlE8IhjU34CfXflcBhW3gXPwr8xaEDEhnpUvChvw5i"
|
encodingAESKey: "bNlE8IhjU34CfXflcBhW3gXPwr8xaEDEhnpUvChvw5i"
|
||||||
|
|||||||
Reference in New Issue
Block a user