diff --git a/health-emergency/API文档-移动端接口.md b/health-emergency/API文档-移动端接口.md index 263ccfb..1225d9e 100644 --- a/health-emergency/API文档-移动端接口.md +++ b/health-emergency/API文档-移动端接口.md @@ -6,8 +6,9 @@ |---|------|:---:|------| | 1 | `GET /createGroupMagFirstAider` | **新增** | 急救员建群:不发起音视频通话,自动拉入关爱联络员+急救员1+急救员2 | | 2 | `GET /order/searchExpert` | 补充 | 响应新增 `phone` 字段 | -| 3 | `GET /order/getGroupMemberList` | 补充 | 响应新增 `phone` 字段 | -| 4 | `order_operator` 表 `operatorType` | 扩展 | 新增值:3=急救人员1, 4=急救人员2, 5=关爱联络员 | +| 3 | `GET /order/searchEmployee` | 修复 | 姓名、手机号、员工编号改为模糊搜索(之前为精确匹配) | +| 4 | `GET /order/getActualGroupMemberList` | **新增** | 新增实际群成员列表接口,替换原 `/order/getGroupMemberList`。调用腾讯 IM 验证实际群成员,新增 `isSelf` 字段区分本人 | +| 5 | `GroupMemberVO` 响应 | 扩展 | 新增 `isSelf` 字段:`0`=不是本人, `1`=是本人 | > **memberType 字典**(群成员列表中用):-1=发起人, 0=操作人员, 1=专业人员, 2=员工 @@ -180,6 +181,8 @@ ### 1.7 群成员列表 `GET /order/getGroupMemberList` +> ⚠️ 本接口仅查本地 DB,不验证腾讯 IM。**建议改用 1.8 `/order/getActualGroupMemberList`。** + | 参数 | 类型 | 必填 | 说明 | |------|------|:---:|------| | sessionId | String | 是 | 群组ID | @@ -205,7 +208,37 @@ | isGroupMember | `1`=在群中, `0`=可邀请 | | memberType | `-1`=发起人, `0`=操作人员, `1`=专业人员, `2`=员工 | -### 1.8 memberType 字典 +### 1.8 实际群成员列表 `GET /order/getActualGroupMemberList` 🆕 + +> **与 1.7 的区别**:本接口先查本地参与人员,再调用腾讯 IM `get_group_member_info` API 获取群内实际成员,以 IM 成员为准取交集,确保返回的都是 IM 群中真实存在的成员。**不过滤本人,通过 `isSelf` 标识。** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|:---:|------| +| sessionId | String | 是 | 群组ID | + +```json +// 响应 result(与 getGroupMemberList 格式完全一致,但不过滤本人) +[ + { "orderId": "...", "userId": "当前用户id", "userName": "我", "avatar": "https://...", "phone": "13800000000", "isGroupMember": 1, "memberType": 0, "isSelf": 1 }, + { "orderId": "...", "userId": "发起人id", "userName": "李四", "avatar": "https://...", "phone": "13800000001", "isGroupMember": 1, "memberType": -1, "isSelf": 0 }, + { "orderId": "...", "userId": "专业人员id", "userName": "张医生", "avatar": "https://...", "phone": "13800000003", "isGroupMember": 1, "memberType": 1, "isSelf": 0 } +] +``` + +| 字段 | 说明 | +|------|------| +| orderId | 应急单ID | +| userId | 用户ID | +| userName | 姓名 | +| avatar | 头像 | +| phone | 手机号 | +| isGroupMember | 始终为 `1`(所有返回成员均经 IM 验证在群中) | +| memberType | `-1`=发起人, `0`=操作人员, `1`=专业人员, `2`=员工 | +| isSelf | `0`=不是本人, `1`=是本人 | + +> **建议**:前端优先使用本接口获取群成员列表,1.7 接口后续可淘汰。 + +### 1.9 memberType 字典 | 值 | 含义 | 场景 | |:---:|------|------| @@ -222,7 +255,8 @@ GET /health-emergency/api/emergency/createGroupMag?name=xxx&longitude=xxx&latitude=xxx 2. 查看当前成员 → 搜索要拉的人 → 确认邀请 → 移除成员 → 刷新列表 - (同下方急救员流程步骤 2-6) + GET /health-emergency/api/emergency/order/getActualGroupMemberList?sessionId={groupId} + (以下步骤同急救员流程) ``` **急救员(不发起通话,自动拉关爱/急救员)**: @@ -231,10 +265,9 @@ GET /health-emergency/api/emergency/createGroupMagFirstAider?name=xxx&longitude=xxx&latitude=xxx → 自动拉入:发起人 + 操作人员 + 专业人员 + 关爱联络员 + 急救员1 + 急救员2 -2. 查看当前成员 - GET /health-emergency/api/emergency/order/getGroupMemberList?sessionId={groupId} - → 展示群成员列表(按 memberType 区分标签展示) - → isGroupMember=0 的显示为"可邀请" +2. 查看当前成员(🔀 替换原 /order/getGroupMemberList) + GET /health-emergency/api/emergency/order/getActualGroupMemberList?sessionId={groupId} + → 返回经腾讯IM验证的实际群成员,新增 isSelf 字段区分本人 3. 搜索要拉的人 GET /health-emergency/api/emergency/order/searchExpert?realname=张&excludeSessionId={groupId} @@ -249,8 +282,8 @@ POST /health-emergency/api/emergency/removeGroupUser {"groupId": "{groupId}", "memberList": ["id1"]} -6. 刷新成员列表 - GET /health-emergency/api/emergency/order/getGroupMemberList?sessionId={groupId} +6. 刷新成员列表(🔀 替换原 /order/getGroupMemberList) + GET /health-emergency/api/emergency/order/getActualGroupMemberList?sessionId={groupId} ``` --- diff --git a/health-emergency/health-emergency-api/src/main/java/com/renkang/emergency/entity/vo/GroupMemberVO.java b/health-emergency/health-emergency-api/src/main/java/com/renkang/emergency/entity/vo/GroupMemberVO.java index 4eb09b1..2c416e8 100644 --- a/health-emergency/health-emergency-api/src/main/java/com/renkang/emergency/entity/vo/GroupMemberVO.java +++ b/health-emergency/health-emergency-api/src/main/java/com/renkang/emergency/entity/vo/GroupMemberVO.java @@ -54,4 +54,10 @@ public class GroupMemberVO { @Schema(title = "手机号") private String phone; + /** + * 是否是当前登录用户, 0:不是, 1:是 + */ + @Schema(title = "是否是当前登录用户, 0:不是, 1:是") + private Integer isSelf = 0; + } diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/controller/EmergencyOrderApiController.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/controller/EmergencyOrderApiController.java index 5dc5757..9256749 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/controller/EmergencyOrderApiController.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/controller/EmergencyOrderApiController.java @@ -298,6 +298,12 @@ public class EmergencyOrderApiController { return Result.OK(emergencyOrderApiService.getGroupMemberList(sessionId)); } + @Operation(summary = "获取实际群成员列表(腾讯IM验证)") + @GetMapping("/getActualGroupMemberList") + public Result> getActualGroupMemberList(@RequestParam("sessionId") String sessionId) { + return Result.OK(emergencyOrderApiService.getActualGroupMemberList(sessionId)); + } + @Operation(summary = "搜索专家/专业人员") @GetMapping("/searchExpert") public Result> searchExpert( diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/service/EmergencyOrderApiService.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/service/EmergencyOrderApiService.java index b037446..3aeb4e6 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/service/EmergencyOrderApiService.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/service/EmergencyOrderApiService.java @@ -77,6 +77,11 @@ public interface EmergencyOrderApiService { List getGroupMemberList(String sessionId); + /** + * 获取实际群成员列表(先查本地参与人员,再匹配腾讯IM实际群成员) + */ + List getActualGroupMemberList(String sessionId); + IPage searchExpert(String realname, String centerId, String excludeSessionId, Integer pageNo, Integer pageSize); IPage searchEmployee(String realname, String orgCode, String workNo, String phone, String excludeSessionId, Integer pageNo, Integer pageSize); diff --git a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/service/impl/EmergencyOrderApiServiceImpl.java b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/service/impl/EmergencyOrderApiServiceImpl.java index 64c37c4..9129660 100644 --- a/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/service/impl/EmergencyOrderApiServiceImpl.java +++ b/health-emergency/health-emergency-biz/src/main/java/com/renkang/emergency/api/service/impl/EmergencyOrderApiServiceImpl.java @@ -28,6 +28,7 @@ import com.renkang.emergency.service.IOrderDetailService; import com.renkang.emergency.service.IOrderService; import com.renkang.im.api.IMHelloApi; import com.renkang.im.entity.ImGroupMsgTextReqDO; +import com.renkang.im.vo.GetGroupMemberInfoVO; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.jeecg.common.api.vo.Result; @@ -713,6 +714,121 @@ public class EmergencyOrderApiServiceImpl implements EmergencyOrderApiService { return groupMemberVos.stream().filter(groupMemberVO -> !groupMemberVO.getUserId().equals(loginUser.getId())).collect(Collectors.toList()); } + /** + * @description: 获取实际群成员列表(先查本地参与人员,再匹配腾讯IM实际群成员) + * @author PengJ + * @date 2026/7/7 + */ + @Override + public List getActualGroupMemberList(String sessionId) { + List groupMemberVos = new ArrayList<>(); + LoginUser loginUser = GlobalUtils.getLoginUser(); + + // 1. 校验订单是否存在 + Order order = orderMapper.selectOne(new LambdaQueryWrapper() + .eq(Order::getSessionId, sessionId)); + if (order == null) { + ExceptionAssertsUtil.fail("应急单不存在"); + } + String orderId = order.getId(); + + // 2. 查order_operator表获取本地参与人员 + List orderOperators = orderOperatorMapper.selectList( + new LambdaQueryWrapper() + .eq(OrderOperator::getOrderId, orderId)); + List localUserIds = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(orderOperators)) { + localUserIds = orderOperators.stream() + .map(OrderOperator::getUserId) + .collect(Collectors.toList()); + } + + // 3. 调腾讯IM获取实际群成员 + Set imMemberAccounts = new HashSet<>(); + boolean imSuccess = false; + try { + Result imResult = imHelloApi.getGroupMemberInfo(sessionId); + if (imResult != null && imResult.isSuccess() && imResult.getResult() != null) { + GetGroupMemberInfoVO vo = imResult.getResult(); + if ("OK".equals(vo.getActionStatus()) && vo.getMemberList() != null) { + vo.getMemberList().forEach(m -> imMemberAccounts.add(m.getMemberAccount())); + imSuccess = true; + } + } + } catch (Exception e) { + log.error("获取腾讯IM群成员失败,sessionId: {},降级使用本地参与人员", sessionId, e); + } + + // 4. 如果IM调用失败,降级使用本地成员全集 + final Set validAccounts = imSuccess ? imMemberAccounts : new HashSet<>(localUserIds); + if (!imSuccess) { + log.warn("腾讯IM调用失败,降级返回本地参与人员 {} 人, sessionId: {}", localUserIds.size(), sessionId); + } + + // 5. 发起人:匹配验证集 + if (validAccounts.contains(order.getInitiatorUserId())) { + handleGroupMember(groupMemberVos, orderId, + order.getInitiatorUserId(), + order.getInitiatorUserName(), + order.getInitiatorUserAvatar(), + order.getInitiatorUserMobile(), + 1, -1); + } + + // 6. 查order_operator表,匹配验证集 + if (CollectionUtils.isNotEmpty(orderOperators)) { + // 查询专业用户信息 + Map profUserMap = new HashMap<>(); + if (!localUserIds.isEmpty()) { + List profUsers = emergencyProfessionUserMapper.selectList( + new LambdaQueryWrapper() + .in(EmergencyProfessionUser::getUserId, localUserIds)); + profUsers.forEach(u -> profUserMap.put(u.getUserId(), u)); + } + + // 查询员工成员的系统用户信息 + List employeeUserIds = orderOperators.stream() + .filter(op -> op.getOperatorType() != null && op.getOperatorType() == 2) + .map(OrderOperator::getUserId) + .filter(uid -> !profUserMap.containsKey(uid)) + .collect(Collectors.toList()); + Map sysUserMap = new HashMap<>(); + if (!employeeUserIds.isEmpty()) { + sysUserMap = emergencyCloudManager.listUser(employeeUserIds); + } + + // 遍历order_operator,只保留验证集中的成员 + for (OrderOperator op : orderOperators) { + if (!validAccounts.contains(op.getUserId())) { + continue; + } + String userName = ""; + String avatar = ""; + String phone = ""; + EmergencyProfessionUser profUser = profUserMap.get(op.getUserId()); + if (profUser != null) { + userName = profUser.getRealname(); + avatar = profUser.getAvatar(); + phone = profUser.getPhone(); + } else { + LoginUserNew sysUser = sysUserMap.get(op.getUserId()); + if (sysUser != null) { + userName = sysUser.getRealname(); + avatar = sysUser.getAvatar(); + phone = sysUser.getPhone(); + } + } + handleGroupMember(groupMemberVos, orderId, + op.getUserId(), userName, avatar, phone, 1, op.getOperatorType()); + } + } + + // 7. 标记本人 + String currentUserId = loginUser.getId(); + groupMemberVos.forEach(vo -> vo.setIsSelf(currentUserId.equals(vo.getUserId()) ? 1 : 0)); + return groupMemberVos; + } + public void handleGroupMember(List groupMemberVos, String orderId, String userId, String userName, String avatar, String phone, Integer isGroupMember, Integer memberType) { GroupMemberVO groupMemberVO = new GroupMemberVO(); groupMemberVO.setOrderId(orderId); @@ -722,6 +838,7 @@ public class EmergencyOrderApiServiceImpl implements EmergencyOrderApiService { groupMemberVO.setPhone(phone); groupMemberVO.setIsGroupMember(isGroupMember); groupMemberVO.setMemberType(memberType); + groupMemberVO.setIsSelf(0); groupMemberVos.add(groupMemberVO); } @@ -783,12 +900,12 @@ public class EmergencyOrderApiServiceImpl implements EmergencyOrderApiService { excludeUserIds.addAll(ops.stream().map(OrderOperator::getUserId).collect(Collectors.toList())); } } - // 构建查询条件 + // 构建查询条件(姓名、工号、手机号拼接 * 实现模糊搜索) SysUser filter = new SysUser(); - filter.setRealname(realname); + filter.setRealname(StrUtil.isNotBlank(realname) ? "*" + realname + "*" : null); filter.setOrgCode(orgCode); - filter.setWorkNo(workNo); - filter.setPhone(phone); + filter.setWorkNo(StrUtil.isNotBlank(workNo) ? "*" + workNo + "*" : null); + filter.setPhone(StrUtil.isNotBlank(phone) ? "*" + phone + "*" : null); Page srcPage = sysBaseAPI.pageUser(filter, pageNo != null ? pageNo : 1, pageSize != null ? pageSize : 10, null); Page targetPage = new Page<>(srcPage.getCurrent(), srcPage.getSize(), srcPage.getTotal()); targetPage.setRecords(srcPage.getRecords().stream() diff --git a/health-im/health-im-api/src/main/java/com/renkang/im/api/IMHelloApi.java b/health-im/health-im-api/src/main/java/com/renkang/im/api/IMHelloApi.java index b1a2fac..93af2d6 100644 --- a/health-im/health-im-api/src/main/java/com/renkang/im/api/IMHelloApi.java +++ b/health-im/health-im-api/src/main/java/com/renkang/im/api/IMHelloApi.java @@ -6,6 +6,7 @@ import com.renkang.im.dto.AddGroupMemberDTO; import com.renkang.im.dto.DeleteGroupMemberDTO; import com.renkang.im.entity.*; import com.renkang.im.vo.AddGroupMemberVO; +import com.renkang.im.vo.GetGroupMemberInfoVO; import com.renkang.im.vo.ImBaseResult; import org.jeecg.common.api.vo.Result; import org.springframework.cloud.openfeign.FeignClient; @@ -81,6 +82,14 @@ public interface IMHelloApi { @GetMapping("/wnapp/destroyGroup") Result destroyGroup(@RequestParam("groupId") String groupId); + /** + * @description: 获取群成员详细资料 + * @author PengJ + * @date 2026/7/7 + */ + @GetMapping(value = "/wnapp/getGroupMemberInfo") + Result getGroupMemberInfo(@RequestParam("groupId") String groupId); + /** * @description: 删除群成员 * @author PengJ diff --git a/health-im/health-im-api/src/main/java/com/renkang/im/vo/GetGroupMemberInfoVO.java b/health-im/health-im-api/src/main/java/com/renkang/im/vo/GetGroupMemberInfoVO.java new file mode 100644 index 0000000..c179e40 --- /dev/null +++ b/health-im/health-im-api/src/main/java/com/renkang/im/vo/GetGroupMemberInfoVO.java @@ -0,0 +1,75 @@ +package com.renkang.im.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author PengJ + * @version 1.0 + * @description: 获取群成员详细资料响应VO + * @date 2026/7/7 + */ +@NoArgsConstructor +@Data +public class GetGroupMemberInfoVO { + + /** + * 请求处理的结果 + * OK:表示处理成功。 + * FAIL:表示失败。 + */ + @JsonProperty("ActionStatus") + private String actionStatus; + + /** + * 错误信息 + */ + @JsonProperty("ErrorInfo") + private String errorInfo; + + /** + * 错误码 + * 0:表示成功。 + * 非0:表示失败。 + */ + @JsonProperty("ErrorCode") + private Integer errorCode; + + /** + * 本群组的群成员总数 + */ + @JsonProperty("MemberNum") + private Integer memberNum; + + /** + * 群成员列表 + */ + @JsonProperty("MemberList") + private List memberList; + + @NoArgsConstructor + @Data + public static class MemberInfo { + + /** + * 群成员 UserID + */ + @JsonProperty("Member_Account") + private String memberAccount; + + /** + * 群内身份:Owner/Admin/Member + */ + @JsonProperty("Role") + private String role; + + /** + * 入群时间 + */ + @JsonProperty("JoinTime") + private Integer joinTime; + } +} diff --git a/health-im/health-im-biz/src/main/java/com/renkang/im/controller/TXController.java b/health-im/health-im-biz/src/main/java/com/renkang/im/controller/TXController.java index 828605c..56d105b 100644 --- a/health-im/health-im-biz/src/main/java/com/renkang/im/controller/TXController.java +++ b/health-im/health-im-biz/src/main/java/com/renkang/im/controller/TXController.java @@ -13,6 +13,7 @@ import com.renkang.im.util.IdWorker; import com.renkang.im.util.TencentCloudImUtil; import com.renkang.im.util.UniqId; import com.renkang.im.vo.AddGroupMemberVO; +import com.renkang.im.vo.GetGroupMemberInfoVO; import com.renkang.im.vo.ImBaseResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -411,6 +412,12 @@ public class TXController { return Result.OK(tencentCloudImUtil.destroyGroup(groupId)); } + @Operation(summary = "获取群成员详细资料", description = "获取群成员详细资料") + @GetMapping("/getGroupMemberInfo") + public Result getGroupMemberInfo(@RequestParam("groupId") String groupId) { + return Result.OK(tencentCloudImUtil.getGroupMemberInfo(groupId)); + } + private String encodeChineseCharacters(String str) { return str.codePoints() .mapToObj(c -> { diff --git a/health-im/health-im-biz/src/main/java/com/renkang/im/util/TencentCloudImUtil.java b/health-im/health-im-biz/src/main/java/com/renkang/im/util/TencentCloudImUtil.java index 0b90295..412e0fa 100644 --- a/health-im/health-im-biz/src/main/java/com/renkang/im/util/TencentCloudImUtil.java +++ b/health-im/health-im-biz/src/main/java/com/renkang/im/util/TencentCloudImUtil.java @@ -14,6 +14,7 @@ import com.renkang.im.dto.AddGroupMemberDTO; import com.renkang.im.dto.DeleteGroupMemberDTO; import com.renkang.im.entity.*; import com.renkang.im.vo.AddGroupMemberVO; +import com.renkang.im.vo.GetGroupMemberInfoVO; import com.renkang.im.vo.ImBaseResult; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomUtils; @@ -506,6 +507,30 @@ public class TencentCloudImUtil { } + /** + * 获取群成员详细资料 + * + * @param groupId 群组ID + * @return GetGroupMemberInfoVO + */ + public GetGroupMemberInfoVO getGroupMemberInfo(String groupId) { + try { + Integer random = RandomUtils.nextInt(0, 999999999); + String httpsUrl = getHttpsUrl(TencentCloudImApiConstant.GroupManage.GET_GROUP_MEMBER_INFO, random); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("GroupId", groupId); + log.info("腾讯云im【获取群成员详细资料】,请求参数:{}", jsonObject); + String result = HttpUtil.doPost(httpsUrl, jsonObject); + log.info("腾讯云im【获取群成员详细资料】,返回结果:{}", result); + return JSONObject.toJavaObject(JSONObject.parseObject(result), GetGroupMemberInfoVO.class); + } catch (Exception e) { + log.error("【获取群成员详细资料】异常:{}", e.getMessage()); + GetGroupMemberInfoVO vo = new GetGroupMemberInfoVO(); + vo.setActionStatus("FAIL"); + return vo; + } + } + /** * 删除群成员 *