1
This commit is contained in:
@@ -12,7 +12,7 @@ public class PhoneForwardActivePushImpl implements IPhoneForwardActivePush {
|
|||||||
private WxSendWeComPushClient wxSendWeComPushClient;
|
private WxSendWeComPushClient wxSendWeComPushClient;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean schedulePushChunks(String toUser, List<String> chunks) {
|
public boolean schedulePushChunks(String toUser, String wecomCallbackAgentId, List<String> chunks) {
|
||||||
return wxSendWeComPushClient.pushAfterPassiveDelaySync(toUser, chunks);
|
return wxSendWeComPushClient.pushAfterPassiveDelaySync(toUser, wecomCallbackAgentId, chunks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,13 @@ public class WxSendWeComPushClient {
|
|||||||
private String pushSecret;
|
private String pushSecret;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 与 {@link #scheduleActivePushes(String, List)} 相同顺序与延迟,但在当前线程同步执行,并返回是否全部成功。
|
* 与 {@link #scheduleActivePushes(String, String, List)} 相同顺序与延迟,但在当前线程同步执行,并返回是否全部成功。
|
||||||
* 供「开/慢开」异步 TG 查询结束后立刻知晓推送结果并打错误日志。
|
* {@code wecomCallbackAgentId} 与回调 XML 中 AgentId 一致时须传入,便于 wxSend 选用对应应用 secret 调用 message/send。
|
||||||
*/
|
*/
|
||||||
public boolean pushAfterPassiveDelaySync(String toUser, List<String> contents) {
|
public boolean pushAfterPassiveDelaySync(String toUser, String wecomCallbackAgentId, List<String> contents) {
|
||||||
final String userId = toUser != null ? toUser.trim() : "";
|
final String userId = toUser != null ? toUser.trim() : "";
|
||||||
final List<String> list = contents != null ? new ArrayList<>(contents) : Collections.emptyList();
|
final List<String> list = contents != null ? new ArrayList<>(contents) : Collections.emptyList();
|
||||||
|
final String agentOpt = StringUtils.hasText(wecomCallbackAgentId) ? wecomCallbackAgentId.trim() : null;
|
||||||
|
|
||||||
if (!StringUtils.hasText(wxsendBaseUrl)) {
|
if (!StringUtils.hasText(wxsendBaseUrl)) {
|
||||||
log.error("企微主动推送未执行:未配置 jarvis.wecom.wxsend-base-url(用户会话中收不到查询结果或报错)");
|
log.error("企微主动推送未执行:未配置 jarvis.wecom.wxsend-base-url(用户会话中收不到查询结果或报错)");
|
||||||
@@ -69,7 +70,7 @@ public class WxSendWeComPushClient {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
anySent = true;
|
anySent = true;
|
||||||
if (!postJson(url, userId, c.trim())) {
|
if (!postJson(url, userId, c.trim(), agentOpt)) {
|
||||||
allOk = false;
|
allOk = false;
|
||||||
}
|
}
|
||||||
Thread.sleep(120);
|
Thread.sleep(120);
|
||||||
@@ -95,11 +96,13 @@ public class WxSendWeComPushClient {
|
|||||||
/**
|
/**
|
||||||
* 在被动回复返回后再发,保证企微侧先出现首条被动消息。
|
* 在被动回复返回后再发,保证企微侧先出现首条被动消息。
|
||||||
*/
|
*/
|
||||||
public void scheduleActivePushes(String toUser, List<String> contents) {
|
public void scheduleActivePushes(String toUser, String wecomCallbackAgentId, List<String> contents) {
|
||||||
final String userId = toUser != null ? toUser.trim() : "";
|
final String userId = toUser != null ? toUser.trim() : "";
|
||||||
|
final String agentPass = wecomCallbackAgentId != null ? wecomCallbackAgentId.trim() : "";
|
||||||
final List<String> list = contents != null ? new ArrayList<>(contents) : Collections.emptyList();
|
final List<String> list = contents != null ? new ArrayList<>(contents) : Collections.emptyList();
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
boolean ok = pushAfterPassiveDelaySync(userId, list);
|
boolean ok = pushAfterPassiveDelaySync(
|
||||||
|
userId, StringUtils.hasText(agentPass) ? agentPass : null, list);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
log.error(
|
log.error(
|
||||||
"scheduleActivePushes 未完全成功 userId={}(用户可能未收到会话内的后续分段)",
|
"scheduleActivePushes 未完全成功 userId={}(用户可能未收到会话内的后续分段)",
|
||||||
@@ -117,10 +120,13 @@ public class WxSendWeComPushClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @return HTTP 2xx 且无异常时为 true */
|
/** @return HTTP 2xx 且无异常时为 true */
|
||||||
private boolean postJson(String url, String toUser, String content) {
|
private boolean postJson(String url, String toUser, String content, String agentIdOpt) {
|
||||||
JSONObject body = new JSONObject();
|
JSONObject body = new JSONObject();
|
||||||
body.put("toUser", toUser);
|
body.put("toUser", toUser);
|
||||||
body.put("content", content);
|
body.put("content", content);
|
||||||
|
if (StringUtils.hasText(agentIdOpt)) {
|
||||||
|
body.put("agentId", agentIdOpt.trim());
|
||||||
|
}
|
||||||
byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8);
|
byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8);
|
||||||
HttpURLConnection conn = null;
|
HttpURLConnection conn = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.ruoyi.jarvis.domain.dto.WeComInboundRequest;
|
|||||||
import com.ruoyi.jarvis.domain.dto.WeComInboundResult;
|
import com.ruoyi.jarvis.domain.dto.WeComInboundResult;
|
||||||
import com.ruoyi.jarvis.service.IWeComInboundService;
|
import com.ruoyi.jarvis.service.IWeComInboundService;
|
||||||
import com.ruoyi.jarvis.service.IWeComInboundTraceService;
|
import com.ruoyi.jarvis.service.IWeComInboundTraceService;
|
||||||
|
import com.ruoyi.jarvis.service.SuperAdminService;
|
||||||
import com.ruoyi.jarvis.wecom.WxSendWeComPushClient;
|
import com.ruoyi.jarvis.wecom.WxSendWeComPushClient;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -32,6 +33,8 @@ public class WeComInboundController {
|
|||||||
private IWeComInboundTraceService weComInboundTraceService;
|
private IWeComInboundTraceService weComInboundTraceService;
|
||||||
@Resource
|
@Resource
|
||||||
private WxSendWeComPushClient wxSendWeComPushClient;
|
private WxSendWeComPushClient wxSendWeComPushClient;
|
||||||
|
@Resource
|
||||||
|
private SuperAdminService superAdminService;
|
||||||
|
|
||||||
@PostMapping("/inbound")
|
@PostMapping("/inbound")
|
||||||
public AjaxResult inbound(
|
public AjaxResult inbound(
|
||||||
@@ -46,7 +49,12 @@ public class WeComInboundController {
|
|||||||
Map<String, Object> data = new HashMap<>(4);
|
Map<String, Object> data = new HashMap<>(4);
|
||||||
data.put("reply", result.getPassiveReply());
|
data.put("reply", result.getPassiveReply());
|
||||||
data.put("activePushCount", result.getActivePushContents().size());
|
data.put("activePushCount", result.getActivePushContents().size());
|
||||||
wxSendWeComPushClient.scheduleActivePushes(req.getFromUserName(), result.getActivePushContents());
|
String pipeTouser = superAdminService.resolveTouserPipeForActivePush(req.getFromUserName());
|
||||||
|
if (!StringUtils.hasText(pipeTouser) && StringUtils.hasText(req.getFromUserName())) {
|
||||||
|
pipeTouser = req.getFromUserName().trim();
|
||||||
|
}
|
||||||
|
String callbackAgentId = StringUtils.hasText(req.getAgentId()) ? req.getAgentId().trim() : null;
|
||||||
|
wxSendWeComPushClient.scheduleActivePushes(pipeTouser, callbackAgentId, result.getActivePushContents());
|
||||||
return AjaxResult.success(data);
|
return AjaxResult.success(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ public interface IPhoneForwardActivePush {
|
|||||||
/**
|
/**
|
||||||
* 在被动回复已发出后,按序推送到企微成员(首条前短暂延迟,避免次序错乱)。
|
* 在被动回复已发出后,按序推送到企微成员(首条前短暂延迟,避免次序错乱)。
|
||||||
*
|
*
|
||||||
* @param toUser 成员 UserID(FromUserName)
|
* @param toUser 企微 message/send 的 touser(可与 XML FromUserName 相同,或由超级管理员 touser 解析为 {@code |} 分隔多成员)
|
||||||
|
* @param wecomCallbackAgentId 回调明文 XML 中的 AgentId(字符串);空则 wxSend 使用默认 {@code qywx.app.agentId}
|
||||||
* @param chunks 每段不超过 UTF-8 2048 字节的正文
|
* @param chunks 每段不超过 UTF-8 2048 字节的正文
|
||||||
* @return {@code true} 表示配置齐全且每一段非空正文均收到 wxSend 2xx 响应
|
* @return {@code true} 表示配置齐全且每一段非空正文均收到 wxSend 2xx 响应
|
||||||
*/
|
*/
|
||||||
boolean schedulePushChunks(String toUser, List<String> chunks);
|
boolean schedulePushChunks(String toUser, String wecomCallbackAgentId, List<String> chunks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,4 +61,10 @@ public interface SuperAdminService
|
|||||||
SuperAdmin selectSuperAdminByUnionId(Long unionId);
|
SuperAdmin selectSuperAdminByUnionId(Long unionId);
|
||||||
|
|
||||||
SuperAdmin selectSuperAdminByWecomUserId(String wxid);
|
SuperAdmin selectSuperAdminByWecomUserId(String wxid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微 {@code message/send} 的 {@code touser}:优先超级管理员表 {@code touser}(支持英文/中文逗号,转换为 {@code |}),
|
||||||
|
* 否则为发消息成员 UserID。逻辑与 {@link com.ruoyi.jarvis.service.impl.WeComInboundServiceImpl} 中 {@code resolveTouser} 一致。
|
||||||
|
*/
|
||||||
|
String resolveTouserPipeForActivePush(String fromWecomUserId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import com.ruoyi.jarvis.domain.TgScalperPhone;
|
|||||||
import com.ruoyi.jarvis.domain.dto.WeComInboundRequest;
|
import com.ruoyi.jarvis.domain.dto.WeComInboundRequest;
|
||||||
import com.ruoyi.jarvis.service.IPhoneForwardActivePush;
|
import com.ruoyi.jarvis.service.IPhoneForwardActivePush;
|
||||||
import com.ruoyi.jarvis.service.ITgScalperPhoneService;
|
import com.ruoyi.jarvis.service.ITgScalperPhoneService;
|
||||||
|
import com.ruoyi.jarvis.service.SuperAdminService;
|
||||||
import com.ruoyi.jarvis.util.WeComUtf8ChunkUtil;
|
import com.ruoyi.jarvis.util.WeComUtf8ChunkUtil;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -138,6 +139,9 @@ public class OpenPhoneForwardService {
|
|||||||
@Resource
|
@Resource
|
||||||
private ITgScalperPhoneService tgScalperPhoneService;
|
private ITgScalperPhoneService tgScalperPhoneService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SuperAdminService superAdminService;
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private IPhoneForwardActivePush phoneForwardActivePush;
|
private IPhoneForwardActivePush phoneForwardActivePush;
|
||||||
|
|
||||||
@@ -218,8 +222,9 @@ public class OpenPhoneForwardService {
|
|||||||
|
|
||||||
if (asyncResultPushEnabled && phoneForwardActivePush != null && StringUtils.hasText(req.getFromUserName())) {
|
if (asyncResultPushEnabled && phoneForwardActivePush != null && StringUtils.hasText(req.getFromUserName())) {
|
||||||
markForwardInflight(cKey);
|
markForwardInflight(cKey);
|
||||||
final String toUser = req.getFromUserName().trim();
|
final String pushTouserPipe = superAdminService.resolveTouserPipeForActivePush(req.getFromUserName());
|
||||||
CompletableFuture.runAsync(() -> runForwardAndPush(toUser, phone, bot, cKey));
|
final String callbackAgentId = StringUtils.hasText(req.getAgentId()) ? req.getAgentId().trim() : null;
|
||||||
|
CompletableFuture.runAsync(() -> runForwardAndPush(pushTouserPipe, callbackAgentId, phone, bot, cKey));
|
||||||
return String.format("收到电话:%s。\n后续结果将通过应用消息推送。", phone);
|
return String.format("收到电话:%s。\n后续结果将通过应用消息推送。", phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,31 +261,33 @@ public class OpenPhoneForwardService {
|
|||||||
forwardInflightUntilMs.remove(cKey);
|
forwardInflightUntilMs.remove(cKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runForwardAndPush(String toUser, String phone, String bot, String cKey) {
|
private void runForwardAndPush(String pushTouserPipe, String wecomCallbackAgentId, String phone, String bot, String cKey) {
|
||||||
try {
|
try {
|
||||||
log.info("phone-forward 异步 TG 查询开始 phone={} bot={} toUser={}", phone, bot, toUser);
|
log.info(
|
||||||
|
"phone-forward 异步 TG 查询开始 phone={} bot={} touser={} callbackAgentId={}",
|
||||||
|
phone, bot, pushTouserPipe, wecomCallbackAgentId);
|
||||||
String reply = doForward(phone, bot, dedupEnabled ? cKey : null);
|
String reply = doForward(phone, bot, dedupEnabled ? cKey : null);
|
||||||
List<String> chunks = WeComUtf8ChunkUtil.splitUtf8Chunks(reply, WeComUtf8ChunkUtil.WE_COM_TEXT_MAX_UTF8_BYTES);
|
List<String> chunks = WeComUtf8ChunkUtil.splitUtf8Chunks(reply, WeComUtf8ChunkUtil.WE_COM_TEXT_MAX_UTF8_BYTES);
|
||||||
chunks.removeIf(s -> !StringUtils.hasText(s));
|
chunks.removeIf(s -> !StringUtils.hasText(s));
|
||||||
if (chunks.isEmpty()) {
|
if (chunks.isEmpty()) {
|
||||||
chunks = Collections.singletonList("(无返回内容)");
|
chunks = Collections.singletonList("(无返回内容)");
|
||||||
}
|
}
|
||||||
boolean pushed = phoneForwardActivePush.schedulePushChunks(toUser, chunks);
|
boolean pushed = phoneForwardActivePush.schedulePushChunks(pushTouserPipe, wecomCallbackAgentId, chunks);
|
||||||
if (!pushed) {
|
if (!pushed) {
|
||||||
log.error(
|
log.error(
|
||||||
"phone-forward 结果未能推送到企微 user={} phone={}(可能为 TG/转发错误或服务端返回的正文,请检查 wxSend 与 jarvis.wecom)",
|
"phone-forward 结果未能推送到企微 touser={} phone={}(可能为 TG/转发错误或服务端返回的正文,请检查 wxSend 与 jarvis.wecom)",
|
||||||
toUser, phone);
|
pushTouserPipe, phone);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("phone-forward 异步处理异常 phone={} user={}", phone, toUser, e);
|
log.error("phone-forward 异步处理异常 phone={} user={}", phone, pushTouserPipe, e);
|
||||||
pushForwardFailureNotice(toUser, userVisibleThrowableMessage(e));
|
pushForwardFailureNotice(pushTouserPipe, wecomCallbackAgentId, userVisibleThrowableMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
clearForwardInflight(cKey);
|
clearForwardInflight(cKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushForwardFailureNotice(String toUser, String line) {
|
private void pushForwardFailureNotice(String pushTouserPipe, String wecomCallbackAgentId, String line) {
|
||||||
if (phoneForwardActivePush == null || !StringUtils.hasText(toUser)) {
|
if (phoneForwardActivePush == null || !StringUtils.hasText(pushTouserPipe)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -288,9 +295,9 @@ public class OpenPhoneForwardService {
|
|||||||
if (parts.isEmpty()) {
|
if (parts.isEmpty()) {
|
||||||
parts = Collections.singletonList("「转发服务」异常,请稍后重试。");
|
parts = Collections.singletonList("「转发服务」异常,请稍后重试。");
|
||||||
}
|
}
|
||||||
boolean ok = phoneForwardActivePush.schedulePushChunks(toUser.trim(), parts);
|
boolean ok = phoneForwardActivePush.schedulePushChunks(pushTouserPipe.trim(), wecomCallbackAgentId, parts);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
log.error("phone-forward 异常说明未能推送到企微 user={}", toUser);
|
log.error("phone-forward 异常说明未能推送到企微 user={}", pushTouserPipe);
|
||||||
}
|
}
|
||||||
} catch (Exception e2) {
|
} catch (Exception e2) {
|
||||||
log.warn("phone-forward 异常说明推送过程失败 err={}", e2.toString());
|
log.warn("phone-forward 异常说明推送过程失败 err={}", e2.toString());
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.jarvis.service.impl;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import com.ruoyi.jarvis.mapper.SuperAdminMapper;
|
import com.ruoyi.jarvis.mapper.SuperAdminMapper;
|
||||||
import com.ruoyi.jarvis.domain.SuperAdmin;
|
import com.ruoyi.jarvis.domain.SuperAdmin;
|
||||||
import com.ruoyi.jarvis.service.SuperAdminService;
|
import com.ruoyi.jarvis.service.SuperAdminService;
|
||||||
@@ -15,6 +16,9 @@ import com.ruoyi.jarvis.service.SuperAdminService;
|
|||||||
@Service
|
@Service
|
||||||
public class SuperAdminServiceImpl implements SuperAdminService
|
public class SuperAdminServiceImpl implements SuperAdminService
|
||||||
{
|
{
|
||||||
|
/** 与 {@link com.ruoyi.jarvis.service.impl.WeComInboundServiceImpl#WE_COM_SUPER_USER_ID} 一致 */
|
||||||
|
private static final String WE_COM_SUPER_USER_ID = "LinPingFan";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SuperAdminMapper superAdminMapper;
|
private SuperAdminMapper superAdminMapper;
|
||||||
|
|
||||||
@@ -99,4 +103,47 @@ public class SuperAdminServiceImpl implements SuperAdminService
|
|||||||
public SuperAdmin selectSuperAdminByWecomUserId(String wxid) {
|
public SuperAdmin selectSuperAdminByWecomUserId(String wxid) {
|
||||||
return superAdminMapper.selectSuperAdminByWecomUserId(wxid);
|
return superAdminMapper.selectSuperAdminByWecomUserId(wxid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolveTouserPipeForActivePush(String fromWecomUserId) {
|
||||||
|
if (!StringUtils.hasText(fromWecomUserId)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String from = fromWecomUserId.trim();
|
||||||
|
boolean isSuper = WE_COM_SUPER_USER_ID.equals(from);
|
||||||
|
SuperAdmin row = selectSuperAdminByWecomUserId(from);
|
||||||
|
String raw = null;
|
||||||
|
if (row != null && StringUtils.hasText(row.getTouser())) {
|
||||||
|
raw = row.getTouser().trim();
|
||||||
|
} else if (isSuper) {
|
||||||
|
SuperAdmin ping = selectSuperAdminByWecomUserId(WE_COM_SUPER_USER_ID);
|
||||||
|
if (ping != null && StringUtils.hasText(ping.getTouser())) {
|
||||||
|
raw = ping.getTouser().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (raw != null) {
|
||||||
|
return commaSeparatedToWeComTouser(raw);
|
||||||
|
}
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 企微 API:多个成员 UserID 用 {@code |} 分隔 */
|
||||||
|
private static String commaSeparatedToWeComTouser(String raw) {
|
||||||
|
if (!StringUtils.hasText(raw)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String[] parts = raw.split("[,,]");
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String p : parts) {
|
||||||
|
String t = p != null ? p.trim() : "";
|
||||||
|
if (t.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append('|');
|
||||||
|
}
|
||||||
|
sb.append(t);
|
||||||
|
}
|
||||||
|
return sb.length() > 0 ? sb.toString() : raw.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user