From 83ed8e4150e75f426bbefe2c35f032efdd29fcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E4=BD=B3=E8=B1=AA?= <18505142974@163.com> Date: Thu, 17 Jul 2025 14:30:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9B=9E=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E5=90=88=E5=88=86=E5=8F=91=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/SysMessageServiceImpl.java | 27 +- .../member/controller/FansController.java | 78 ++++++ .../controller/ImCallbackController.java | 261 ++++++++++++++++++ 3 files changed, 342 insertions(+), 24 deletions(-) create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ImCallbackController.java diff --git a/ruoyi-modules/ruoyi-im/src/main/java/org/dromara/system/service/impl/SysMessageServiceImpl.java b/ruoyi-modules/ruoyi-im/src/main/java/org/dromara/system/service/impl/SysMessageServiceImpl.java index 0cc97661d..1b4cf9b47 100644 --- a/ruoyi-modules/ruoyi-im/src/main/java/org/dromara/system/service/impl/SysMessageServiceImpl.java +++ b/ruoyi-modules/ruoyi-im/src/main/java/org/dromara/system/service/impl/SysMessageServiceImpl.java @@ -87,33 +87,12 @@ public class SysMessageServiceImpl extends ServiceImpl queryWrapper = new LambdaQueryWrapper<>(); -// queryWrapper.eq(SysMessageUser::getMessageId, entity.getId()) -// .eq(SysMessageUser::getUserId, userId); -// int count = Math.toIntExact(messageUserMapper.selectCount(queryWrapper)); -// int rows = 0; -// if (count == 0) { -// SysMessageUser messageUser = new SysMessageUser(); -// messageUser.setMessageId(entity.getId()); -// messageUser.setUserId(userId); -// messageUser.setIsRead(false); -// rows = messageUserMapper.insert(messageUser); -// } else { -// log.info("消息与用户关联已存在,跳过创建: messageId={}, userId={}", entity.getId(), userId); -// rows = 1; -// } -// // 定时消息不发布事件 -// return rows; -// } else { entity.setStatus("1"); // 已发送 messageMapper.insert(entity); // 检查消息与用户关联是否已存在 diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/FansController.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/FansController.java index 82ba574be..04cc828b7 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/FansController.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/FansController.java @@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.commons.codec.digest.DigestUtils; @Slf4j @RequestMapping("/ums/fans/") @@ -103,6 +104,42 @@ public class FansController { // 1. IM回调格式 if (callbackData != null && callbackData.containsKey("CallbackCommand")) { log.info("收到IM关注回调: {}", callbackData); + + // 1.1 兼容腾讯IM的 PairList 回调格式 + Object pairListObj = callbackData.get("PairList"); + if (pairListObj instanceof List) { + List pairList = (List) pairListObj; + for (Object pairObj : pairList) { + if (pairObj instanceof Map) { + Map pair = (Map) pairObj; + Object fromAccountObj = pair.get("From_Account"); + Object toAccountObj = pair.get("To_Account"); + if (fromAccountObj != null && toAccountObj != null) { + Long fromId = null; + Long toId = null; + try { + fromId = Long.valueOf(fromAccountObj.toString()); + toId = Long.valueOf(toAccountObj.toString()); + } catch (Exception e) { + log.warn("IM回调Pair参数转换失败: {}", pair); + continue; + } + handleImFollow(fromId, java.util.Arrays.asList(toId)); + log.info("IM回调处理成功: fromId={}, vloggerId={}", fromId, toId); + } else { + log.warn("IM回调Pair参数不完整: {}", pair); + } + } + } + // 返回IM平台要求的应答 + Map resp = new HashMap<>(); + resp.put("ActionStatus", "OK"); + resp.put("ErrorCode", 0); + resp.put("ErrorInfo", ""); + return resp; + } + + // 1.2 兼容原有 FollowList 格式 String fromAccount = (String) callbackData.get("From_Account"); List> followList = (List>) callbackData.get("FollowList"); if (fromAccount != null && followList != null) { @@ -156,6 +193,47 @@ public class FansController { // 1. 判断是否为IM回调格式 if (callbackData != null && callbackData.containsKey("CallbackCommand")) { log.info("收到IM取消关注回调: {}", callbackData); + + // 1.1 兼容腾讯IM的 PairList 回调格式 + Object pairListObj = callbackData.get("PairList"); + log.info("PairList 类型: {}, 内容: {}", pairListObj != null ? pairListObj.getClass() : "null", pairListObj); + if (pairListObj instanceof List) { + List pairList = (List) pairListObj; + for (Object pairObj : pairList) { + if (pairObj instanceof Map) { + Map pair = (Map) pairObj; + Object fromAccountObj = pair.get("From_Account"); + Object toAccountObj = pair.get("To_Account"); + if (fromAccountObj != null && toAccountObj != null) { + Long fromId = null; + Long toId = null; + try { + fromId = Long.valueOf(fromAccountObj.toString()); + toId = Long.valueOf(toAccountObj.toString()); + } catch (Exception e) { + log.warn("IM回调Pair参数转换失败: {}", pair); + continue; + } + service.doCancel(fromId, toId); + log.info("IM取消关注回调处理成功: fromId={}, toId={}", fromId, toId); + } else { + log.warn("IM取消关注回调Pair参数不完整: {}", pair); + } + } else { + log.warn("PairList元素不是Map: {}", pairObj); + } + } + // 返回IM平台要求的应答 + Map resp = new HashMap<>(); + resp.put("ActionStatus", "OK"); + resp.put("ErrorCode", 0); + resp.put("ErrorInfo", ""); + return resp; + } else { + log.warn("PairList 不是 List 类型: {}", pairListObj); + } + + // 1.2 兼容原有 To_Account 逻辑 String fromAccount = (String) callbackData.get("From_Account"); List toAccounts = null; // 兼容腾讯IM的 To_Account 可能是 List 或 List> diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ImCallbackController.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ImCallbackController.java new file mode 100644 index 000000000..b8042897a --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ImCallbackController.java @@ -0,0 +1,261 @@ +package com.wzj.soopin.member.controller; + +import com.wzj.soopin.member.domain.po.Member; +import com.wzj.soopin.member.service.IMemberService; +import com.wzj.soopin.member.service.IFansService; +import org.apache.commons.codec.digest.DigestUtils; +import org.dromara.common.core.constant.CacheConstants; +import org.dromara.common.redis.utils.RedisUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.wzj.soopin.member.service.IMemberBlockService; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.utils.StringUtils; + +@RestController +@RequestMapping("/callback/api") +public class ImCallbackController { + + private static final Logger log = LoggerFactory.getLogger(ImCallbackController.class); +// private static final String CALLBACK_TOKEN = "your_auth_token"; // + + @Autowired + private IMemberService memberService; + @Autowired + private IFansService fansService; + @Autowired + private FansController fansController; + @Autowired + private IMemberBlockService memberBlockService; + + @PostMapping + public R handleCallback( + @RequestParam(value = "Sign", required = false) String sign, + @RequestParam(value = "RequestTime", required = false) String requestTime, + @RequestParam(value = "CallbackCommand", required = false) String callbackCommand, + @RequestBody Map requestBody) { + +// // 1. 签名验证 +// if (!verifySignature(CALLBACK_TOKEN, sign, requestTime)) { +// return Map.of("ActionStatus", "FAIL", "ErrorCode", 1001); +// } + + // 2. 快速响应 + R response = R.ok(); + + // 3. 分发业务逻辑 + if (StringUtils.isBlank(callbackCommand)) { + return R.fail(1002, "缺少CallbackCommand"); + } + switch (callbackCommand) { + case "Sns.CallbackFriendAdd": + // 关注,直接调用 FansController 的 follow + fansController.follow(requestBody, null, null); + break; + case "Sns.CallbackFriendDelete": + // 取关,直接调用 FansController 的 cancel + fansController.cancel(requestBody, null, null); + break; + case "Profile.CallbackPortraitSet": + // 资料修改 + handleProfileCallbackPortraitSet(requestBody); + break; + case "Sns.CallbackBlackListAdd": + handleImAddBlock(requestBody); + break; + case "Sns.CallbackBlackListDelete": + handleImCancelBlock(requestBody); + break; + default: + log.info("收到未知事件类型: {}, 参数: {}", callbackCommand, requestBody); + break; + } + return response; + } + + // 处理IM关注事件 + private void handleImFollow(MaprequestBody) { + Object pairListObj = requestBody.get("PairList"); + if (pairListObj instanceof List) { + List pairList = (List) pairListObj; + for (Object pairObj : pairList) { + if (pairObj instanceof Map) { + Map pair = (Map) pairObj; + Object fromAccountObj = pair.get("From_Account"); + Object toAccountObj = pair.get("To_Account"); + if (fromAccountObj != null && toAccountObj != null) { + Long fromId = Long.valueOf(fromAccountObj.toString()); + Long toId = Long.valueOf(toAccountObj.toString()); + fansService.doFollow(fromId, toId); + } + } + } + } + } + + // 处理IM取关事件 + private void handleImCancel(Map requestBody) { + Object pairListObj = requestBody.get("PairList"); + if (pairListObj instanceof List) { + List pairList = (List) pairListObj; + for (Object pairObj : pairList) { + if (pairObj instanceof Map) { + Map pair = (Map) pairObj; + Object fromAccountObj = pair.get("From_Account"); + Object toAccountObj = pair.get("To_Account"); + if (fromAccountObj != null && toAccountObj != null) { + Long fromId = Long.valueOf(fromAccountObj.toString()); + Long toId = Long.valueOf(toAccountObj.toString()); + fansService.doCancel(fromId, toId); + } + } + } + } + } + + // 处理IM黑名单添加事件 + private void handleImAddBlock(Map requestBody) { + Object pairListObj = requestBody.get("PairList"); + if (pairListObj instanceof List) { + List pairList = (List) pairListObj; + for (Object pairObj : pairList) { + if (pairObj instanceof Map) { + Map pair = (Map) pairObj; + Object fromAccountObj = pair.get("From_Account"); + Object toAccountObj = pair.get("To_Account"); + if (fromAccountObj != null && toAccountObj != null) { + try { + Long myId = Long.valueOf(fromAccountObj.toString()); + Long vloggerId = Long.valueOf(toAccountObj.toString()); + // 1. 判断两个id不能为空 + if (myId == null || vloggerId == null) { + log.warn("黑名单回调参数id为空: myId={}, vloggerId={}", myId, vloggerId); + continue; + } + // 2. 是否已经存在关注关系 + boolean flow = fansService.queryDoIFollowVloger(myId, vloggerId); + if (flow) { + // 删除关注 + fansService.doCancel(myId, vloggerId); + // 博主的粉丝-1,我的关注-1 + RedisUtils.decrAtomicValue(CacheConstants.MEMBER_FANS + ":" + vloggerId); + RedisUtils.decrAtomicValue(CacheConstants.MEMBER_FOLLOW + ":" + myId); + } + // 3. 检查是否已在黑名单中 + boolean hasblock = memberBlockService.hasBlocked(myId, vloggerId); + if (hasblock) { + log.info("用户{}已拉黑{}", myId, vloggerId); + continue; + } + // 4. 拉黑 + memberBlockService.addBlock(myId, vloggerId); + log.info("用户{}成功拉黑{}", myId, vloggerId); + } catch (Exception e) { + log.warn("黑名单回调参数转换失败: {}", pair); + } + } + } + } + } + } + + // 处理IM取消拉黑事件 + private void handleImCancelBlock(Map requestBody) { + Object pairListObj = requestBody.get("PairList"); + if (pairListObj instanceof List) { + List pairList = (List) pairListObj; + for (Object pairObj : pairList) { + if (pairObj instanceof Map) { + Map pair = (Map) pairObj; + Object fromAccountObj = pair.get("From_Account"); + Object toAccountObj = pair.get("To_Account"); + if (fromAccountObj != null && toAccountObj != null) { + try { + Long myId = Long.valueOf(fromAccountObj.toString()); + Long vloggerId = Long.valueOf(toAccountObj.toString()); + if (myId == null || vloggerId == null) { + log.warn("取消拉黑回调参数id为空: myId={}, vloggerId={}", myId, vloggerId); + continue; + } + memberBlockService.removeBlock(myId, vloggerId); + log.info("用户{}已取消拉黑{}", myId, vloggerId); + } catch (Exception e) { + log.warn("取消拉黑回调参数转换失败: {}", pair); + } + } + } + } + } + } + + // 签名校验方法 + private boolean verifySignature(String token, String sign, String requestTime) { + if (sign == null || requestTime == null) return false; + long now = System.currentTimeMillis() / 1000; + if (Math.abs(now - Long.parseLong(requestTime)) > 60) return false; + String localSign = DigestUtils.sha256Hex(token + requestTime); + return localSign.equals(sign); + } + + // 资料修改回调处理逻辑 + private void handleProfileCallbackPortraitSet(Map callbackData) { + log.info("收到IM用户资料更新回调: {}", callbackData); + String fromAccount = (String) callbackData.get("From_Account"); + List> profileItems = (List>) callbackData.get("ProfileItem"); + if (fromAccount != null && profileItems != null) { + Long userId = null; + try { + userId = Long.valueOf(fromAccount.replace("UserID_", "")); + } catch (Exception e) { + log.warn("用户ID转换失败: {}", fromAccount); + } + if (userId != null) { + Member memberToUpdate = memberService.getById(userId); + if (memberToUpdate != null) { + for (Map item : profileItems) { + String tag = (String) item.get("Tag"); + Object value = item.get("Value"); + updateMemberProfile(memberToUpdate, tag, value); + } + memberService.updateById(memberToUpdate); + log.info("IM用户资料更新回调处理成功: userId={}, profileItems={}", userId, profileItems); + } else { + log.warn("IM用户资料更新回调中用户不存在: userId={}", userId); + } + } + } else { + log.warn("IM用户资料更新回调参数不完整: {}", callbackData); + } + } + + // 只更新 nickname/gender 字段 + private void updateMemberProfile(Member member, String tag, Object value) { + if (tag == null || value == null) { + return; + } + switch (tag) { + case "Tag_Profile_IM_Nick": + member.setNickname(value.toString()); + break; + case "Tag_Profile_IM_Gender": + if ("Gender_Type_Male".equals(value)) { + member.setGender(1); + } else if ("Gender_Type_Female".equals(value)) { + member.setGender(2); + } else { + member.setGender(0); + } + break; + default: + log.debug("未处理的用户资料字段: tag={}, value={}", tag, value); + break; + } + } +}