diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/dto/WeComInboundRequest.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/dto/WeComInboundRequest.java index c1d3556..2cbf586 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/dto/WeComInboundRequest.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/dto/WeComInboundRequest.java @@ -1,9 +1,14 @@ package com.ruoyi.jarvis.domain.dto; /** - * 企微消息经 wxSend 转发至 Jarvis 的请求体 + * 企微消息经 wxSend 转发至 Jarvis 的请求体。 + *
+ * 身份约定:{@link #fromUserName} 必须为解密后 XML 中的成员 UserID,节点名 {@code FromUserName}(见 {@link com.ruoyi.jarvis.wecom.WeComConvention}), + * 全链路(会话 Redis、权限识别目标)均以此为准,勿用 MsgId、纯展示昵称等。 + *
*/ public class WeComInboundRequest { + /** 成员 UserID,与 XML {@code FromUserName} 同源 */ private String fromUserName; private String content; /** 企微 CorpId */ diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/SuperAdminMapper.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/SuperAdminMapper.java index 3ec6857..117f0ec 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/SuperAdminMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/SuperAdminMapper.java @@ -67,7 +67,7 @@ public interface SuperAdminMapper public int deleteSuperAdminByIds(Long[] ids); /** - * 企微成员 UserID 对应 super_admin.wxid + * 企微成员 UserID:匹配 super_admin.wxid,或包含在 touser(逗号分隔)中 */ SuperAdmin selectSuperAdminByWecomUserId(String wxid); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComChatSessionService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComChatSessionService.java index b69a699..2efe310 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComChatSessionService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComChatSessionService.java @@ -1,15 +1,20 @@ package com.ruoyi.jarvis.service; import com.ruoyi.jarvis.domain.dto.WeComChatSession; +import com.ruoyi.jarvis.wecom.WeComConvention; /** - * 企微多轮会话(Redis JSON,键前缀与旧版 JDUtil「interaction_state」思路一致:interaction_state:wecom:{userId}) + * 企微多轮会话(Redis JSON)。键由 {@link WeComConvention#sessionRedisKey(String)} 生成, + * 参数 wecomUserId 必须为明文 XML 中的 FromUserName(成员 UserID),与 wxSend 转发字段一致。 */ public interface IWeComChatSessionService { + /** @param wecomUserId 成员 UserID,同 FromUserName */ WeComChatSession get(String wecomUserId); + /** @param wecomUserId 成员 UserID,同 FromUserName */ void put(String wecomUserId, WeComChatSession session); + /** @param wecomUserId 成员 UserID,同 FromUserName */ void delete(String wecomUserId); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComChatSessionServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComChatSessionServiceImpl.java index b842296..a50425f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComChatSessionServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComChatSessionServiceImpl.java @@ -3,6 +3,7 @@ package com.ruoyi.jarvis.service.impl; import com.alibaba.fastjson2.JSON; import com.ruoyi.jarvis.domain.dto.WeComChatSession; import com.ruoyi.jarvis.service.IWeComChatSessionService; +import com.ruoyi.jarvis.wecom.WeComConvention; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -23,9 +24,6 @@ public class WeComChatSessionServiceImpl implements IWeComChatSessionService { private static final Logger log = LoggerFactory.getLogger(WeComChatSessionServiceImpl.class); - /** 与 JDUtil 的 interaction_state 命名风格一致,避免与个人微信会话键冲突加 wecom 命名空间 */ - public static final String REDIS_PREFIX = "interaction_state:wecom:"; - private static final DateTimeFormatter FMT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; @Resource @@ -42,7 +40,7 @@ public class WeComChatSessionServiceImpl implements IWeComChatSessionService { if (!StringUtils.hasText(wecomUserId)) { return null; } - String json = stringRedisTemplate.opsForValue().get(REDIS_PREFIX + wecomUserId.trim()); + String json = stringRedisTemplate.opsForValue().get(WeComConvention.sessionRedisKey(wecomUserId.trim())); if (!StringUtils.hasText(json)) { return null; } @@ -53,7 +51,7 @@ public class WeComChatSessionServiceImpl implements IWeComChatSessionService { } LocalDateTime last = LocalDateTime.parse(s.getLastInteractionTime(), FMT); if (ChronoUnit.MINUTES.between(last, LocalDateTime.now()) > idleTimeoutMinutes) { - stringRedisTemplate.delete(REDIS_PREFIX + wecomUserId.trim()); + stringRedisTemplate.delete(WeComConvention.sessionRedisKey(wecomUserId.trim())); return null; } return s; @@ -70,7 +68,7 @@ public class WeComChatSessionServiceImpl implements IWeComChatSessionService { } session.touch(); stringRedisTemplate.opsForValue().set( - REDIS_PREFIX + wecomUserId.trim(), + WeComConvention.sessionRedisKey(wecomUserId.trim()), JSON.toJSONString(session), sessionTtlMinutes, TimeUnit.MINUTES); @@ -81,7 +79,7 @@ public class WeComChatSessionServiceImpl implements IWeComChatSessionService { if (!StringUtils.hasText(wecomUserId)) { return; } - stringRedisTemplate.delete(REDIS_PREFIX + wecomUserId.trim()); + stringRedisTemplate.delete(WeComConvention.sessionRedisKey(wecomUserId.trim())); } /** @@ -90,7 +88,7 @@ public class WeComChatSessionServiceImpl implements IWeComChatSessionService { @Scheduled(fixedDelayString = "${jarvis.wecom.session-sweep-ms:60000}") public void sweepIdleSessions() { try { - Set+ * 锚定字段(存在且稳定):企业微信成员 UserID,在接收到自建应用的明文 XML 中与节点 + * {@code FromUserName} 对应。该 ID 由管理员在「管理后台—通讯录」中设定, + * 企业内唯一;一般不随成员对外昵称修改而变化。 + *
+ *+ * 下列场景必须使用同一值(trim 后): + *
+ * 禁止使用 MsgId、消息正文、CorpId+AgentId 单独、或其它逐条变化的量作为会话主键(会导致会话无法连续)。 + * AgentId 仅作业务旁路信息;若未来多应用共用同一回调入口,可在会话 JSON 内记录 agentId,但会话键仍以成员 UserID 为主。 + *
+ */ +public final class WeComConvention { + + private WeComConvention() { + } + + /** + * 多轮会话在 Redis 中的键前缀。完整键 = 此前缀 + 成员 UserID(trim)。 + */ + public static final String SESSION_REDIS_KEY_PREFIX = "interaction_state:wecom:"; + + /** + * 企微推送明文 XML 中表示「发送方成员 UserID」的节点名(供解析端对照,值为成员 UserID 字符串)。 + */ + public static final String XML_FROM_USERNAME = "FromUserName"; + + /** + * @param wecomMemberUserId 成员 UserID,须与明文 XML 中 FromUserName 一致 + */ + public static String sessionRedisKey(String wecomMemberUserId) { + if (wecomMemberUserId == null || wecomMemberUserId.trim().isEmpty()) { + throw new IllegalArgumentException("wecomMemberUserId 不能为空(应为企业微信 FromUserName / 成员 UserID)"); + } + return SESSION_REDIS_KEY_PREFIX + wecomMemberUserId.trim(); + } +} diff --git a/ruoyi-system/src/main/resources/mapper/jarvis/SuperAdminMapper.xml b/ruoyi-system/src/main/resources/mapper/jarvis/SuperAdminMapper.xml index 48c3b23..ad04e5b 100644 --- a/ruoyi-system/src/main/resources/mapper/jarvis/SuperAdminMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/jarvis/SuperAdminMapper.xml @@ -43,10 +43,14 @@ where union_id = #{unionId} +