This commit is contained in:
van
2026-04-10 00:39:44 +08:00
parent 0205fe2c09
commit ce3af838bd
4 changed files with 182 additions and 0 deletions

View File

@@ -214,6 +214,10 @@ jarvis:
wxsend-base-url: http://127.0.0.1:36699
# 须与 wxSend jarvis.wecom.push-secret 一致Header X-WxSend-WeCom-Push-Secret
push-secret: jarvis_wecom_push_change_me
# 闲鱼订单事件 → wxSend POST /wecom/goofish-active-push须与 wxSend goofish-push-secret 一致)
goofish-push-secret: jarvis_wecom_goofish_push_change_me
# 接收企微通知的成员 UserID多个逗号或 |;留空则不推送
goofish-notify-touser: "LinPinFan"
# 多轮会话:与 JDUtil interaction_state 类似TTL 与空闲超时(分钟)
session-ttl-minutes: 30
session-idle-timeout-minutes: 30

View File

@@ -210,6 +210,8 @@ jarvis:
inbound-secret: jarvis_wecom_bridge_change_me
wxsend-base-url: http://127.0.0.1:36699
push-secret: jarvis_wecom_push_change_me
goofish-push-secret: jarvis_wecom_goofish_push_change_me
goofish-notify-touser: "LinPinFan"
session-ttl-minutes: 30
session-idle-timeout-minutes: 30
session-sweep-ms: 60000

View File

@@ -5,6 +5,7 @@ import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.jarvis.domain.ErpGoofishOrder;
import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog;
import com.ruoyi.jarvis.mapper.ErpGoofishOrderEventLogMapper;
import com.ruoyi.jarvis.wecom.WxSendGoofishNotifyClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@@ -30,6 +31,9 @@ public class GoofishOrderChangeLogger {
@Resource
private ErpGoofishOrderEventLogMapper erpGoofishOrderEventLogMapper;
@Resource
private WxSendGoofishNotifyClient wxSendGoofishNotifyClient;
public void append(Long orderId, String appKey, String orderNo, String eventType, String source, String message) {
if (orderId == null || StringUtils.isEmpty(message)) {
return;
@@ -47,8 +51,14 @@ public class GoofishOrderChangeLogger {
erpGoofishOrderEventLogMapper.insert(row);
} catch (Exception e) {
log.warn("闲管家订单事件日志写入失败 orderId={} {}", orderId, e.toString());
return;
}
log.info("[goofish-order-event] orderId={} orderNo={} type={} source={} {}", orderId, orderNo, eventType, source, msg);
try {
wxSendGoofishNotifyClient.notifyGoofishEvent(orderNo, eventType, source, msg);
} catch (Exception ex) {
log.debug("闲鱼订单事件 wxSend 通知跳过 err={}", ex.toString());
}
}
/**

View File

@@ -0,0 +1,166 @@
package com.ruoyi.jarvis.wecom;
import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* 调用 wxSend 闲鱼自建应用文本推送POST /wecom/goofish-active-pushagent 见 wxSend qywx.app.goofishAgentId
*/
@Component
public class WxSendGoofishNotifyClient {
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 上限对齐,预留余量 */
private static final int CONTENT_MAX = 2000;
@Value("${jarvis.wecom.wxsend-base-url:}")
private String wxsendBaseUrl;
@Value("${jarvis.wecom.goofish-push-secret:}")
private String goofishPushSecret;
/**
* 接收通知的成员 UserID企微管理后台可见多个用英文逗号或 | 分隔;
* 发往 wxSend 时会规范为 touser 的 user1|user2 形式。留空表示不推送。
*/
@Value("${jarvis.wecom.goofish-notify-touser:}")
private String goofishNotifyTouser;
/**
* 闲鱼订单事件通知 wxSend由 wxSend 转企微应用消息)。
*
* @param orderNo 闲鱼订单号
* @param eventType ORDER_SYNC / LOGISTICS_SYNC / SHIP
* @param source NOTIFY、LIST、DETAIL_REFRESH 等
* @param message 已截断的说明文案
*/
public void notifyGoofishEvent(String orderNo, String eventType, String source, String message) {
if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(goofishPushSecret)) {
return;
}
String toUser = buildTouserParam(goofishNotifyTouser);
if (!StringUtils.hasText(toUser)) {
return;
}
String content = buildContent(orderNo, eventType, source, message);
if (!StringUtils.hasText(content)) {
return;
}
try {
String base = normalizeBase(wxsendBaseUrl);
String url = base + "/wecom/goofish-active-push";
postJson(url, toUser, content);
} catch (Exception e) {
log.warn("wxSend goofish-active-push 失败 orderNo={} err={}", orderNo, e.toString());
}
}
private static String buildContent(String orderNo, String eventType, String source, String message) {
StringBuilder sb = new StringBuilder();
sb.append("【闲鱼订单】").append(orderNo != null ? orderNo : "-").append("\n");
if (StringUtils.hasText(eventType)) {
sb.append(eventType);
}
if (StringUtils.hasText(source)) {
sb.append(" | ").append(source);
}
sb.append("\n");
if (StringUtils.hasText(message)) {
sb.append(message);
}
String s = sb.toString();
if (s.length() > CONTENT_MAX) {
return s.substring(0, CONTENT_MAX - 1) + "";
}
return s;
}
/**
* 逗号或 | 分隔的 UserID → 企微 API 要求的 user1|user2
*/
private static String buildTouserParam(String raw) {
if (!StringUtils.hasText(raw)) {
return null;
}
String[] parts = raw.split("[,|]");
List<String> ids = new ArrayList<>();
for (String p : parts) {
String t = p == null ? "" : p.trim();
if (t.length() > 0) {
ids.add(t);
}
}
if (ids.isEmpty()) {
return null;
}
return String.join("|", ids);
}
private static String normalizeBase(String base) {
String b = base.trim();
if (b.endsWith("/")) {
return b.substring(0, b.length() - 1);
}
return b;
}
private void postJson(String url, String toUser, String content) throws Exception {
JSONObject body = new JSONObject();
body.put("toUser", toUser);
body.put("content", content);
byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8);
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(15000);
conn.setReadTimeout(60000);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
conn.setRequestProperty(HEADER_GOOFISH_PUSH_SECRET, goofishPushSecret);
try (OutputStream os = conn.getOutputStream()) {
os.write(bytes);
}
int code = conn.getResponseCode();
InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream();
String resp = readAll(is);
if (code < 200 || code >= 300) {
log.warn("wxSend goofish-active-push HTTP {} body={}", code, resp);
} else {
log.debug("wxSend goofish-active-push OK http={} resp={}", code, resp);
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
private static String readAll(InputStream is) throws java.io.IOException {
if (is == null) {
return "";
}
byte[] buf = new byte[4096];
StringBuilder sb = new StringBuilder();
int n;
while ((n = is.read(buf)) >= 0) {
sb.append(new String(buf, 0, n, StandardCharsets.UTF_8));
}
return sb.toString();
}
}