This commit is contained in:
van
2026-04-12 01:19:10 +08:00
parent 1446ea2432
commit c825c6b81a
8 changed files with 74 additions and 52 deletions

View File

@@ -947,7 +947,7 @@ public class TencentDocController extends BaseController {
sb.append("… 共 ").append(errorLogs.size()).append(" 条错误\n"); sb.append("… 共 ").append(errorLogs.size()).append(" 条错误\n");
} }
} }
wxSendGoofishNotifyClient.pushGoofishAgentText(null, sb.toString()); wxSendGoofishNotifyClient.pushGoofishAgentText(null, "", sb.toString());
} }
/** /**
@@ -2015,7 +2015,7 @@ public class TencentDocController extends BaseController {
log.error("异常后更新批量推送记录失败 batchId={}", batchId, ex); log.error("异常后更新批量推送记录失败 batchId={}", batchId, ex);
} }
try { try {
wxSendGoofishNotifyClient.pushGoofishAgentText(null, wxSendGoofishNotifyClient.pushGoofishAgentText(null, "",
"【腾讯文档推送】批量同步异常\n批次: " + batchId + "\n" + errMsg); "【腾讯文档推送】批量同步异常\n批次: " + batchId + "\n" + errMsg);
} catch (Exception ex) { } catch (Exception ex) {
log.warn("腾讯文档推送异常企微通知失败: {}", ex.toString()); log.warn("腾讯文档推送异常企微通知失败: {}", ex.toString());

View File

@@ -148,7 +148,7 @@ public class ServerController
} }
/** /**
* 手动测试企微闲鱼通知(经 wxSend goofish-active-push * 手动测试企微闲鱼通知(经 wxSend POST /wx/send/goofish与 /send/pdd 相同 vanToken + title/text/touser
*/ */
@PreAuthorize("@ss.hasPermi('monitor:server:list')") @PreAuthorize("@ss.hasPermi('monitor:server:list')")
@PostMapping("/health/goofish-notify-test") @PostMapping("/health/goofish-notify-test")

View File

@@ -220,8 +220,8 @@ jarvis:
wxsend-base-url: http://127.0.0.1:36699 wxsend-base-url: http://127.0.0.1:36699
# 须与 wxSend jarvis.wecom.push-secret 一致Header X-WxSend-WeCom-Push-Secret # 须与 wxSend jarvis.wecom.push-secret 一致Header X-WxSend-WeCom-Push-Secret
push-secret: jarvis_wecom_push_change_me push-secret: jarvis_wecom_push_change_me
# 闲鱼订单事件 → wxSend POST /wecom/goofish-active-push须与 wxSend goofish-push-secret 一致 # 与 /wx/send/pdd、/wx/send/goofish 请求头 vanToken 一致wxSend TokenUtil
goofish-push-secret: jarvis_wecom_goofish_push_change_me wxsend-van-token: super_token_b62190c26
# 接收企微通知的成员 UserID多个逗号或 |;留空则不推送 # 接收企微通知的成员 UserID多个逗号或 |;留空则不推送
goofish-notify-touser: "LinPinFan" goofish-notify-touser: "LinPinFan"
# 多轮会话:与 JDUtil interaction_state 类似TTL 与空闲超时(分钟) # 多轮会话:与 JDUtil interaction_state 类似TTL 与空闲超时(分钟)

View File

@@ -214,7 +214,7 @@ jarvis:
inbound-secret: jarvis_wecom_bridge_change_me inbound-secret: jarvis_wecom_bridge_change_me
wxsend-base-url: http://127.0.0.1:36699 wxsend-base-url: http://127.0.0.1:36699
push-secret: jarvis_wecom_push_change_me push-secret: jarvis_wecom_push_change_me
goofish-push-secret: jarvis_wecom_goofish_push_change_me wxsend-van-token: super_token_b62190c26
goofish-notify-touser: "LinPinFan" goofish-notify-touser: "LinPinFan"
session-ttl-minutes: 30 session-ttl-minutes: 30
session-idle-timeout-minutes: 30 session-idle-timeout-minutes: 30

