This commit is contained in:
van
2026-09-06 23:24:38 +08:00
parent 2acfdf9708
commit 1faa4c8713
2 changed files with 89 additions and 12 deletions
@@ -12,7 +12,10 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 SEND = QYWX_ORIGIN + "/cgi-bin/message/send?access_token=";
private static final String WX_ACCESS_TOKEN = "WECOM_APP_ACTIVE_PUSH_TOKEN:";
/** 企微文本消息 content 上限约 2048,预留余量 */
private static final int CONTENT_MAX = 2000;
/** 企微文本 content 上限 2048 字节;按 UTF-8 拆条,禁止按字符数截断丢尾部 */
private static final int CONTENT_MAX_BYTES = 1800;
@Resource
private RedisCache redisCache;
@@ -51,6 +54,13 @@ public class WeComApplicationTextPushService {
@Value("${qywx.app.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 {
sendTextToUser(toUser, content, null);
}
@@ -92,8 +102,17 @@ public class WeComApplicationTextPushService {
sendTextWithAgent(toUser, content, fishAid, goofishSecret);
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(
"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)) {
throw new IllegalArgumentException("toUser 不能为空");
}
String text = content != null ? content : "";
if (text.length() > CONTENT_MAX) {
text = text.substring(0, CONTENT_MAX) + "…";
}
String token = getAccessToken(useCorpSecret);
if (StrUtil.isBlank(token)) {
throw new IllegalStateException("获取企微 access_token 失败");
}
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("touser", toUser.trim());
int agentIdInt;
try {
agentIdInt = Integer.parseInt(useAgentId.trim());
} catch (NumberFormatException e) {
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("safe", 0);
jsonMap.put("msgtype", "text");
@@ -144,7 +174,7 @@ public class WeComApplicationTextPushService {
.timeout(30_000)
.execute()
.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);
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) {
if (StrUtil.isBlank(corpId) || StrUtil.isBlank(corpSecretForApp)) {
return null;
+3
View File
@@ -88,5 +88,8 @@ qywx:
# 生产建议仅通过环境变量注入,勿将真实 Secret 提交仓库
goofishAgentId: "1000013"
goofishSecret: "${QYWX_GOOFISH_SECRET:hINY8boNFjY3lypOKbcukE8wYnNS9wnXeonB4iTO1rA}"
# 慢单利润日报/周报/月报:POST /wecom/active-push 传入 agentId=1000014
reportAgentId: "1000014"
reportSecret: "${QYWX_REPORT_SECRET:pCrqYLR4VODNyVyWbqP9IRUodzsOY7JrPWlKvoeIxNY}"
token: "34NXCtEjkl"
encodingAESKey: "bNlE8IhjU34CfXflcBhW3gXPwr8xaEDEhnpUvChvw5i"