Commit 37f0a8a01d727f38ca7576a33ee0a3bf5b13acab
1 parent
d0fbab8f
✨ feat(im): 增加 friend、group、message、sensitiveword 的管理接口
Showing
37 changed files
with
1292 additions
and
45 deletions
yudao-module-im/pom.xml
| ... | ... | @@ -62,6 +62,14 @@ |
| 62 | 62 | <artifactId>yudao-spring-boot-starter-excel</artifactId> |
| 63 | 63 | </dependency> |
| 64 | 64 | |
| 65 | + <!-- 敏感词检测:houbb sensitive-word(trie 树高效匹配,支持忽略大小写 / 全半角 / 数字风格) --> | |
| 66 | + <!-- TODO @AI:放到基础 pom 里管理。 --> | |
| 67 | + <dependency> | |
| 68 | + <groupId>com.github.houbb</groupId> | |
| 69 | + <artifactId>sensitive-word</artifactId> | |
| 70 | + <version>0.29.5</version> | |
| 71 | + </dependency> | |
| 72 | + | |
| 65 | 73 | <!-- Test 测试相关 --> |
| 66 | 74 | <dependency> |
| 67 | 75 | <groupId>cn.iocoder.boot</groupId> | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.friend; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; | |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO; | |
| 7 | +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; | |
| 8 | +import io.swagger.v3.oas.annotations.Operation; | |
| 9 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 10 | +import jakarta.annotation.Resource; | |
| 11 | +import jakarta.validation.Valid; | |
| 12 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 13 | +import org.springframework.validation.annotation.Validated; | |
| 14 | +import org.springframework.web.bind.annotation.GetMapping; | |
| 15 | +import org.springframework.web.bind.annotation.RequestMapping; | |
| 16 | +import org.springframework.web.bind.annotation.RestController; | |
| 17 | + | |
| 18 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 19 | + | |
| 20 | +@Tag(name = "管理后台 - IM 好友管理") | |
| 21 | +@RestController | |
| 22 | +@RequestMapping("/im/manager/friend") | |
| 23 | +@Validated | |
| 24 | +public class ImFriendManagerController { | |
| 25 | + | |
| 26 | + @Resource | |
| 27 | + private ImFriendService friendService; | |
| 28 | + | |
| 29 | + @GetMapping("/page") | |
| 30 | + @Operation(summary = "获得好友关系分页") | |
| 31 | + @PreAuthorize("@ss.hasPermission('im:manager:friend:query')") | |
| 32 | + public CommonResult<PageResult<ImFriendManagerRespVO>> getFriendPage( | |
| 33 | + @Valid ImFriendManagerPageReqVO pageReqVO) { | |
| 34 | + return success(friendService.getFriendPage(pageReqVO)); | |
| 35 | + } | |
| 36 | + | |
| 37 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.EqualsAndHashCode; | |
| 7 | +import org.springframework.format.annotation.DateTimeFormat; | |
| 8 | + | |
| 9 | +import java.time.LocalDateTime; | |
| 10 | + | |
| 11 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | |
| 12 | + | |
| 13 | +@Schema(description = "管理后台 - IM 好友关系分页 Request VO") | |
| 14 | +@Data | |
| 15 | +@EqualsAndHashCode(callSuper = true) | |
| 16 | +public class ImFriendManagerPageReqVO extends PageParam { | |
| 17 | + | |
| 18 | + @Schema(description = "用户编号", example = "1024") | |
| 19 | + private Long userId; | |
| 20 | + | |
| 21 | + @Schema(description = "好友用户编号", example = "2048") | |
| 22 | + private Long friendUserId; | |
| 23 | + | |
| 24 | + @Schema(description = "好友状态", example = "0") | |
| 25 | + private Integer status; // 参见 CommonStatusEnum 枚举类 | |
| 26 | + | |
| 27 | + @Schema(description = "是否免打扰", example = "false") | |
| 28 | + private Boolean muted; | |
| 29 | + | |
| 30 | + @Schema(description = "添加好友时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") | |
| 31 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 32 | + private LocalDateTime[] addTime; | |
| 33 | + | |
| 34 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.time.LocalDateTime; | |
| 7 | + | |
| 8 | +@Schema(description = "管理后台 - IM 好友关系 Response VO") | |
| 9 | +@Data | |
| 10 | +public class ImFriendManagerRespVO { | |
| 11 | + | |
| 12 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 13 | + private Long id; | |
| 14 | + | |
| 15 | + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 16 | + private Long userId; | |
| 17 | + | |
| 18 | + @Schema(description = "用户昵称", example = "张三") | |
| 19 | + private String userNickname; | |
| 20 | + | |
| 21 | + @Schema(description = "好友用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") | |
| 22 | + private Long friendUserId; | |
| 23 | + | |
| 24 | + @Schema(description = "好友昵称", example = "李四") | |
| 25 | + private String friendNickname; | |
| 26 | + | |
| 27 | + @Schema(description = "好友展示备注") | |
| 28 | + private String displayName; | |
| 29 | + | |
| 30 | + @Schema(description = "是否免打扰", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") | |
| 31 | + private Boolean muted; | |
| 32 | + | |
| 33 | + @Schema(description = "好友状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 34 | + private Integer status; // 参见 CommonStatusEnum 枚举类 | |
| 35 | + | |
| 36 | + @Schema(description = "添加好友时间") | |
| 37 | + private LocalDateTime addTime; | |
| 38 | + | |
| 39 | + @Schema(description = "删除好友时间") | |
| 40 | + private LocalDateTime deleteTime; | |
| 41 | + | |
| 42 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 43 | + private LocalDateTime createTime; | |
| 44 | + | |
| 45 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.group; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; | |
| 7 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; | |
| 8 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO; | |
| 9 | +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; | |
| 10 | +import cn.iocoder.yudao.module.im.service.group.ImGroupService; | |
| 11 | +import io.swagger.v3.oas.annotations.Operation; | |
| 12 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 13 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 14 | +import jakarta.annotation.Resource; | |
| 15 | +import jakarta.validation.Valid; | |
| 16 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 17 | +import org.springframework.validation.annotation.Validated; | |
| 18 | +import org.springframework.web.bind.annotation.*; | |
| 19 | + | |
| 20 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 21 | + | |
| 22 | +@Tag(name = "管理后台 - IM 群聊管理") | |
| 23 | +@RestController | |
| 24 | +@RequestMapping("/im/manager/group") | |
| 25 | +@Validated | |
| 26 | +public class ImGroupManagerController { | |
| 27 | + | |
| 28 | + @Resource | |
| 29 | + private ImGroupService groupService; | |
| 30 | + | |
| 31 | + @GetMapping("/page") | |
| 32 | + @Operation(summary = "获得群分页") | |
| 33 | + @PreAuthorize("@ss.hasPermission('im:manager:group:query')") | |
| 34 | + public CommonResult<PageResult<ImGroupManagerRespVO>> getGroupPage(@Valid ImGroupManagerPageReqVO pageReqVO) { | |
| 35 | + return success(groupService.getGroupPage(pageReqVO)); | |
| 36 | + } | |
| 37 | + | |
| 38 | + @GetMapping("/get") | |
| 39 | + @Operation(summary = "获得群详情") | |
| 40 | + @Parameter(name = "id", description = "群编号", required = true, example = "1024") | |
| 41 | + @PreAuthorize("@ss.hasPermission('im:manager:group:query')") | |
| 42 | + public CommonResult<ImGroupManagerRespVO> getGroup(@RequestParam("id") Long id) { | |
| 43 | + ImGroupDO group = groupService.getGroup(id); | |
| 44 | + return success(BeanUtils.toBean(group, ImGroupManagerRespVO.class)); | |
| 45 | + } | |
| 46 | + | |
| 47 | + @PutMapping("/ban") | |
| 48 | + @Operation(summary = "封禁群") | |
| 49 | + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')") | |
| 50 | + public CommonResult<Boolean> banGroup(@Valid @RequestBody ImGroupManagerBanReqVO reqVO) { | |
| 51 | + groupService.banGroup(reqVO); | |
| 52 | + return success(true); | |
| 53 | + } | |
| 54 | + | |
| 55 | + @PutMapping("/unban") | |
| 56 | + @Operation(summary = "解封群") | |
| 57 | + @Parameter(name = "id", description = "群编号", required = true, example = "1024") | |
| 58 | + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')") | |
| 59 | + public CommonResult<Boolean> unbanGroup(@RequestParam("id") Long id) { | |
| 60 | + groupService.unbanGroup(id); | |
| 61 | + return success(true); | |
| 62 | + } | |
| 63 | + | |
| 64 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.group; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO; | |
| 5 | +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; | |
| 6 | +import io.swagger.v3.oas.annotations.Operation; | |
| 7 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 8 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 9 | +import jakarta.annotation.Resource; | |
| 10 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 11 | +import org.springframework.validation.annotation.Validated; | |
| 12 | +import org.springframework.web.bind.annotation.GetMapping; | |
| 13 | +import org.springframework.web.bind.annotation.RequestMapping; | |
| 14 | +import org.springframework.web.bind.annotation.RequestParam; | |
| 15 | +import org.springframework.web.bind.annotation.RestController; | |
| 16 | + | |
| 17 | +import java.util.List; | |
| 18 | + | |
| 19 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 20 | + | |
| 21 | +@Tag(name = "管理后台 - IM 群成员管理") | |
| 22 | +@RestController | |
| 23 | +@RequestMapping("/im/manager/group/member") | |
| 24 | +@Validated | |
| 25 | +public class ImGroupMemberManagerController { | |
| 26 | + | |
| 27 | + @Resource | |
| 28 | + private ImGroupMemberService groupMemberService; | |
| 29 | + | |
| 30 | + @GetMapping("/list") | |
| 31 | + @Operation(summary = "获得群成员列表") | |
| 32 | + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") | |
| 33 | + @PreAuthorize("@ss.hasPermission('im:manager:group:query')") | |
| 34 | + public CommonResult<List<ImGroupMemberManagerRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) { | |
| 35 | + return success(groupMemberService.getGroupMemberList(groupId)); | |
| 36 | + } | |
| 37 | + | |
| 38 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.message; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO; | |
| 8 | +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; | |
| 9 | +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; | |
| 10 | +import io.swagger.v3.oas.annotations.Operation; | |
| 11 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 12 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 13 | +import jakarta.annotation.Resource; | |
| 14 | +import jakarta.validation.Valid; | |
| 15 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 16 | +import org.springframework.validation.annotation.Validated; | |
| 17 | +import org.springframework.web.bind.annotation.*; | |
| 18 | + | |
| 19 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 20 | + | |
| 21 | +@Tag(name = "管理后台 - IM 群聊消息") | |
| 22 | +@RestController | |
| 23 | +@RequestMapping("/im/manager/message/group") | |
| 24 | +@Validated | |
| 25 | +public class ImGroupMessageManagerController { | |
| 26 | + | |
| 27 | + @Resource | |
| 28 | + private ImGroupMessageService groupMessageService; | |
| 29 | + | |
| 30 | + @GetMapping("/page") | |
| 31 | + @Operation(summary = "获得群聊消息分页") | |
| 32 | + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") | |
| 33 | + public CommonResult<PageResult<ImGroupMessageManagerRespVO>> getGroupMessagePage( | |
| 34 | + @Valid ImGroupMessageManagerPageReqVO pageReqVO) { | |
| 35 | + return success(groupMessageService.getGroupMessagePage(pageReqVO)); | |
| 36 | + } | |
| 37 | + | |
| 38 | + @GetMapping("/get") | |
| 39 | + @Operation(summary = "获得群聊消息详情") | |
| 40 | + @Parameter(name = "id", description = "消息编号", required = true, example = "1024") | |
| 41 | + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") | |
| 42 | + public CommonResult<ImGroupMessageManagerRespVO> getGroupMessage(@RequestParam("id") Long id) { | |
| 43 | + ImGroupMessageDO message = groupMessageService.getGroupMessage(id); | |
| 44 | + return success(BeanUtils.toBean(message, ImGroupMessageManagerRespVO.class)); | |
| 45 | + } | |
| 46 | + | |
| 47 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.message; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO; | |
| 8 | +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; | |
| 9 | +import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; | |
| 10 | +import io.swagger.v3.oas.annotations.Operation; | |
| 11 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 12 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 13 | +import jakarta.annotation.Resource; | |
| 14 | +import jakarta.validation.Valid; | |
| 15 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 16 | +import org.springframework.validation.annotation.Validated; | |
| 17 | +import org.springframework.web.bind.annotation.*; | |
| 18 | + | |
| 19 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 20 | + | |
| 21 | +@Tag(name = "管理后台 - IM 私聊消息") | |
| 22 | +@RestController | |
| 23 | +@RequestMapping("/im/manager/message/private") | |
| 24 | +@Validated | |
| 25 | +public class ImPrivateMessageManagerController { | |
| 26 | + | |
| 27 | + @Resource | |
| 28 | + private ImPrivateMessageService privateMessageService; | |
| 29 | + | |
| 30 | + @GetMapping("/page") | |
| 31 | + @Operation(summary = "获得私聊消息分页") | |
| 32 | + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") | |
| 33 | + public CommonResult<PageResult<ImPrivateMessageManagerRespVO>> getPrivateMessagePage( | |
| 34 | + @Valid ImPrivateMessageManagerPageReqVO pageReqVO) { | |
| 35 | + return success(privateMessageService.getPrivateMessagePage(pageReqVO)); | |
| 36 | + } | |
| 37 | + | |
| 38 | + @GetMapping("/get") | |
| 39 | + @Operation(summary = "获得私聊消息详情") | |
| 40 | + @Parameter(name = "id", description = "消息编号", required = true, example = "1024") | |
| 41 | + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") | |
| 42 | + public CommonResult<ImPrivateMessageManagerRespVO> getPrivateMessage(@RequestParam("id") Long id) { | |
| 43 | + ImPrivateMessageDO message = privateMessageService.getPrivateMessage(id); | |
| 44 | + return success(BeanUtils.toBean(message, ImPrivateMessageManagerRespVO.class)); | |
| 45 | + } | |
| 46 | + | |
| 47 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.EqualsAndHashCode; | |
| 7 | +import org.springframework.format.annotation.DateTimeFormat; | |
| 8 | + | |
| 9 | +import java.time.LocalDateTime; | |
| 10 | + | |
| 11 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | |
| 12 | + | |
| 13 | +@Schema(description = "管理后台 - IM 群聊消息分页 Request VO") | |
| 14 | +@Data | |
| 15 | +@EqualsAndHashCode(callSuper = true) | |
| 16 | +public class ImGroupMessageManagerPageReqVO extends PageParam { | |
| 17 | + | |
| 18 | + @Schema(description = "群编号", example = "1024") | |
| 19 | + private Long groupId; | |
| 20 | + | |
| 21 | + @Schema(description = "发送人编号", example = "1024") | |
| 22 | + private Long senderId; | |
| 23 | + | |
| 24 | + // TODO @AI:参见 ImMessageTypeEnum 枚举类,都放到 // 注释后面。注意加到我的 memory 里。其它类似也处理下。im 的。 | |
| 25 | + @Schema(description = "消息类型,参见 ImMessageTypeEnum 枚举类", example = "1") | |
| 26 | + private Integer type; | |
| 27 | + | |
| 28 | + @Schema(description = "消息状态,参见 ImMessageStatusEnum 枚举类", example = "0") | |
| 29 | + private Integer status; | |
| 30 | + | |
| 31 | + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") | |
| 32 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 33 | + private LocalDateTime[] sendTime; | |
| 34 | + | |
| 35 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.time.LocalDateTime; | |
| 7 | +import java.util.List; | |
| 8 | + | |
| 9 | +@Schema(description = "管理后台 - IM 群聊消息 Response VO") | |
| 10 | +@Data | |
| 11 | +public class ImGroupMessageManagerRespVO { | |
| 12 | + | |
| 13 | + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 14 | + private Long id; | |
| 15 | + | |
| 16 | + @Schema(description = "客户端消息编号", example = "c-uuid-xxx") | |
| 17 | + private String clientMessageId; | |
| 18 | + | |
| 19 | + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 20 | + private Long groupId; | |
| 21 | + | |
| 22 | + @Schema(description = "群名称", example = "技术交流群") | |
| 23 | + private String groupName; | |
| 24 | + | |
| 25 | + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 26 | + private Long senderId; | |
| 27 | + | |
| 28 | + @Schema(description = "发送人昵称", example = "张三") | |
| 29 | + private String senderNickname; | |
| 30 | + | |
| 31 | + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 32 | + private Integer type; // 参见 ImMessageTypeEnum 枚举类 | |
| 33 | + | |
| 34 | + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 35 | + private String content; | |
| 36 | + | |
| 37 | + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 38 | + private Integer status; // 参见 ImMessageStatusEnum 枚举类 | |
| 39 | + | |
| 40 | + @Schema(description = "@ 目标用户编号列表") | |
| 41 | + private List<Long> atUserIds; | |
| 42 | + | |
| 43 | + @Schema(description = "回执状态", example = "0") | |
| 44 | + private Integer receiptStatus; // 参见 ImGroupMessageReceiptStatusEnum 枚举类 | |
| 45 | + | |
| 46 | + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 47 | + private LocalDateTime sendTime; | |
| 48 | + | |
| 49 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 50 | + private LocalDateTime createTime; | |
| 51 | + | |
| 52 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.EqualsAndHashCode; | |
| 7 | +import org.springframework.format.annotation.DateTimeFormat; | |
| 8 | + | |
| 9 | +import java.time.LocalDateTime; | |
| 10 | + | |
| 11 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | |
| 12 | + | |
| 13 | +@Schema(description = "管理后台 - IM 私聊消息分页 Request VO") | |
| 14 | +@Data | |
| 15 | +@EqualsAndHashCode(callSuper = true) | |
| 16 | +public class ImPrivateMessageManagerPageReqVO extends PageParam { | |
| 17 | + | |
| 18 | + @Schema(description = "发送人编号", example = "1024") | |
| 19 | + private Long senderId; | |
| 20 | + | |
| 21 | + @Schema(description = "接收人编号", example = "2048") | |
| 22 | + private Long receiverId; | |
| 23 | + | |
| 24 | + @Schema(description = "消息类型,参见 ImMessageTypeEnum 枚举类", example = "1") | |
| 25 | + private Integer type; | |
| 26 | + | |
| 27 | + @Schema(description = "消息状态,参见 ImMessageStatusEnum 枚举类", example = "0") | |
| 28 | + private Integer status; | |
| 29 | + | |
| 30 | + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") | |
| 31 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 32 | + private LocalDateTime[] sendTime; | |
| 33 | + | |
| 34 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.time.LocalDateTime; | |
| 7 | + | |
| 8 | +@Schema(description = "管理后台 - IM 私聊消息 Response VO") | |
| 9 | +@Data | |
| 10 | +public class ImPrivateMessageManagerRespVO { | |
| 11 | + | |
| 12 | + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 13 | + private Long id; | |
| 14 | + | |
| 15 | + @Schema(description = "客户端消息编号", example = "c-uuid-xxx") | |
| 16 | + private String clientMessageId; | |
| 17 | + | |
| 18 | + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 19 | + private Long senderId; | |
| 20 | + | |
| 21 | + @Schema(description = "发送人昵称", example = "张三") | |
| 22 | + private String senderNickname; | |
| 23 | + | |
| 24 | + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") | |
| 25 | + private Long receiverId; | |
| 26 | + | |
| 27 | + @Schema(description = "接收人昵称", example = "李四") | |
| 28 | + private String receiverNickname; | |
| 29 | + | |
| 30 | + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 31 | + private Integer type; // 参见 ImMessageTypeEnum 枚举类 | |
| 32 | + | |
| 33 | + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 34 | + private String content; | |
| 35 | + | |
| 36 | + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 37 | + private Integer status; // 参见 ImMessageStatusEnum 枚举类 | |
| 38 | + | |
| 39 | + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 40 | + private LocalDateTime sendTime; | |
| 41 | + | |
| 42 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 43 | + private LocalDateTime createTime; | |
| 44 | + | |
| 45 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordRespVO; | |
| 8 | +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; | |
| 9 | +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; | |
| 10 | +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; | |
| 11 | +import io.swagger.v3.oas.annotations.Operation; | |
| 12 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 13 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 14 | +import jakarta.annotation.Resource; | |
| 15 | +import jakarta.validation.Valid; | |
| 16 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 17 | +import org.springframework.validation.annotation.Validated; | |
| 18 | +import org.springframework.web.bind.annotation.*; | |
| 19 | + | |
| 20 | +import java.util.List; | |
| 21 | + | |
| 22 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 23 | + | |
| 24 | +@Tag(name = "管理后台 - IM 敏感词") | |
| 25 | +@RestController | |
| 26 | +@RequestMapping("/im/manager/sensitive-word") | |
| 27 | +@Validated | |
| 28 | +public class ImSensitiveWordManagerController { | |
| 29 | + | |
| 30 | + @Resource | |
| 31 | + private ImSensitiveWordService sensitiveWordService; | |
| 32 | + | |
| 33 | + @PostMapping("/create") | |
| 34 | + @Operation(summary = "新增敏感词") | |
| 35 | + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:create')") | |
| 36 | + public CommonResult<Long> createSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) { | |
| 37 | + return success(sensitiveWordService.createSensitiveWord(reqVO)); | |
| 38 | + } | |
| 39 | + | |
| 40 | + @PutMapping("/update") | |
| 41 | + @Operation(summary = "修改敏感词") | |
| 42 | + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:update')") | |
| 43 | + public CommonResult<Boolean> updateSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) { | |
| 44 | + sensitiveWordService.updateSensitiveWord(reqVO); | |
| 45 | + return success(true); | |
| 46 | + } | |
| 47 | + | |
| 48 | + @DeleteMapping("/delete") | |
| 49 | + @Operation(summary = "删除敏感词") | |
| 50 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | |
| 51 | + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')") | |
| 52 | + public CommonResult<Boolean> deleteSensitiveWord(@RequestParam("id") Long id) { | |
| 53 | + sensitiveWordService.deleteSensitiveWord(id); | |
| 54 | + return success(true); | |
| 55 | + } | |
| 56 | + | |
| 57 | + @DeleteMapping("/delete-list") | |
| 58 | + @Operation(summary = "批量删除敏感词") | |
| 59 | + @Parameter(name = "ids", description = "编号列表", required = true) | |
| 60 | + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')") | |
| 61 | + public CommonResult<Boolean> deleteSensitiveWordList(@RequestParam("ids") List<Long> ids) { | |
| 62 | + sensitiveWordService.deleteSensitiveWordList(ids); | |
| 63 | + return success(true); | |
| 64 | + } | |
| 65 | + | |
| 66 | + @GetMapping("/page") | |
| 67 | + @Operation(summary = "获得敏感词分页") | |
| 68 | + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')") | |
| 69 | + public CommonResult<PageResult<ImSensitiveWordRespVO>> getSensitiveWordPage( | |
| 70 | + @Valid ImSensitiveWordPageReqVO pageReqVO) { | |
| 71 | + PageResult<ImSensitiveWordDO> pageResult = sensitiveWordService.getSensitiveWordPage(pageReqVO); | |
| 72 | + return success(BeanUtils.toBean(pageResult, ImSensitiveWordRespVO.class)); | |
| 73 | + } | |
| 74 | + | |
| 75 | + @GetMapping("/get") | |
| 76 | + @Operation(summary = "获得敏感词详情") | |
| 77 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | |
| 78 | + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')") | |
| 79 | + public CommonResult<ImSensitiveWordRespVO> getSensitiveWord(@RequestParam("id") Long id) { | |
| 80 | + ImSensitiveWordDO word = sensitiveWordService.getSensitiveWord(id); | |
| 81 | + return success(BeanUtils.toBean(word, ImSensitiveWordRespVO.class)); | |
| 82 | + } | |
| 83 | + | |
| 84 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.EqualsAndHashCode; | |
| 7 | +import org.springframework.format.annotation.DateTimeFormat; | |
| 8 | + | |
| 9 | +import java.time.LocalDateTime; | |
| 10 | + | |
| 11 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | |
| 12 | + | |
| 13 | +@Schema(description = "管理后台 - IM 敏感词分页 Request VO") | |
| 14 | +@Data | |
| 15 | +@EqualsAndHashCode(callSuper = true) | |
| 16 | +public class ImSensitiveWordPageReqVO extends PageParam { | |
| 17 | + | |
| 18 | + @Schema(description = "敏感词,模糊匹配", example = "敏感") | |
| 19 | + private String word; | |
| 20 | + | |
| 21 | + @Schema(description = "状态,参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)", example = "0") | |
| 22 | + private Integer status; | |
| 23 | + | |
| 24 | + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") | |
| 25 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 26 | + private LocalDateTime[] createTime; | |
| 27 | + | |
| 28 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.time.LocalDateTime; | |
| 7 | + | |
| 8 | +@Schema(description = "管理后台 - IM 敏感词 Response VO") | |
| 9 | +@Data | |
| 10 | +public class ImSensitiveWordRespVO { | |
| 11 | + | |
| 12 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 13 | + private Long id; | |
| 14 | + | |
| 15 | + @Schema(description = "敏感词", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏感词内容") | |
| 16 | + private String word; | |
| 17 | + | |
| 18 | + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 19 | + private Integer status; // 参见 CommonStatusEnum 枚举类 | |
| 20 | + | |
| 21 | + @Schema(description = "创建人") | |
| 22 | + private String creator; | |
| 23 | + | |
| 24 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 25 | + private LocalDateTime createTime; | |
| 26 | + | |
| 27 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import jakarta.validation.constraints.NotBlank; | |
| 5 | +import jakarta.validation.constraints.NotNull; | |
| 6 | +import jakarta.validation.constraints.Size; | |
| 7 | +import lombok.Data; | |
| 8 | + | |
| 9 | +@Schema(description = "管理后台 - IM 敏感词新增 / 修改 Request VO") | |
| 10 | +@Data | |
| 11 | +public class ImSensitiveWordSaveReqVO { | |
| 12 | + | |
| 13 | + @Schema(description = "编号(修改时必填)", example = "1024") | |
| 14 | + private Long id; | |
| 15 | + | |
| 16 | + @Schema(description = "敏感词", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏感词内容") | |
| 17 | + @NotBlank(message = "敏感词不能为空") | |
| 18 | + @Size(max = 64, message = "敏感词长度不能超过 64") | |
| 19 | + private String word; | |
| 20 | + | |
| 21 | + @Schema(description = "状态,参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)", | |
| 22 | + requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 23 | + @NotNull(message = "状态不能为空") | |
| 24 | + private Integer status; | |
| 25 | + | |
| 26 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.im.dal.mysql.friend; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 3 | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; | |
| 5 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 6 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 9 | |
| ... | ... | @@ -43,4 +45,14 @@ public interface ImFriendMapper extends BaseMapperX<ImFriendDO> { |
| 43 | 45 | .eq(ImFriendDO::getStatus, status)); |
| 44 | 46 | } |
| 45 | 47 | |
| 48 | + default PageResult<ImFriendDO> selectPage(ImFriendManagerPageReqVO reqVO) { | |
| 49 | + return selectPage(reqVO, new LambdaQueryWrapperX<ImFriendDO>() | |
| 50 | + .eqIfPresent(ImFriendDO::getUserId, reqVO.getUserId()) | |
| 51 | + .eqIfPresent(ImFriendDO::getFriendUserId, reqVO.getFriendUserId()) | |
| 52 | + .eqIfPresent(ImFriendDO::getStatus, reqVO.getStatus()) | |
| 53 | + .eqIfPresent(ImFriendDO::getMuted, reqVO.getMuted()) | |
| 54 | + .betweenIfPresent(ImFriendDO::getAddTime, reqVO.getAddTime()) | |
| 55 | + .orderByDesc(ImFriendDO::getId)); | |
| 56 | + } | |
| 57 | + | |
| 46 | 58 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.im.dal.mysql.group; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 3 | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; | |
| 4 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 5 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 6 | 9 | |
| ... | ... | @@ -11,4 +14,15 @@ import org.apache.ibatis.annotations.Mapper; |
| 11 | 14 | */ |
| 12 | 15 | @Mapper |
| 13 | 16 | public interface ImGroupMapper extends BaseMapperX<ImGroupDO> { |
| 17 | + | |
| 18 | + default PageResult<ImGroupDO> selectPage(ImGroupManagerPageReqVO reqVO) { | |
| 19 | + return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupDO>() | |
| 20 | + .likeIfPresent(ImGroupDO::getName, reqVO.getName()) | |
| 21 | + .eqIfPresent(ImGroupDO::getOwnerUserId, reqVO.getOwnerUserId()) | |
| 22 | + .eqIfPresent(ImGroupDO::getStatus, reqVO.getStatus()) | |
| 23 | + .eqIfPresent(ImGroupDO::getBanned, reqVO.getBanned()) | |
| 24 | + .betweenIfPresent(ImGroupDO::getCreateTime, reqVO.getCreateTime()) | |
| 25 | + .orderByDesc(ImGroupDO::getId)); | |
| 26 | + } | |
| 27 | + | |
| 14 | 28 | } |
| 15 | 29 | \ No newline at end of file | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.im.dal.mysql.group; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 8 | +import com.baomidou.mybatisplus.core.toolkit.Wrappers; | |
| 7 | 9 | import org.apache.ibatis.annotations.Mapper; |
| 8 | 10 | |
| 9 | 11 | import java.time.LocalDateTime; |
| 10 | 12 | import java.util.Collection; |
| 13 | +import java.util.Collections; | |
| 14 | +import java.util.HashMap; | |
| 11 | 15 | import java.util.List; |
| 16 | +import java.util.Map; | |
| 12 | 17 | |
| 13 | 18 | /** |
| 14 | 19 | * IM 群成员 Mapper |
| ... | ... | @@ -78,4 +83,25 @@ public interface ImGroupMemberMapper extends BaseMapperX<ImGroupMemberDO> { |
| 78 | 83 | .eq(ImGroupMemberDO::getStatus, oldStatus)); |
| 79 | 84 | } |
| 80 | 85 | |
| 86 | + /** | |
| 87 | + * 批量按 group_id 统计指定状态的成员数 | |
| 88 | + */ | |
| 89 | + default Map<Long, Long> selectCountMapByGroupIdsAndStatus(Collection<Long> groupIds, Integer status) { | |
| 90 | + if (CollUtil.isEmpty(groupIds)) { | |
| 91 | + return Collections.emptyMap(); | |
| 92 | + } | |
| 93 | + // TODO @AI:使用 mybatis plus join,可以简化这个操作么? | |
| 94 | + List<Map<String, Object>> rows = selectMaps(Wrappers.<ImGroupMemberDO>query() | |
| 95 | + .select("group_id AS groupId", "COUNT(*) AS cnt") | |
| 96 | + .in("group_id", groupIds) | |
| 97 | + .eq("status", status) | |
| 98 | + .groupBy("group_id")); | |
| 99 | + // 转换成 Map<Long, Long> | |
| 100 | + Map<Long, Long> result = new HashMap<>(rows.size()); | |
| 101 | + rows.forEach(row -> result.put( | |
| 102 | + ((Number) row.get("groupId")).longValue(), | |
| 103 | + ((Number) row.get("cnt")).longValue())); | |
| 104 | + return result; | |
| 105 | + } | |
| 106 | + | |
| 81 | 107 | } |
| 82 | 108 | \ No newline at end of file | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.im.dal.mysql.message; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 3 | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; |
| 7 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; | |
| 6 | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| 7 | 9 | import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; |
| 8 | 10 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; |
| ... | ... | @@ -113,4 +115,14 @@ public interface ImGroupMessageMapper extends BaseMapperX<ImGroupMessageDO> { |
| 113 | 115 | .ne(ImGroupMessageDO::getStatus, ImMessageStatusEnum.RECALL.getStatus())); |
| 114 | 116 | } |
| 115 | 117 | |
| 118 | + default PageResult<ImGroupMessageDO> selectPage(ImGroupMessageManagerPageReqVO reqVO) { | |
| 119 | + return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupMessageDO>() | |
| 120 | + .eqIfPresent(ImGroupMessageDO::getGroupId, reqVO.getGroupId()) | |
| 121 | + .eqIfPresent(ImGroupMessageDO::getSenderId, reqVO.getSenderId()) | |
| 122 | + .eqIfPresent(ImGroupMessageDO::getType, reqVO.getType()) | |
| 123 | + .eqIfPresent(ImGroupMessageDO::getStatus, reqVO.getStatus()) | |
| 124 | + .betweenIfPresent(ImGroupMessageDO::getSendTime, reqVO.getSendTime()) | |
| 125 | + .orderByDesc(ImGroupMessageDO::getId)); | |
| 126 | + } | |
| 127 | + | |
| 116 | 128 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.im.dal.mysql.message; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 3 | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; |
| 7 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; | |
| 6 | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| 7 | 9 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; |
| 8 | 10 | import org.apache.ibatis.annotations.Mapper; |
| ... | ... | @@ -86,4 +88,14 @@ public interface ImPrivateMessageMapper extends BaseMapperX<ImPrivateMessageDO> |
| 86 | 88 | .eq(ImPrivateMessageDO::getStatus, whereStatus)); |
| 87 | 89 | } |
| 88 | 90 | |
| 91 | + default PageResult<ImPrivateMessageDO> selectPage(ImPrivateMessageManagerPageReqVO reqVO) { | |
| 92 | + return selectPage(reqVO, new LambdaQueryWrapperX<ImPrivateMessageDO>() | |
| 93 | + .eqIfPresent(ImPrivateMessageDO::getSenderId, reqVO.getSenderId()) | |
| 94 | + .eqIfPresent(ImPrivateMessageDO::getReceiverId, reqVO.getReceiverId()) | |
| 95 | + .eqIfPresent(ImPrivateMessageDO::getType, reqVO.getType()) | |
| 96 | + .eqIfPresent(ImPrivateMessageDO::getStatus, reqVO.getStatus()) | |
| 97 | + .betweenIfPresent(ImPrivateMessageDO::getSendTime, reqVO.getSendTime()) | |
| 98 | + .orderByDesc(ImPrivateMessageDO::getId)); | |
| 99 | + } | |
| 100 | + | |
| 89 | 101 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.im.dal.mysql.sensitiveword; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; | |
| 6 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; |
| 7 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 8 | 9 | |
| ... | ... | @@ -16,10 +17,22 @@ import java.util.List; |
| 16 | 17 | @Mapper |
| 17 | 18 | public interface ImSensitiveWordMapper extends BaseMapperX<ImSensitiveWordDO> { |
| 18 | 19 | |
| 19 | - // DONE @AI:已改名为 selectListByStatus,status 作为参数 | |
| 20 | 20 | default List<ImSensitiveWordDO> selectListByStatus(Integer status) { |
| 21 | 21 | return selectList(new LambdaQueryWrapperX<ImSensitiveWordDO>() |
| 22 | 22 | .eq(ImSensitiveWordDO::getStatus, status)); |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | + default ImSensitiveWordDO selectByWord(String word) { | |
| 26 | + return selectOne(new LambdaQueryWrapperX<ImSensitiveWordDO>() | |
| 27 | + .eq(ImSensitiveWordDO::getWord, word)); | |
| 28 | + } | |
| 29 | + | |
| 30 | + default PageResult<ImSensitiveWordDO> selectPage(ImSensitiveWordPageReqVO reqVO) { | |
| 31 | + return selectPage(reqVO, new LambdaQueryWrapperX<ImSensitiveWordDO>() | |
| 32 | + .likeIfPresent(ImSensitiveWordDO::getWord, reqVO.getWord()) | |
| 33 | + .eqIfPresent(ImSensitiveWordDO::getStatus, reqVO.getStatus()) | |
| 34 | + .betweenIfPresent(ImSensitiveWordDO::getCreateTime, reqVO.getCreateTime()) | |
| 35 | + .orderByDesc(ImSensitiveWordDO::getId)); | |
| 36 | + } | |
| 37 | + | |
| 25 | 38 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
| ... | ... | @@ -34,4 +34,8 @@ public interface ErrorCodeConstants { |
| 34 | 34 | ErrorCode FRIEND_NOT_FRIEND = new ErrorCode(1_040_600_001, "对方不是您的好友"); |
| 35 | 35 | ErrorCode FRIEND_ADD_SELF = new ErrorCode(1_040_600_002, "不允许添加自己为好友"); |
| 36 | 36 | |
| 37 | + // ========== 敏感词 (1-040-700-000) ========== | |
| 38 | + ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1_040_700_000, "敏感词不存在"); | |
| 39 | + ErrorCode SENSITIVE_WORD_DUPLICATED = new ErrorCode(1_040_700_001, "敏感词 '{}' 已存在"); | |
| 40 | + | |
| 37 | 41 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.framework.sensitiveword; | |
| 2 | + | |
| 3 | +import com.github.houbb.sensitive.word.bs.SensitiveWordBs; | |
| 4 | +import org.springframework.context.annotation.Bean; | |
| 5 | +import org.springframework.context.annotation.Configuration; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * IM 敏感词检测配置 | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@Configuration | |
| 13 | +public class ImSensitiveWordConfiguration { | |
| 14 | + | |
| 15 | + // TODO @AI:可能需要按需创建;因为多租户。 | |
| 16 | + /** | |
| 17 | + * 装配 {@link SensitiveWordBs}: | |
| 18 | + * 词库来源 = {@link ImSensitiveWordDeny}(DB 里启用的敏感词),并开启常用规范化能力。 | |
| 19 | + */ | |
| 20 | + @Bean | |
| 21 | + public SensitiveWordBs sensitiveWordBs(ImSensitiveWordDeny imSensitiveWordDeny) { | |
| 22 | + return SensitiveWordBs.newInstance() | |
| 23 | + .wordDeny(imSensitiveWordDeny) | |
| 24 | + .ignoreCase(true) | |
| 25 | + .ignoreWidth(true) // 忽略全/半角 | |
| 26 | + .ignoreNumStyle(true) // 忽略数字风格(中文/阿拉伯) | |
| 27 | + .ignoreChineseStyle(true) // 忽略繁简体 | |
| 28 | + .enableWordCheck(true) | |
| 29 | + .init(); | |
| 30 | + } | |
| 31 | + | |
| 32 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.framework.sensitiveword; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 4 | +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; | |
| 5 | +import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper; | |
| 6 | +import com.github.houbb.sensitive.word.api.IWordDeny; | |
| 7 | +import jakarta.annotation.Resource; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | + | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 12 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 13 | + | |
| 14 | +// TODO @AI:是不是可以,直接塞入一个数组。而不是这里查询??? | |
| 15 | +/** | |
| 16 | + * 自定义敏感词数据来源:从 im_sensitive_word 表加载启用状态的敏感词 | |
| 17 | + * <p> | |
| 18 | + * 由 {@link com.github.houbb.sensitive.word.bs.SensitiveWordBs} 在 init() 时回调, | |
| 19 | + * 因此 CRUD 完成后调用 sensitiveWordBs.init() 即可实现热更新。 | |
| 20 | + * | |
| 21 | + * @author 芋道源码 | |
| 22 | + */ | |
| 23 | +@Component | |
| 24 | +public class ImSensitiveWordDeny implements IWordDeny { | |
| 25 | + | |
| 26 | + @Resource | |
| 27 | + private ImSensitiveWordMapper sensitiveWordMapper; | |
| 28 | + | |
| 29 | + @Override | |
| 30 | + public List<String> deny() { | |
| 31 | + return convertList( | |
| 32 | + sensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()), | |
| 33 | + ImSensitiveWordDO::getWord); | |
| 34 | + } | |
| 35 | + | |
| 36 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.friend; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 3 | 4 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; |
| 5 | +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; | |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO; | |
| 4 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 5 | 8 | |
| 6 | 9 | import java.util.Collection; |
| ... | ... | @@ -84,5 +87,11 @@ public interface ImFriendService { |
| 84 | 87 | */ |
| 85 | 88 | void updateFriend(Long userId, ImFriendUpdateReqVO reqVO); |
| 86 | 89 | |
| 90 | + // ==================== 管理后台 ==================== | |
| 91 | + | |
| 92 | + /** | |
| 93 | + * 【管理后台】分页查询好友关系(已填充用户 / 好友昵称) | |
| 94 | + */ | |
| 95 | + PageResult<ImFriendManagerRespVO> getFriendPage(ImFriendManagerPageReqVO reqVO); | |
| 87 | 96 | |
| 88 | 97 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.friend; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.extra.spring.SpringUtil; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 7 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 8 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 5 | 9 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; |
| 10 | +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; | |
| 11 | +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO; | |
| 6 | 12 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 7 | 13 | import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; |
| 8 | 14 | import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; |
| 9 | 15 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 10 | 16 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; |
| 11 | 17 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 18 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 12 | 19 | import jakarta.annotation.Resource; |
| 13 | 20 | import lombok.extern.slf4j.Slf4j; |
| 14 | 21 | import org.springframework.cache.annotation.CacheEvict; |
| ... | ... | @@ -20,11 +27,10 @@ import org.springframework.transaction.annotation.Transactional; |
| 20 | 27 | import org.springframework.validation.annotation.Validated; |
| 21 | 28 | |
| 22 | 29 | import java.time.LocalDateTime; |
| 23 | -import java.util.Collection; | |
| 24 | -import java.util.List; | |
| 25 | -import java.util.Objects; | |
| 30 | +import java.util.*; | |
| 26 | 31 | |
| 27 | 32 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 33 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 28 | 34 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND; |
| 29 | 35 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF; |
| 30 | 36 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; |
| ... | ... | @@ -181,4 +187,28 @@ public class ImFriendServiceImpl implements ImFriendService { |
| 181 | 187 | return SpringUtil.getBean(getClass()); |
| 182 | 188 | } |
| 183 | 189 | |
| 190 | + // ==================== 管理后台 ==================== | |
| 191 | + | |
| 192 | + // TODO @AI:拿到 controller 拼接数据,简化 service; | |
| 193 | + @Override | |
| 194 | + public PageResult<ImFriendManagerRespVO> getFriendPage(ImFriendManagerPageReqVO reqVO) { | |
| 195 | + // 1. 分页查询 | |
| 196 | + PageResult<ImFriendDO> pageResult = imFriendMapper.selectPage(reqVO); | |
| 197 | + if (CollUtil.isEmpty(pageResult.getList())) { | |
| 198 | + return PageResult.empty(pageResult.getTotal()); | |
| 199 | + } | |
| 200 | + | |
| 201 | + // 2. 一次性批量查询用户 + 好友的昵称 | |
| 202 | + Set<Long> userIds = new HashSet<>(); | |
| 203 | + userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getUserId)); | |
| 204 | + userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getFriendUserId)); | |
| 205 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); | |
| 206 | + | |
| 207 | + // 3. 转换为 VO,填充昵称 | |
| 208 | + return BeanUtils.toBean(pageResult, ImFriendManagerRespVO.class, vo -> { | |
| 209 | + MapUtils.findAndThen(userMap, vo.getUserId(), user -> vo.setUserNickname(user.getNickname())); | |
| 210 | + MapUtils.findAndThen(userMap, vo.getFriendUserId(), user -> vo.setFriendNickname(user.getNickname())); | |
| 211 | + }); | |
| 212 | + } | |
| 213 | + | |
| 184 | 214 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.group; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; |
| 4 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO; | |
| 4 | 5 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 5 | 6 | import jakarta.validation.Valid; |
| 6 | 7 | |
| 7 | 8 | import java.time.LocalDateTime; |
| 8 | 9 | import java.util.Collection; |
| 9 | 10 | import java.util.List; |
| 11 | +import java.util.Map; | |
| 10 | 12 | |
| 11 | 13 | /** |
| 12 | 14 | * 群成员 Service 接口 |
| ... | ... | @@ -24,6 +26,15 @@ public interface ImGroupMemberService { |
| 24 | 26 | ImGroupMemberDO getGroupMember(Long id); |
| 25 | 27 | |
| 26 | 28 | /** |
| 29 | + * 获得群成员 | |
| 30 | + * | |
| 31 | + * @param groupId 群编号 | |
| 32 | + * @param userId 用户编号 | |
| 33 | + * @return 群成员 | |
| 34 | + */ | |
| 35 | + ImGroupMemberDO getGroupMember(Long groupId, Long userId); | |
| 36 | + | |
| 37 | + /** | |
| 27 | 38 | * 根据群组 id 查询群成员(包含所有状态) |
| 28 | 39 | * |
| 29 | 40 | * @param groupId 群组id |
| ... | ... | @@ -137,4 +148,22 @@ public interface ImGroupMemberService { |
| 137 | 148 | */ |
| 138 | 149 | void removeGroupMembersByGroupId(Long groupId); |
| 139 | 150 | |
| 151 | + /** | |
| 152 | + * 批量按 group 统计活跃成员数:(group_id → count) | |
| 153 | + * | |
| 154 | + * @param groupIds 群编号集合 | |
| 155 | + * @return 群成员数 Map | |
| 156 | + */ | |
| 157 | + Map<Long, Long> getActiveMemberCountMap(Collection<Long> groupIds); | |
| 158 | + | |
| 159 | + // ==================== 管理后台 ==================== | |
| 160 | + | |
| 161 | + /** | |
| 162 | + * 【管理后台】获取群成员列表 | |
| 163 | + * | |
| 164 | + * @param groupId 群编号 | |
| 165 | + * @return 群成员列表 | |
| 166 | + */ | |
| 167 | + List<ImGroupMemberManagerRespVO> getGroupMemberList(Long groupId); | |
| 168 | + | |
| 140 | 169 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
| ... | ... | @@ -2,12 +2,16 @@ package cn.iocoder.yudao.module.im.service.group; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 5 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 5 | 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | 7 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; |
| 8 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO; | |
| 7 | 9 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 8 | 10 | import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; |
| 9 | 11 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 10 | 12 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; |
| 13 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 14 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 11 | 15 | import jakarta.annotation.Resource; |
| 12 | 16 | import lombok.extern.slf4j.Slf4j; |
| 13 | 17 | import org.springframework.cache.annotation.CacheEvict; |
| ... | ... | @@ -17,14 +21,10 @@ import org.springframework.stereotype.Service; |
| 17 | 21 | import org.springframework.validation.annotation.Validated; |
| 18 | 22 | |
| 19 | 23 | import java.time.LocalDateTime; |
| 20 | -import java.util.ArrayList; | |
| 21 | -import java.util.Collection; | |
| 22 | -import java.util.List; | |
| 23 | -import java.util.Map; | |
| 24 | +import java.util.*; | |
| 24 | 25 | |
| 25 | 26 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 26 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 27 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 27 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | |
| 28 | 28 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MEMBER_IDS; |
| 29 | 29 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; |
| 30 | 30 | |
| ... | ... | @@ -44,12 +44,20 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { |
| 44 | 44 | @Resource |
| 45 | 45 | private ImWebSocketService webSocketService; |
| 46 | 46 | |
| 47 | + @Resource | |
| 48 | + private AdminUserApi adminUserApi; | |
| 49 | + | |
| 47 | 50 | @Override |
| 48 | 51 | public ImGroupMemberDO getGroupMember(Long id) { |
| 49 | 52 | return groupMemberMapper.selectById(id); |
| 50 | 53 | } |
| 51 | 54 | |
| 52 | 55 | @Override |
| 56 | + public ImGroupMemberDO getGroupMember(Long groupId, Long userId) { | |
| 57 | + return groupMemberMapper.selectByGroupIdAndUserId(groupId, userId); | |
| 58 | + } | |
| 59 | + | |
| 60 | + @Override | |
| 53 | 61 | public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) { |
| 54 | 62 | return groupMemberMapper.selectListByGroupId(groupId); |
| 55 | 63 | } |
| ... | ... | @@ -209,4 +217,30 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { |
| 209 | 217 | .setQuitTime(LocalDateTime.now())); |
| 210 | 218 | } |
| 211 | 219 | |
| 220 | + @Override | |
| 221 | + public Map<Long, Long> getActiveMemberCountMap(Collection<Long> groupIds) { | |
| 222 | + return groupMemberMapper.selectCountMapByGroupIdsAndStatus(groupIds, CommonStatusEnum.ENABLE.getStatus()); | |
| 223 | + } | |
| 224 | + | |
| 225 | + // ==================== 管理后台 ==================== | |
| 226 | + | |
| 227 | + // TODO @AI:这个拼接的逻辑,拿到 controller,这里只返回 list do; | |
| 228 | + @Override | |
| 229 | + public List<ImGroupMemberManagerRespVO> getGroupMemberList(Long groupId) { | |
| 230 | + // 1. 查询有效群成员 | |
| 231 | + List<ImGroupMemberDO> members = getActiveGroupMemberListByGroupId(groupId); | |
| 232 | + if (CollUtil.isEmpty(members)) { | |
| 233 | + return Collections.emptyList(); | |
| 234 | + } | |
| 235 | + | |
| 236 | + // 2. 批量查询用户信息 | |
| 237 | + Set<Long> userIds = convertSet(members, ImGroupMemberDO::getUserId); | |
| 238 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); | |
| 239 | + | |
| 240 | + // 3. 转换为 VO,填充昵称、头像 | |
| 241 | + return BeanUtils.toBean(members, ImGroupMemberManagerRespVO.class, vo -> | |
| 242 | + MapUtils.findAndThen(userMap, vo.getUserId(), user -> | |
| 243 | + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()))); | |
| 244 | + } | |
| 245 | + | |
| 212 | 246 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.group; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 3 | 4 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; |
| 4 | 5 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| 5 | 6 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; |
| 8 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; | |
| 9 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; | |
| 10 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO; | |
| 7 | 11 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 8 | 12 | import jakarta.validation.Valid; |
| 9 | 13 | |
| 14 | +import java.util.Collection; | |
| 10 | 15 | import java.util.List; |
| 16 | +import java.util.Map; | |
| 11 | 17 | |
| 12 | 18 | /** |
| 13 | 19 | * 群 Service 接口 |
| ... | ... | @@ -59,6 +65,14 @@ public interface ImGroupService { |
| 59 | 65 | ImGroupDO getGroup(Long id); |
| 60 | 66 | |
| 61 | 67 | /** |
| 68 | + * 批量获得群 Map | |
| 69 | + * | |
| 70 | + * @param ids 群编号集合 | |
| 71 | + * @return 群 Map(key = 群编号) | |
| 72 | + */ | |
| 73 | + Map<Long, ImGroupDO> getGroupMap(Collection<Long> ids); | |
| 74 | + | |
| 75 | + /** | |
| 62 | 76 | * 校验群存在且未封禁、未解散 |
| 63 | 77 | * |
| 64 | 78 | * @param groupId 群编号 |
| ... | ... | @@ -122,4 +136,30 @@ public interface ImGroupService { |
| 122 | 136 | */ |
| 123 | 137 | void removeGroupMember(Long userId, @Valid ImGroupMemberRemoveReqVO removeReqVO); |
| 124 | 138 | |
| 139 | + // ==================== 管理后台 ==================== | |
| 140 | + | |
| 141 | + // TODO DONE @AI:是不是 controller 拆分哈;(getGroupPage 留在 ImGroupService;getGroupMemberList 已拆到 ImGroupMemberService + ImGroupMemberManagerController) | |
| 142 | + // TODO @AI:不对;应该 VO 放在 controller 拼接 | |
| 143 | + /** | |
| 144 | + * 【管理后台】分页查询群列表 | |
| 145 | + * | |
| 146 | + * @param pageReqVO 分页查询条件 | |
| 147 | + * @return 群分页列表 | |
| 148 | + */ | |
| 149 | + PageResult<ImGroupManagerRespVO> getGroupPage(ImGroupManagerPageReqVO pageReqVO); | |
| 150 | + | |
| 151 | + /** | |
| 152 | + * 【管理后台】封禁群 | |
| 153 | + * | |
| 154 | + * @param banReqVO 封禁信息(含群编号、封禁原因) | |
| 155 | + */ | |
| 156 | + void banGroup(@Valid ImGroupManagerBanReqVO banReqVO); | |
| 157 | + | |
| 158 | + /** | |
| 159 | + * 【管理后台】解封群 | |
| 160 | + * | |
| 161 | + * @param id 群编号 | |
| 162 | + */ | |
| 163 | + void unbanGroup(Long id); | |
| 164 | + | |
| 125 | 165 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
| ... | ... | @@ -7,11 +7,16 @@ import cn.hutool.core.util.ObjUtil; |
| 7 | 7 | import cn.hutool.core.util.StrUtil; |
| 8 | 8 | import cn.hutool.extra.spring.SpringUtil; |
| 9 | 9 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 10 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 11 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 10 | 12 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | 13 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; |
| 12 | 14 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| 13 | 15 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; |
| 14 | 16 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; |
| 17 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; | |
| 18 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; | |
| 19 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO; | |
| 15 | 20 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 16 | 21 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 17 | 22 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| ... | ... | @@ -35,6 +40,7 @@ import java.util.*; |
| 35 | 40 | import java.util.stream.Collectors; |
| 36 | 41 | |
| 37 | 42 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 43 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 38 | 44 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 39 | 45 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP; |
| 40 | 46 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; |
| ... | ... | @@ -256,6 +262,14 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 256 | 262 | } |
| 257 | 263 | |
| 258 | 264 | @Override |
| 265 | + public Map<Long, ImGroupDO> getGroupMap(Collection<Long> ids) { | |
| 266 | + if (CollUtil.isEmpty(ids)) { | |
| 267 | + return Collections.emptyMap(); | |
| 268 | + } | |
| 269 | + return convertMap(groupMapper.selectByIds(ids), ImGroupDO::getId); | |
| 270 | + } | |
| 271 | + | |
| 272 | + @Override | |
| 259 | 273 | public List<ImGroupDO> getMyGroupList(Long userId) { |
| 260 | 274 | // 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态) |
| 261 | 275 | List<ImGroupMemberDO> members = groupMemberService.getActiveGroupMemberListByUserId(userId); |
| ... | ... | @@ -271,6 +285,53 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 271 | 285 | return groupMapper.selectByIds(groupIds); |
| 272 | 286 | } |
| 273 | 287 | |
| 288 | + // ==================== 管理后台 ==================== | |
| 289 | + | |
| 290 | + @Override | |
| 291 | + public PageResult<ImGroupManagerRespVO> getGroupPage(ImGroupManagerPageReqVO pageReqVO) { | |
| 292 | + // 1. 分页查询群 | |
| 293 | + PageResult<ImGroupDO> pageResult = groupMapper.selectPage(pageReqVO); | |
| 294 | + if (CollUtil.isEmpty(pageResult.getList())) { | |
| 295 | + return PageResult.empty(pageResult.getTotal()); | |
| 296 | + } | |
| 297 | + | |
| 298 | + // 2.1 批量查询群主昵称 | |
| 299 | + Set<Long> ownerUserIds = convertSet(pageResult.getList(), ImGroupDO::getOwnerUserId); | |
| 300 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(ownerUserIds); | |
| 301 | + // 2.2 一次 GROUP BY 查出本页所有群的活跃成员数,避免按行 N+1 | |
| 302 | + Set<Long> groupIds = convertSet(pageResult.getList(), ImGroupDO::getId); | |
| 303 | + Map<Long, Long> memberCountMap = groupMemberService.getActiveMemberCountMap(groupIds); | |
| 304 | + // 2.3 转换为 VO,填充群主昵称、群成员数量 | |
| 305 | + return BeanUtils.toBean(pageResult, ImGroupManagerRespVO.class, vo -> { | |
| 306 | + MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerNickname(user.getNickname())); | |
| 307 | + vo.setMemberCount(memberCountMap.getOrDefault(vo.getId(), 0L).intValue()); | |
| 308 | + }); | |
| 309 | + } | |
| 310 | + | |
| 311 | + @Override | |
| 312 | + @CacheEvict(cacheNames = GROUP, key = "#banReqVO.id") | |
| 313 | + public void banGroup(ImGroupManagerBanReqVO banReqVO) { | |
| 314 | + // 1. 校验群存在 | |
| 315 | + if (getSelf().getGroup(banReqVO.getId()) == null) { | |
| 316 | + throw exception(GROUP_NOT_EXISTS); | |
| 317 | + } | |
| 318 | + // 2. 更新封禁状态 | |
| 319 | + groupMapper.updateById(new ImGroupDO().setId(banReqVO.getId()) | |
| 320 | + .setBanned(true).setBannedReason(banReqVO.getReason()).setBannedTime(LocalDateTime.now())); | |
| 321 | + } | |
| 322 | + | |
| 323 | + @Override | |
| 324 | + @CacheEvict(cacheNames = GROUP, key = "#id") | |
| 325 | + public void unbanGroup(Long id) { | |
| 326 | + // 1. 校验群存在 | |
| 327 | + if (getSelf().getGroup(id) == null) { | |
| 328 | + throw exception(GROUP_NOT_EXISTS); | |
| 329 | + } | |
| 330 | + // 2. 解封(保留 bannedReason / bannedTime 作为历史记录) | |
| 331 | + // TODO DONE @AI:不要使用 mybatis plus 的方法,直接操作。另外,感觉封禁时间、封禁原因,没必要置空,留着问题不大。 | |
| 332 | + groupMapper.updateById(new ImGroupDO().setId(id).setBanned(false)); | |
| 333 | + } | |
| 334 | + | |
| 274 | 335 | private ImGroupServiceImpl getSelf() { |
| 275 | 336 | return SpringUtil.getBean(getClass()); |
| 276 | 337 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.message; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; | |
| 5 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO; | |
| 3 | 6 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; |
| 4 | 7 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; |
| 5 | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| ... | ... | @@ -112,5 +115,17 @@ public interface ImGroupMessageService { |
| 112 | 115 | */ |
| 113 | 116 | void sendTipGroupMessage(Long senderId, Long groupId, Set<Long> memberUserIds, String content); |
| 114 | 117 | |
| 118 | + // ==================== 管理后台 ==================== | |
| 119 | + | |
| 120 | + /** | |
| 121 | + * 【管理后台】分页查询群聊消息(已填充群名称、发送人昵称) | |
| 122 | + */ | |
| 123 | + PageResult<ImGroupMessageManagerRespVO> getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO); | |
| 124 | + | |
| 125 | + /** | |
| 126 | + * 【管理后台】获取群聊消息详情 | |
| 127 | + */ | |
| 128 | + ImGroupMessageDO getGroupMessage(Long id); | |
| 129 | + | |
| 115 | 130 | } |
| 116 | 131 | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
| ... | ... | @@ -6,13 +6,17 @@ import cn.hutool.core.util.IdUtil; |
| 6 | 6 | import cn.hutool.core.util.ObjUtil; |
| 7 | 7 | import cn.hutool.extra.spring.SpringUtil; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 9 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 10 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 9 | 11 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 10 | 12 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 13 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; | |
| 14 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO; | |
| 11 | 15 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; |
| 12 | 16 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; |
| 17 | +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; | |
| 13 | 18 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 14 | 19 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| 15 | -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; | |
| 16 | 20 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; |
| 17 | 21 | import cn.iocoder.yudao.module.im.dal.redis.message.GroupMessageReadRedisDAO; |
| 18 | 22 | import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; |
| ... | ... | @@ -23,6 +27,9 @@ import cn.iocoder.yudao.module.im.service.group.ImGroupService; |
| 23 | 27 | import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; |
| 24 | 28 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 25 | 29 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; |
| 30 | +import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; | |
| 31 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 32 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 26 | 33 | import jakarta.annotation.Resource; |
| 27 | 34 | import lombok.extern.slf4j.Slf4j; |
| 28 | 35 | import org.springframework.scheduling.annotation.Async; |
| ... | ... | @@ -70,6 +77,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { |
| 70 | 77 | @Resource |
| 71 | 78 | private ImWebSocketService imWebSocketService; |
| 72 | 79 | |
| 80 | + @Resource | |
| 81 | + private AdminUserApi adminUserApi; | |
| 82 | + | |
| 73 | 83 | @Override |
| 74 | 84 | public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) { |
| 75 | 85 | // 1.1 幂等校验:根据 senderId + clientMessageId 查重 |
| ... | ... | @@ -542,6 +552,35 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { |
| 542 | 552 | ImGroupMessageDTO.ofSend(tipMessage)); |
| 543 | 553 | } |
| 544 | 554 | |
| 555 | + // ==================== 管理后台 ==================== | |
| 556 | + | |
| 557 | + // TODO @AI:这些挪到 controller 拼接 VO | |
| 558 | + @Override | |
| 559 | + public PageResult<ImGroupMessageManagerRespVO> getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO) { | |
| 560 | + // 1. 分页查询 | |
| 561 | + PageResult<ImGroupMessageDO> pageResult = groupMessageMapper.selectPage(reqVO); | |
| 562 | + if (CollUtil.isEmpty(pageResult.getList())) { | |
| 563 | + return PageResult.empty(pageResult.getTotal()); | |
| 564 | + } | |
| 565 | + | |
| 566 | + // 2. 批量查询群名称、发送人昵称 | |
| 567 | + Set<Long> groupIds = convertSet(pageResult.getList(), ImGroupMessageDO::getGroupId); | |
| 568 | + Set<Long> senderIds = convertSet(pageResult.getList(), ImGroupMessageDO::getSenderId); | |
| 569 | + Map<Long, ImGroupDO> groupMap = groupService.getGroupMap(groupIds); | |
| 570 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(senderIds); | |
| 571 | + | |
| 572 | + // 3. 转换为 VO,填充群名 / 发送人昵称 | |
| 573 | + return BeanUtils.toBean(pageResult, ImGroupMessageManagerRespVO.class, vo -> { | |
| 574 | + MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName())); | |
| 575 | + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname())); | |
| 576 | + }); | |
| 577 | + } | |
| 578 | + | |
| 579 | + @Override | |
| 580 | + public ImGroupMessageDO getGroupMessage(Long id) { | |
| 581 | + return groupMessageMapper.selectById(id); | |
| 582 | + } | |
| 583 | + | |
| 545 | 584 | private ImGroupMessageServiceImpl getSelf() { |
| 546 | 585 | return SpringUtil.getBean(getClass()); |
| 547 | 586 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.message; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; | |
| 5 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO; | |
| 3 | 6 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; |
| 4 | 7 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 5 | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| ... | ... | @@ -85,4 +88,16 @@ public interface ImPrivateMessageService { |
| 85 | 88 | */ |
| 86 | 89 | void sendTipPrivateMessage(Long senderId, Long receiverId, String content); |
| 87 | 90 | |
| 91 | + // ==================== 管理后台 ==================== | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * 【管理后台】分页查询私聊消息(已填充发送人 / 接收人昵称) | |
| 95 | + */ | |
| 96 | + PageResult<ImPrivateMessageManagerRespVO> getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO); | |
| 97 | + | |
| 98 | + /** | |
| 99 | + * 【管理后台】获取私聊消息详情 | |
| 100 | + */ | |
| 101 | + ImPrivateMessageDO getPrivateMessage(Long id); | |
| 102 | + | |
| 88 | 103 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.message; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.lang.Assert; |
| 4 | 5 | import cn.hutool.core.util.IdUtil; |
| 5 | 6 | import cn.hutool.core.util.ObjUtil; |
| 7 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 8 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 6 | 9 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 7 | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; | |
| 12 | +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO; | |
| 8 | 13 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; |
| 9 | 14 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 10 | 15 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| ... | ... | @@ -16,6 +21,8 @@ import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; |
| 16 | 21 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 17 | 22 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; |
| 18 | 23 | import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; |
| 24 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 25 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 19 | 26 | import jakarta.annotation.Resource; |
| 20 | 27 | import lombok.extern.slf4j.Slf4j; |
| 21 | 28 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -23,9 +30,13 @@ import org.springframework.transaction.annotation.Transactional; |
| 23 | 30 | import org.springframework.validation.annotation.Validated; |
| 24 | 31 | |
| 25 | 32 | import java.time.LocalDateTime; |
| 33 | +import java.util.HashSet; | |
| 26 | 34 | import java.util.List; |
| 35 | +import java.util.Map; | |
| 36 | +import java.util.Set; | |
| 27 | 37 | |
| 28 | 38 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 39 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 29 | 40 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; |
| 30 | 41 | import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; |
| 31 | 42 | |
| ... | ... | @@ -50,6 +61,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { |
| 50 | 61 | @Resource |
| 51 | 62 | private ImWebSocketService imWebSocketService; |
| 52 | 63 | |
| 64 | + @Resource | |
| 65 | + private AdminUserApi adminUserApi; | |
| 66 | + | |
| 53 | 67 | @Override |
| 54 | 68 | public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) { |
| 55 | 69 | // 1.1 幂等校验:根据 senderId + clientMessageId 查重 |
| ... | ... | @@ -167,6 +181,35 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { |
| 167 | 181 | return privateMessageMapper.selectHistoryList(userId, reqVO.getReceiverId(), reqVO.getMaxId(), reqVO.getLimit()); |
| 168 | 182 | } |
| 169 | 183 | |
| 184 | + // ==================== 管理后台 ==================== | |
| 185 | + | |
| 186 | + // TODO @AI:这些挪到 controller 拼接 VO | |
| 187 | + @Override | |
| 188 | + public PageResult<ImPrivateMessageManagerRespVO> getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO) { | |
| 189 | + // 1. 分页查询 | |
| 190 | + PageResult<ImPrivateMessageDO> pageResult = privateMessageMapper.selectPage(reqVO); | |
| 191 | + if (CollUtil.isEmpty(pageResult.getList())) { | |
| 192 | + return PageResult.empty(pageResult.getTotal()); | |
| 193 | + } | |
| 194 | + | |
| 195 | + // 2. 一次性批量查询发送人 + 接收人昵称 | |
| 196 | + Set<Long> userIds = new HashSet<>(); | |
| 197 | + userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getSenderId)); | |
| 198 | + userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getReceiverId)); | |
| 199 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); | |
| 200 | + | |
| 201 | + // 3. 转换为 VO,填充昵称 | |
| 202 | + return BeanUtils.toBean(pageResult, ImPrivateMessageManagerRespVO.class, vo -> { | |
| 203 | + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname())); | |
| 204 | + MapUtils.findAndThen(userMap, vo.getReceiverId(), user -> vo.setReceiverNickname(user.getNickname())); | |
| 205 | + }); | |
| 206 | + } | |
| 207 | + | |
| 208 | + @Override | |
| 209 | + public ImPrivateMessageDO getPrivateMessage(Long id) { | |
| 210 | + return privateMessageMapper.selectById(id); | |
| 211 | + } | |
| 212 | + | |
| 170 | 213 | @Override |
| 171 | 214 | public void sendTipPrivateMessage(Long senderId, Long receiverId, String content) { |
| 172 | 215 | // 1. 插入 TIP_TEXT 消息 | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.sensitiveword; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; | |
| 5 | +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; | |
| 6 | +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; | |
| 7 | +import jakarta.validation.Valid; | |
| 8 | + | |
| 9 | +import java.util.List; | |
| 10 | + | |
| 3 | 11 | /** |
| 4 | 12 | * IM 敏感词 Service 接口 |
| 5 | 13 | * |
| ... | ... | @@ -14,4 +22,36 @@ public interface ImSensitiveWordService { |
| 14 | 22 | */ |
| 15 | 23 | void validateText(String text); |
| 16 | 24 | |
| 25 | + // ==================== 管理后台 ==================== | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 【管理后台】分页查询敏感词 | |
| 29 | + */ | |
| 30 | + PageResult<ImSensitiveWordDO> getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO); | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * 【管理后台】获取敏感词详情 | |
| 34 | + */ | |
| 35 | + ImSensitiveWordDO getSensitiveWord(Long id); | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * 【管理后台】新增敏感词,返回新增 id | |
| 39 | + */ | |
| 40 | + Long createSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO); | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * 【管理后台】修改敏感词 | |
| 44 | + */ | |
| 45 | + void updateSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO); | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * 【管理后台】删除敏感词 | |
| 49 | + */ | |
| 50 | + void deleteSensitiveWord(Long id); | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * 【管理后台】批量删除敏感词 | |
| 54 | + */ | |
| 55 | + void deleteSensitiveWordList(List<Long> ids); | |
| 56 | + | |
| 17 | 57 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.sensitiveword; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.util.ObjUtil; | |
| 5 | +import cn.hutool.core.util.StrUtil; | |
| 6 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 7 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 8 | +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; | |
| 9 | +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; | |
| 4 | 10 | import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; |
| 5 | 11 | import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper; |
| 6 | -import jakarta.annotation.PostConstruct; | |
| 12 | +import com.github.houbb.sensitive.word.bs.SensitiveWordBs; | |
| 7 | 13 | import jakarta.annotation.Resource; |
| 8 | 14 | import lombok.extern.slf4j.Slf4j; |
| 9 | -import org.springframework.scheduling.annotation.Scheduled; | |
| 10 | 15 | import org.springframework.stereotype.Service; |
| 11 | 16 | import org.springframework.validation.annotation.Validated; |
| 12 | 17 | |
| 13 | -import java.util.Collections; | |
| 14 | 18 | import java.util.List; |
| 15 | -import java.util.stream.Collectors; | |
| 16 | 19 | |
| 17 | 20 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 18 | 21 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED; |
| 22 | +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_DUPLICATED; | |
| 23 | +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS; | |
| 19 | 24 | |
| 20 | -// DONE @AI:已实现内存化,启动时加载 + 定时刷新 | |
| 21 | 25 | /** |
| 22 | 26 | * IM 敏感词 Service 实现类 |
| 27 | + * <p> | |
| 28 | + * 词库匹配交给 houbb sensitive-word 库(trie 树 + 全/半角 / 大小写 / 繁简体 / 数字风格规范化) | |
| 23 | 29 | * |
| 24 | 30 | * @author 芋道源码 |
| 25 | 31 | */ |
| ... | ... | @@ -29,48 +35,122 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSIT |
| 29 | 35 | public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { |
| 30 | 36 | |
| 31 | 37 | @Resource |
| 32 | - private ImSensitiveWordMapper imSensitiveWordMapper; | |
| 38 | + private ImSensitiveWordMapper sensitiveWordMapper; | |
| 33 | 39 | |
| 40 | + // TODO @AI:当前节点,立即刷新。其他节点,通过 job 扫描最后更新时间(只要有不同于目前最后一条记录的最大更新时间,就重新构建对应的树)。 | |
| 34 | 41 | /** |
| 35 | - * 内存缓存的敏感词列表 | |
| 42 | + * houbb 敏感词检测器,词库由 ImSensitiveWordDeny 在 init 时回调加载 | |
| 36 | 43 | */ |
| 37 | - private volatile List<String> sensitiveWords = Collections.emptyList(); | |
| 44 | + @Resource | |
| 45 | + private SensitiveWordBs sensitiveWordBs; | |
| 38 | 46 | |
| 47 | + // TODO @AI:你看看现有仓库里,是否有类似这种的。按照最后更新判断,是否需要刷新本地缓存。 | |
| 39 | 48 | /** |
| 40 | - * 初始化:启动时加载敏感词到内存 | |
| 49 | + * 触发 houbb 词库重新加载(重建 trie) | |
| 50 | + * <p> | |
| 51 | + * 加 synchronized 是因为 SensitiveWordBs.init() 内部对 wordData 做就地写入, | |
| 52 | + * 若与并发的 contains() 重叠会读到半构建的 trie。互斥后短暂阻塞 contains 即可保证可见性。 | |
| 53 | + * <p> | |
| 54 | + * init() 本身幂等,单实例可以直接调;多实例部署时需配合 Redis pub/sub 广播让各实例 init。 | |
| 41 | 55 | */ |
| 42 | - @PostConstruct | |
| 43 | - public void init() { | |
| 44 | - loadSensitiveWords(); | |
| 56 | + private synchronized void reloadSensitiveWords() { | |
| 57 | + sensitiveWordBs.init(); | |
| 58 | + log.info("[reloadSensitiveWords][敏感词库已 reload]"); | |
| 45 | 59 | } |
| 46 | 60 | |
| 47 | - /** | |
| 48 | - * 定时刷新:每 5 分钟重新加载一次 | |
| 49 | - */ | |
| 50 | - @Scheduled(fixedDelay = 5 * 60 * 1000, initialDelay = 5 * 60 * 1000) | |
| 51 | - public void refresh() { | |
| 52 | - loadSensitiveWords(); | |
| 61 | + // TODO @AI:需要按照租户来。 | |
| 62 | + @Override | |
| 63 | + public void validateText(String text) { | |
| 64 | + if (StrUtil.isBlank(text)) { | |
| 65 | + return; | |
| 66 | + } | |
| 67 | + if (sensitiveWordBs.contains(text)) { | |
| 68 | + throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED); | |
| 69 | + } | |
| 53 | 70 | } |
| 54 | 71 | |
| 55 | - private void loadSensitiveWords() { | |
| 56 | - List<ImSensitiveWordDO> enabledWords = imSensitiveWordMapper.selectListByStatus( | |
| 57 | - CommonStatusEnum.ENABLE.getStatus()); | |
| 58 | - sensitiveWords = enabledWords.stream() | |
| 59 | - .map(ImSensitiveWordDO::getWord) | |
| 60 | - .collect(Collectors.toList()); | |
| 61 | - log.info("[loadSensitiveWords][加载 IM 敏感词 {} 个]", sensitiveWords.size()); | |
| 72 | + // ==================== 管理后台 ==================== | |
| 73 | + | |
| 74 | + @Override | |
| 75 | + public PageResult<ImSensitiveWordDO> getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO) { | |
| 76 | + return sensitiveWordMapper.selectPage(reqVO); | |
| 62 | 77 | } |
| 63 | 78 | |
| 64 | 79 | @Override |
| 65 | - public void validateText(String text) { | |
| 66 | - if (text == null || text.isEmpty()) { | |
| 80 | + public ImSensitiveWordDO getSensitiveWord(Long id) { | |
| 81 | + return sensitiveWordMapper.selectById(id); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Override | |
| 85 | + public Long createSensitiveWord(ImSensitiveWordSaveReqVO reqVO) { | |
| 86 | + // 1. 校验唯一 | |
| 87 | + validateWordUnique(null, reqVO.getWord()); | |
| 88 | + | |
| 89 | + // 2.1 入库 | |
| 90 | + ImSensitiveWordDO word = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class); | |
| 91 | + sensitiveWordMapper.insert(word); | |
| 92 | + // TODO @AI:houbb 这个注释有点奇怪! | |
| 93 | + // 2.2 触发 houbb 词库 reload | |
| 94 | + reloadSensitiveWords(); | |
| 95 | + return word.getId(); | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Override | |
| 99 | + public void updateSensitiveWord(ImSensitiveWordSaveReqVO reqVO) { | |
| 100 | + // 1.1 校验存在 | |
| 101 | + validateSensitiveWordExists(reqVO.getId()); | |
| 102 | + // 1.2 校验唯一(排除自身) | |
| 103 | + validateWordUnique(reqVO.getId(), reqVO.getWord()); | |
| 104 | + | |
| 105 | + // 2.1 更新 | |
| 106 | + ImSensitiveWordDO updateObj = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class); | |
| 107 | + sensitiveWordMapper.updateById(updateObj); | |
| 108 | + // 2.2 TODO @AI:houbb 这个注释有点奇怪! | |
| 109 | + // 2.2 触发 houbb 词库 reload | |
| 110 | + reloadSensitiveWords(); | |
| 111 | + } | |
| 112 | + | |
| 113 | + @Override | |
| 114 | + public void deleteSensitiveWord(Long id) { | |
| 115 | + // 1. 校验存在 | |
| 116 | + validateSensitiveWordExists(id); | |
| 117 | + | |
| 118 | + // 2.1 删除 | |
| 119 | + sensitiveWordMapper.deleteById(id); | |
| 120 | + // 2.2 触发 houbb 词库 reload | |
| 121 | + reloadSensitiveWords(); | |
| 122 | + } | |
| 123 | + | |
| 124 | + | |
| 125 | + @Override | |
| 126 | + public void deleteSensitiveWordList(List<Long> ids) { | |
| 127 | + if (CollUtil.isEmpty(ids)) { | |
| 128 | + return; | |
| 129 | + } | |
| 130 | + | |
| 131 | + // 1. 删除 | |
| 132 | + sensitiveWordMapper.deleteByIds(ids); | |
| 133 | + // 触发 houbb 词库 reload | |
| 134 | + reloadSensitiveWords(); | |
| 135 | + } | |
| 136 | + | |
| 137 | + private void validateSensitiveWordExists(Long id) { | |
| 138 | + if (sensitiveWordMapper.selectById(id) == null) { | |
| 139 | + throw exception(SENSITIVE_WORD_NOT_EXISTS); | |
| 140 | + } | |
| 141 | + } | |
| 142 | + | |
| 143 | + /** | |
| 144 | + * 校验敏感词唯一(修改时排除自身) | |
| 145 | + */ | |
| 146 | + private void validateWordUnique(Long id, String word) { | |
| 147 | + ImSensitiveWordDO exist = sensitiveWordMapper.selectByWord(word); | |
| 148 | + if (exist == null) { | |
| 67 | 149 | return; |
| 68 | 150 | } |
| 69 | - // 内存匹配 | |
| 70 | - for (String word : sensitiveWords) { | |
| 71 | - if (text.contains(word)) { | |
| 72 | - throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED); | |
| 73 | - } | |
| 151 | + // TODO @AI:是不是 id 不用判断 | |
| 152 | + if (id == null || ObjUtil.notEqual(exist.getId(), id)) { | |
| 153 | + throw exception(SENSITIVE_WORD_DUPLICATED, word); | |
| 74 | 154 | } |
| 75 | 155 | } |
| 76 | 156 | ... | ... |