View File

@@ -855,11 +855,10 @@ public class LogisticsServiceImpl implements ILogisticsService {
|| (distributionMark != null && distributionMark.contains("闲鱼")); || (distributionMark != null && distributionMark.contains("闲鱼"));
if (useGoofishWecom) { if (useGoofishWecom) {
String touserGoofish = getTouserByDistributionMark(distributionMark); String touserGoofish = getTouserByDistributionMark(distributionMark);
String fullText = "JD物流信息推送\n" + pushContent;
logger.info("闲鱼关联或分销含「闲鱼」:尝试企微闲鱼自建应用 - 订单ID: {}, 分销标识: {}, 接收人: {}", logger.info("闲鱼关联或分销含「闲鱼」:尝试企微闲鱼自建应用 - 订单ID: {}, 分销标识: {}, 接收人: {}",
order.getId(), distributionMark, order.getId(), distributionMark,
StringUtils.hasText(touserGoofish) ? touserGoofish : "(jarvis.wecom.goofish-notify-touser)"); StringUtils.hasText(touserGoofish) ? touserGoofish : "(jarvis.wecom.goofish-notify-touser)");
if (wxSendGoofishNotifyClient.pushGoofishAgentText(touserGoofish, fullText)) { if (wxSendGoofishNotifyClient.pushGoofishAgentText(touserGoofish, "JD物流信息推送", pushContent.toString())) {
logger.info("企微闲鱼应用推送成功 - 订单ID: {}, 订单号: {}, waybill_no: {}", logger.info("企微闲鱼应用推送成功 - 订单ID: {}, 订单号: {}, waybill_no: {}",
order.getId(), order.getOrderId(), waybillNo); order.getId(), order.getOrderId(), waybillNo);
return true; return true;

View File

@@ -213,7 +213,7 @@ public class TencentDocBatchPushServiceImpl implements ITencentDocBatchPushServi
continue; continue;
} }
String pushText = "【腾讯文档推送】批次长时间未结束,已标记为「已中断」\n" + resultMsg; String pushText = "【腾讯文档推送】批次长时间未结束,已标记为「已中断」\n" + resultMsg;
boolean ok = wxSendGoofishNotifyClient.pushGoofishAgentText(null, pushText); boolean ok = wxSendGoofishNotifyClient.pushGoofishAgentText(null, "", pushText);
if (ok) { if (ok) {
redisCache.setCacheObject(dedupeKey, "1", 7, TimeUnit.DAYS); redisCache.setCacheObject(dedupeKey, "1", 7, TimeUnit.DAYS);
} }

View File

@@ -353,7 +353,7 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS
Object msgObj = ar.get(AjaxResult.MSG_TAG); Object msgObj = ar.get(AjaxResult.MSG_TAG);
String msg = msgObj != null ? String.valueOf(msgObj) : "同步接口返回失败"; String msg = msgObj != null ? String.valueOf(msgObj) : "同步接口返回失败";
batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, msg); batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, msg);
wxSendGoofishNotifyClient.pushGoofishAgentText(null, wxSendGoofishNotifyClient.pushGoofishAgentText(null, "",
"【腾讯文档推送】定时批量同步失败\n批次: " + batchId + "\n" + msg); "【腾讯文档推送】定时批量同步失败\n批次: " + batchId + "\n" + msg);
} }
} }
@@ -364,7 +364,7 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS
if (batchId != null) { if (batchId != null) {
String msg = "批量同步调用失败: " + (ex.getMessage() != null ? ex.getMessage() : ex.getClass().getSimpleName()); String msg = "批量同步调用失败: " + (ex.getMessage() != null ? ex.getMessage() : ex.getClass().getSimpleName());
batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, msg); batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, msg);
wxSendGoofishNotifyClient.pushGoofishAgentText(null, wxSendGoofishNotifyClient.pushGoofishAgentText(null, "",
"【腾讯文档推送】定时批量同步异常\n批次: " + batchId + "\n" + msg); "【腾讯文档推送】定时批量同步异常\n批次: " + batchId + "\n" + msg);
} }
} }
@@ -376,7 +376,7 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS
try { try {
String msg = "执行批量同步失败: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName()); String msg = "执行批量同步失败: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName());
batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, msg); batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, msg);
wxSendGoofishNotifyClient.pushGoofishAgentText(null, wxSendGoofishNotifyClient.pushGoofishAgentText(null, "",
"【腾讯文档推送】定时批量同步异常\n批次: " + batchId + "\n" + msg); "【腾讯文档推送】定时批量同步异常\n批次: " + batchId + "\n" + msg);
} catch (Exception ex) { } catch (Exception ex) {
log.error("更新批量推送记录失败", ex); log.error("更新批量推送记录失败", ex);

View File

@@ -16,23 +16,22 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* 调用 wxSend 闲鱼自建应用文本推送POST /wecom/goofish-active-pushagent 见 wxSend qywx.app.goofishAgentId)。 * 调用 wxSend 闲鱼自建应用文本推送,与 /wx/send/pdd 一致Header vanToken + JSONtitle、text、touser)。
*/ */
@Component @Component
public class WxSendGoofishNotifyClient { public class WxSendGoofishNotifyClient {
private static final Logger log = LoggerFactory.getLogger(WxSendGoofishNotifyClient.class); private static final Logger log = LoggerFactory.getLogger(WxSendGoofishNotifyClient.class);
public static final String HEADER_GOOFISH_PUSH_SECRET = "X-WxSend-Goofish-Push-Secret";
/** 与企微 message/send 文本 content 上限对齐,预留余量 */ /** 与企微 message/send 文本 content 上限对齐,预留余量 */
private static final int CONTENT_MAX = 2000; private static final int CONTENT_MAX = 2000;
@Value("${jarvis.wecom.wxsend-base-url:}") @Value("${jarvis.wecom.wxsend-base-url:}")
private String wxsendBaseUrl; private String wxsendBaseUrl;
@Value("${jarvis.wecom.goofish-push-secret:}") /** 与 wxSend TokenUtil 校验一致,请求头 vanToken */
private String goofishPushSecret; @Value("${jarvis.wecom.wxsend-van-token:}")
private String wxsendVanToken;
/** /**
* 接收通知的成员 UserID企微管理后台可见多个用英文逗号或 | 分隔; * 接收通知的成员 UserID企微管理后台可见多个用英文逗号或 | 分隔;
@@ -50,7 +49,7 @@ public class WxSendGoofishNotifyClient {
* @param message 已截断的说明文案 * @param message 已截断的说明文案
*/ */
public void notifyGoofishEvent(String orderNo, String eventType, String source, String message) { public void notifyGoofishEvent(String orderNo, String eventType, String source, String message) {
if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(goofishPushSecret)) { if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(wxsendVanToken)) {
return; return;
} }
String toUser = buildTouserParam(goofishNotifyTouser); String toUser = buildTouserParam(goofishNotifyTouser);
@@ -63,22 +62,24 @@ public class WxSendGoofishNotifyClient {
} }
try { try {
String base = normalizeBase(wxsendBaseUrl); String base = normalizeBase(wxsendBaseUrl);
String url = base + "/wecom/goofish-active-push"; String url = base + "/wx/send/goofish";
postJson(url, toUser, content); postMessageReturnsOk(url, wxsendVanToken, "", content, toUser);
} catch (Exception e) { } catch (Exception e) {
log.warn("wxSend goofish-active-push 失败 orderNo={} err={}", orderNo, e.toString()); log.warn("wxSend /wx/send/goofish 失败 orderNo={} err={}", orderNo, e.toString());
} }
} }
/** /**
* 京东物流扫描等:全文发往企微「闲鱼」自建应用wxSend qywx.app.goofishAgentId)。 * 京东物流扫描等:发往企微「闲鱼」自建应用,请求体与 PDD 推送一致title + text + touser)。
* *
* @param optionalToUser 非空时用与 logistics.push.touser.* 相同的成员 ID逗号/| 亦可);空则用 goofish-notify-touser * @param optionalToUser 非空时用与 logistics.push.touser.* 相同的成员 ID逗号/| 亦可);空则用 goofish-notify-touser
* @return HTTP 2xx 为 true未配置密钥或 toUser 为空返回 false * @param title 与 {@code LogisticsServiceImpl} 推 PDD 时的 title 一致,可为空
* @param textBody 正文(对应 PDD 的 text
* @return wxSend 业务 code=200 为 true未配置 token 或 touser 为空返回 false
*/ */
public boolean pushGoofishAgentText(String optionalToUser, String content) { public boolean pushGoofishAgentText(String optionalToUser, String title, String textBody) {
if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(goofishPushSecret)) { if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(wxsendVanToken)) {
log.debug("wxSend 闲鱼应用物流推送跳过:未配置 wxsend-base-url 或 goofish-push-secret"); log.debug("wxSend 闲鱼应用物流推送跳过:未配置 wxsend-base-url 或 wxsend-van-token");
return false; return false;
} }
String rawTouser = StringUtils.hasText(optionalToUser) ? optionalToUser : goofishNotifyTouser; String rawTouser = StringUtils.hasText(optionalToUser) ? optionalToUser : goofishNotifyTouser;
@@ -87,16 +88,24 @@ public class WxSendGoofishNotifyClient {
log.warn("wxSend 闲鱼应用物流推送跳过:无接收人(请配置 jarvis.wecom.goofish-notify-touser 或 logistics.push.touser"); log.warn("wxSend 闲鱼应用物流推送跳过:无接收人(请配置 jarvis.wecom.goofish-notify-touser 或 logistics.push.touser");
return false; return false;
} }
String text = content != null ? content : ""; String t = title != null ? title : "";
if (text.length() > CONTENT_MAX) { String body = textBody != null ? textBody : "";
text = text.substring(0, CONTENT_MAX - 1) + ""; if (t.length() > CONTENT_MAX) {
t = t.substring(0, CONTENT_MAX - 1) + "";
body = "";
} else {
int overhead = StringUtils.hasText(t) ? t.length() + 1 : 0;
int maxBody = Math.max(0, CONTENT_MAX - overhead);
if (body.length() > maxBody) {
body = body.substring(0, Math.max(0, maxBody - 1)) + "";
}
} }
try { try {
String base = normalizeBase(wxsendBaseUrl); String base = normalizeBase(wxsendBaseUrl);
String url = base + "/wecom/goofish-active-push"; String url = base + "/wx/send/goofish";
return postJsonReturnsOk(url, toUser, text); return postMessageReturnsOk(url, wxsendVanToken, t, body, toUser);
} catch (Exception e) { } catch (Exception e) {
log.warn("wxSend goofish-active-push 物流全文失败 err={}", e.toString()); log.warn("wxSend /wx/send/goofish 物流全文失败 err={}", e.toString());
return false; return false;
} }
} }
@@ -157,40 +166,41 @@ public class WxSendGoofishNotifyClient {
if (!StringUtils.hasText(wxsendBaseUrl)) { if (!StringUtils.hasText(wxsendBaseUrl)) {
return ""; return "";
} }
return normalizeBase(wxsendBaseUrl) + "/wecom/goofish-active-push"; return normalizeBase(wxsendBaseUrl) + "/wx/send/goofish";
} }
/** /**
* 服务监控手动测试:经 wxSend 向企微「闲鱼」应用发一条文本。 * 服务监控手动测试:经 wxSend 向企微「闲鱼」应用发一条文本。
* *
* @return null 表示 HTTP 2xx 且调用链未抛错;非空为可直接展示的失败原因 * @return null 表示 HTTP 2xx 且 wxSend 返回 code=200;非空为可直接展示的失败原因
*/ */
public String testGoofishNotify() { public String testGoofishNotify() {
if (!StringUtils.hasText(wxsendBaseUrl)) { if (!StringUtils.hasText(wxsendBaseUrl)) {
return "未配置 jarvis.wecom.wxsend-base-url"; return "未配置 jarvis.wecom.wxsend-base-url";
} }
if (!StringUtils.hasText(goofishPushSecret)) { if (!StringUtils.hasText(wxsendVanToken)) {
return "未配置 jarvis.wecom.goofish-push-secret"; return "未配置 jarvis.wecom.wxsend-van-token须与 wxSend TokenUtil 一致)";
} }
if (!StringUtils.hasText(goofishNotifyTouser)) { if (!StringUtils.hasText(goofishNotifyTouser)) {
return "未配置 jarvis.wecom.goofish-notify-touser接收人为空"; return "未配置 jarvis.wecom.goofish-notify-touser接收人为空";
} }
String content = "【服务监控·闲鱼通知测试】RuoYi 手动触发 " + new java.util.Date(); String content = "【服务监控·闲鱼通知测试】RuoYi 手动触发 " + new java.util.Date();
boolean ok = pushGoofishAgentText(null, content); boolean ok = pushGoofishAgentText(null, "", content);
if (ok) { if (ok) {
return null; return null;
} }
return "推送未成功:请核对 wxSend 与企微应用、密钥及接收人,或查看服务端日志"; return "推送未成功:请核对 wxSend 服务、vanToken、企微闲鱼应用及接收人,或查看服务端日志";
} }
private void postJson(String url, String toUser, String content) throws Exception { /**
postJsonReturnsOk(url, toUser, content); * POST {@code /wx/send/goofish}body 与 {@code /wx/send/pdd} 相同字段名title、text、touser
} */
private boolean postMessageReturnsOk(String url, String vanToken, String title, String text, String touserPipeJoined)
private boolean postJsonReturnsOk(String url, String toUser, String content) throws Exception { throws Exception {
JSONObject body = new JSONObject(); JSONObject body = new JSONObject();
body.put("toUser", toUser); body.put("title", title != null ? title : "");
body.put("content", content); body.put("text", text != null ? text : "");
body.put("touser", touserPipeJoined);
byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8); byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8);
HttpURLConnection conn = null; HttpURLConnection conn = null;
try { try {
@@ -200,19 +210,32 @@ public class WxSendGoofishNotifyClient {
conn.setReadTimeout(60000); conn.setReadTimeout(60000);
conn.setDoOutput(true); conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
conn.setRequestProperty(HEADER_GOOFISH_PUSH_SECRET, goofishPushSecret); conn.setRequestProperty("vanToken", vanToken);
try (OutputStream os = conn.getOutputStream()) { try (OutputStream os = conn.getOutputStream()) {
os.write(bytes); os.write(bytes);
} }
int code = conn.getResponseCode(); int httpCode = conn.getResponseCode();
InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream(); InputStream is = httpCode >= 200 && httpCode < 300 ? conn.getInputStream() : conn.getErrorStream();
String resp = readAll(is); String resp = readAll(is);
if (code < 200 || code >= 300) { if (httpCode < 200 || httpCode >= 300) {
log.warn("wxSend goofish-active-push HTTP {} body={}", code, resp); log.warn("wxSend /wx/send/goofish HTTP {} body={}", httpCode, resp);
return false; return false;
} }
log.debug("wxSend goofish-active-push OK http={} resp={}", code, resp); Integer bizCode = null;
try {
JSONObject jo = JSONObject.parseObject(resp);
if (jo != null) {
bizCode = jo.getInteger("code");
}
} catch (Exception parseEx) {
log.debug("解析 wxSend 响应: {}", parseEx.toString());
}
if (bizCode != null && bizCode == 200) {
log.debug("wxSend /wx/send/goofish OK resp={}", resp);
return true; return true;
}
log.warn("wxSend /wx/send/goofish 业务未成功 resp={}", resp);
return false;
} finally { } finally {
if (conn != null) { if (conn != null) {
conn.disconnect(); conn.disconnect();