1
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
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;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* 调用 wxSend 的企微应用文本主动推送(POST /wecom/active-push)。
|
||||
*/
|
||||
@Component
|
||||
public class WxSendWeComPushClient {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WxSendWeComPushClient.class);
|
||||
|
||||
public static final String HEADER_PUSH_SECRET = "X-WxSend-WeCom-Push-Secret";
|
||||
|
||||
@Value("${jarvis.wecom.wxsend-base-url:}")
|
||||
private String wxsendBaseUrl;
|
||||
|
||||
@Value("${jarvis.wecom.push-secret:}")
|
||||
private String pushSecret;
|
||||
|
||||
/**
|
||||
* 在被动回复返回后延迟再发,保证企微侧先出现首条被动消息。
|
||||
*/
|
||||
public void scheduleActivePushes(String toUser, List<String> contents) {
|
||||
if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(pushSecret)
|
||||
|| !StringUtils.hasText(toUser) || contents == null || contents.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final String userId = toUser.trim();
|
||||
final List<String> list = new ArrayList<>(contents);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
Thread.sleep(450);
|
||||
String base = normalizeBase(wxsendBaseUrl);
|
||||
String url = base + "/wecom/active-push";
|
||||
for (String c : list) {
|
||||
if (!StringUtils.hasText(c)) {
|
||||
continue;
|
||||
}
|
||||
postJson(url, userId, c.trim());
|
||||
Thread.sleep(120);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("企微主动推送任务异常 userId={} msg={}", userId, e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
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_PUSH_SECRET, pushSecret);
|
||||
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 active-push HTTP {} body={}", code, resp);
|
||||
} else {
|
||||
log.debug("wxSend active-push OK http={} resp={}", code, resp);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("wxSend active-push 请求失败 url={} err={}", url, e.toString());
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@ package com.ruoyi.web.controller.jarvis;
|
||||
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.jarvis.domain.dto.WeComInboundRequest;
|
||||
import com.ruoyi.jarvis.domain.dto.WeComInboundResult;
|
||||
import com.ruoyi.jarvis.service.IWeComInboundService;
|
||||
import com.ruoyi.jarvis.service.IWeComInboundTraceService;
|
||||
import com.ruoyi.jarvis.wecom.WxSendWeComPushClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -28,6 +30,8 @@ public class WeComInboundController {
|
||||
private IWeComInboundService weComInboundService;
|
||||
@Resource
|
||||
private IWeComInboundTraceService weComInboundTraceService;
|
||||
@Resource
|
||||
private WxSendWeComPushClient wxSendWeComPushClient;
|
||||
|
||||
@PostMapping("/inbound")
|
||||
public AjaxResult inbound(
|
||||
@@ -37,10 +41,12 @@ public class WeComInboundController {
|
||||
return AjaxResult.error("拒绝访问");
|
||||
}
|
||||
WeComInboundRequest req = body != null ? body : new WeComInboundRequest();
|
||||
String reply = weComInboundService.handleInbound(req);
|
||||
weComInboundTraceService.recordInbound(req, reply);
|
||||
Map<String, Object> data = new HashMap<>(2);
|
||||
data.put("reply", reply != null ? reply : "");
|
||||
WeComInboundResult result = weComInboundService.handleInbound(req);
|
||||
weComInboundTraceService.recordInbound(req, result.toTraceFullText());
|
||||
Map<String, Object> data = new HashMap<>(4);
|
||||
data.put("reply", result.getPassiveReply());
|
||||
data.put("activePushCount", result.getActivePushContents().size());
|
||||
wxSendWeComPushClient.scheduleActivePushes(req.getFromUserName(), result.getActivePushContents());
|
||||
return AjaxResult.success(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,10 +210,24 @@ jarvis:
|
||||
# 企微经 wxSend 调用本接口时校验(须与 wxSend 配置一致)
|
||||
wecom:
|
||||
inbound-secret: jarvis_wecom_bridge_change_me
|
||||
# wxSend 根地址(无尾斜杠),用于 F 录单等第二条起主动推送;与 wxSend server.port 一致
|
||||
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
|
||||
# 多轮会话:与 JDUtil interaction_state 类似,TTL 与空闲超时(分钟)
|
||||
session-ttl-minutes: 30
|
||||
session-idle-timeout-minutes: 30
|
||||
session-sweep-ms: 60000
|
||||
# 企微「开」+ 手机号:Jarvis POST 该局域网接口,将响应中的 reply_text 被动回复给用户
|
||||
phone-forward:
|
||||
enabled: true
|
||||
base-url: http://192.168.8.60:18080
|
||||
path: /v1/forward
|
||||
connect-timeout-ms: 8000
|
||||
# wait_reply 时服务端会等多条 Bot 回复,宜适当加大
|
||||
read-timeout-ms: 120000
|
||||
wait-reply: true
|
||||
reply-take-nth: 2
|
||||
# Ollama 大模型服务(监控健康度调试用)
|
||||
ollama:
|
||||
base-url: http://192.168.8.34:11434
|
||||
|
||||
@@ -208,9 +208,19 @@ jarvis:
|
||||
base-url: http://192.168.8.60:5008
|
||||
wecom:
|
||||
inbound-secret: jarvis_wecom_bridge_change_me
|
||||
wxsend-base-url: http://127.0.0.1:36699
|
||||
push-secret: jarvis_wecom_push_change_me
|
||||
session-ttl-minutes: 30
|
||||
session-idle-timeout-minutes: 30
|
||||
session-sweep-ms: 60000
|
||||
phone-forward:
|
||||
enabled: true
|
||||
base-url: http://192.168.8.60:18080
|
||||
path: /v1/forward
|
||||
connect-timeout-ms: 8000
|
||||
read-timeout-ms: 120000
|
||||
wait-reply: true
|
||||
reply-take-nth: 2
|
||||
# Ollama 大模型服务(监控健康度调试用)
|
||||
ollama:
|
||||
base-url: http://192.168.8.34:11434
|
||||
|
||||
Reference in New Issue
Block a user