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,6 +62,14 @@ | ||
| 62 | <artifactId>yudao-spring-boot-starter-excel</artifactId> | 62 | <artifactId>yudao-spring-boot-starter-excel</artifactId> |
| 63 | </dependency> | 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 | <!-- Test 测试相关 --> | 73 | <!-- Test 测试相关 --> |
| 66 | <dependency> | 74 | <dependency> |
| 67 | <groupId>cn.iocoder.boot</groupId> | 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 | package cn.iocoder.yudao.module.im.dal.mysql.friend; | 1 | package cn.iocoder.yudao.module.im.dal.mysql.friend; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; | ||
| 5 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 6 | import org.apache.ibatis.annotations.Mapper; | 8 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 9 | ||
| @@ -43,4 +45,14 @@ public interface ImFriendMapper extends BaseMapperX<ImFriendDO> { | @@ -43,4 +45,14 @@ public interface ImFriendMapper extends BaseMapperX<ImFriendDO> { | ||
| 43 | .eq(ImFriendDO::getStatus, status)); | 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 | package cn.iocoder.yudao.module.im.dal.mysql.group; | 1 | package cn.iocoder.yudao.module.im.dal.mysql.group; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 5 | import org.apache.ibatis.annotations.Mapper; | 8 | import org.apache.ibatis.annotations.Mapper; |
| 6 | 9 | ||
| @@ -11,4 +14,15 @@ import org.apache.ibatis.annotations.Mapper; | @@ -11,4 +14,15 @@ import org.apache.ibatis.annotations.Mapper; | ||
| 11 | */ | 14 | */ |
| 12 | @Mapper | 15 | @Mapper |
| 13 | public interface ImGroupMapper extends BaseMapperX<ImGroupDO> { | 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 | \ No newline at end of file | 29 | \ No newline at end of file |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
| 1 | package cn.iocoder.yudao.module.im.dal.mysql.group; | 1 | package cn.iocoder.yudao.module.im.dal.mysql.group; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 3 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 8 | +import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||
| 7 | import org.apache.ibatis.annotations.Mapper; | 9 | import org.apache.ibatis.annotations.Mapper; |
| 8 | 10 | ||
| 9 | import java.time.LocalDateTime; | 11 | import java.time.LocalDateTime; |
| 10 | import java.util.Collection; | 12 | import java.util.Collection; |
| 13 | +import java.util.Collections; | ||
| 14 | +import java.util.HashMap; | ||
| 11 | import java.util.List; | 15 | import java.util.List; |
| 16 | +import java.util.Map; | ||
| 12 | 17 | ||
| 13 | /** | 18 | /** |
| 14 | * IM 群成员 Mapper | 19 | * IM 群成员 Mapper |
| @@ -78,4 +83,25 @@ public interface ImGroupMemberMapper extends BaseMapperX<ImGroupMemberDO> { | @@ -78,4 +83,25 @@ public interface ImGroupMemberMapper extends BaseMapperX<ImGroupMemberDO> { | ||
| 78 | .eq(ImGroupMemberDO::getStatus, oldStatus)); | 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 | \ No newline at end of file | 108 | \ No newline at end of file |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java
| 1 | package cn.iocoder.yudao.module.im.dal.mysql.message; | 1 | package cn.iocoder.yudao.module.im.dal.mysql.message; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| 7 | import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; | 9 | import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; |
| 8 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; | 10 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; |
| @@ -113,4 +115,14 @@ public interface ImGroupMessageMapper extends BaseMapperX<ImGroupMessageDO> { | @@ -113,4 +115,14 @@ public interface ImGroupMessageMapper extends BaseMapperX<ImGroupMessageDO> { | ||
| 113 | .ne(ImGroupMessageDO::getStatus, ImMessageStatusEnum.RECALL.getStatus())); | 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 | package cn.iocoder.yudao.module.im.dal.mysql.message; | 1 | package cn.iocoder.yudao.module.im.dal.mysql.message; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| 7 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; | 9 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; |
| 8 | import org.apache.ibatis.annotations.Mapper; | 10 | import org.apache.ibatis.annotations.Mapper; |
| @@ -86,4 +88,14 @@ public interface ImPrivateMessageMapper extends BaseMapperX<ImPrivateMessageDO> | @@ -86,4 +88,14 @@ public interface ImPrivateMessageMapper extends BaseMapperX<ImPrivateMessageDO> | ||
| 86 | .eq(ImPrivateMessageDO::getStatus, whereStatus)); | 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 | package cn.iocoder.yudao.module.im.dal.mysql.sensitiveword; | 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 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; | ||
| 6 | import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; |
| 7 | import org.apache.ibatis.annotations.Mapper; | 8 | import org.apache.ibatis.annotations.Mapper; |
| 8 | 9 | ||
| @@ -16,10 +17,22 @@ import java.util.List; | @@ -16,10 +17,22 @@ import java.util.List; | ||
| 16 | @Mapper | 17 | @Mapper |
| 17 | public interface ImSensitiveWordMapper extends BaseMapperX<ImSensitiveWordDO> { | 18 | public interface ImSensitiveWordMapper extends BaseMapperX<ImSensitiveWordDO> { |
| 18 | 19 | ||
| 19 | - // DONE @AI:已改名为 selectListByStatus,status 作为参数 | ||
| 20 | default List<ImSensitiveWordDO> selectListByStatus(Integer status) { | 20 | default List<ImSensitiveWordDO> selectListByStatus(Integer status) { |
| 21 | return selectList(new LambdaQueryWrapperX<ImSensitiveWordDO>() | 21 | return selectList(new LambdaQueryWrapperX<ImSensitiveWordDO>() |
| 22 | .eq(ImSensitiveWordDO::getStatus, status)); | 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,4 +34,8 @@ public interface ErrorCodeConstants { | ||
| 34 | ErrorCode FRIEND_NOT_FRIEND = new ErrorCode(1_040_600_001, "对方不是您的好友"); | 34 | ErrorCode FRIEND_NOT_FRIEND = new ErrorCode(1_040_600_001, "对方不是您的好友"); |
| 35 | ErrorCode FRIEND_ADD_SELF = new ErrorCode(1_040_600_002, "不允许添加自己为好友"); | 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 | package cn.iocoder.yudao.module.im.service.friend; | 1 | package cn.iocoder.yudao.module.im.service.friend; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 3 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 5 | 8 | ||
| 6 | import java.util.Collection; | 9 | import java.util.Collection; |
| @@ -84,5 +87,11 @@ public interface ImFriendService { | @@ -84,5 +87,11 @@ public interface ImFriendService { | ||
| 84 | */ | 87 | */ |
| 85 | void updateFriend(Long userId, ImFriendUpdateReqVO reqVO); | 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 | package cn.iocoder.yudao.module.im.service.friend; | 1 | package cn.iocoder.yudao.module.im.service.friend; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 3 | import cn.hutool.extra.spring.SpringUtil; | 4 | import cn.hutool.extra.spring.SpringUtil; |
| 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 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 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; | 12 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 7 | import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; | 13 | import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; |
| 8 | import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; | 14 | import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; |
| 9 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; | 15 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 10 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; | 16 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; |
| 11 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | 17 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 18 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | ||
| 12 | import jakarta.annotation.Resource; | 19 | import jakarta.annotation.Resource; |
| 13 | import lombok.extern.slf4j.Slf4j; | 20 | import lombok.extern.slf4j.Slf4j; |
| 14 | import org.springframework.cache.annotation.CacheEvict; | 21 | import org.springframework.cache.annotation.CacheEvict; |
| @@ -20,11 +27,10 @@ import org.springframework.transaction.annotation.Transactional; | @@ -20,11 +27,10 @@ import org.springframework.transaction.annotation.Transactional; | ||
| 20 | import org.springframework.validation.annotation.Validated; | 27 | import org.springframework.validation.annotation.Validated; |
| 21 | 28 | ||
| 22 | import java.time.LocalDateTime; | 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 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 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 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND; | 34 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND; |
| 29 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF; | 35 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF; |
| 30 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; | 36 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; |
| @@ -181,4 +187,28 @@ public class ImFriendServiceImpl implements ImFriendService { | @@ -181,4 +187,28 @@ public class ImFriendServiceImpl implements ImFriendService { | ||
| 181 | return SpringUtil.getBean(getClass()); | 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 | package cn.iocoder.yudao.module.im.service.group; | 1 | package cn.iocoder.yudao.module.im.service.group; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; | 5 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 5 | import jakarta.validation.Valid; | 6 | import jakarta.validation.Valid; |
| 6 | 7 | ||
| 7 | import java.time.LocalDateTime; | 8 | import java.time.LocalDateTime; |
| 8 | import java.util.Collection; | 9 | import java.util.Collection; |
| 9 | import java.util.List; | 10 | import java.util.List; |
| 11 | +import java.util.Map; | ||
| 10 | 12 | ||
| 11 | /** | 13 | /** |
| 12 | * 群成员 Service 接口 | 14 | * 群成员 Service 接口 |
| @@ -24,6 +26,15 @@ public interface ImGroupMemberService { | @@ -24,6 +26,15 @@ public interface ImGroupMemberService { | ||
| 24 | ImGroupMemberDO getGroupMember(Long id); | 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 | * 根据群组 id 查询群成员(包含所有状态) | 38 | * 根据群组 id 查询群成员(包含所有状态) |
| 28 | * | 39 | * |
| 29 | * @param groupId 群组id | 40 | * @param groupId 群组id |
| @@ -137,4 +148,22 @@ public interface ImGroupMemberService { | @@ -137,4 +148,22 @@ public interface ImGroupMemberService { | ||
| 137 | */ | 148 | */ |
| 138 | void removeGroupMembersByGroupId(Long groupId); | 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,12 +2,16 @@ package cn.iocoder.yudao.module.im.service.group; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 5 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | ||
| 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; | 9 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 8 | import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; | 10 | import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; |
| 9 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; | 11 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 10 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; | 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 | import jakarta.annotation.Resource; | 15 | import jakarta.annotation.Resource; |
| 12 | import lombok.extern.slf4j.Slf4j; | 16 | import lombok.extern.slf4j.Slf4j; |
| 13 | import org.springframework.cache.annotation.CacheEvict; | 17 | import org.springframework.cache.annotation.CacheEvict; |
| @@ -17,14 +21,10 @@ import org.springframework.stereotype.Service; | @@ -17,14 +21,10 @@ import org.springframework.stereotype.Service; | ||
| 17 | import org.springframework.validation.annotation.Validated; | 21 | import org.springframework.validation.annotation.Validated; |
| 18 | 22 | ||
| 19 | import java.time.LocalDateTime; | 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 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 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 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MEMBER_IDS; | 28 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MEMBER_IDS; |
| 29 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; | 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,12 +44,20 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { | ||
| 44 | @Resource | 44 | @Resource |
| 45 | private ImWebSocketService webSocketService; | 45 | private ImWebSocketService webSocketService; |
| 46 | 46 | ||
| 47 | + @Resource | ||
| 48 | + private AdminUserApi adminUserApi; | ||
| 49 | + | ||
| 47 | @Override | 50 | @Override |
| 48 | public ImGroupMemberDO getGroupMember(Long id) { | 51 | public ImGroupMemberDO getGroupMember(Long id) { |
| 49 | return groupMemberMapper.selectById(id); | 52 | return groupMemberMapper.selectById(id); |
| 50 | } | 53 | } |
| 51 | 54 | ||
| 52 | @Override | 55 | @Override |
| 56 | + public ImGroupMemberDO getGroupMember(Long groupId, Long userId) { | ||
| 57 | + return groupMemberMapper.selectByGroupIdAndUserId(groupId, userId); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + @Override | ||
| 53 | public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) { | 61 | public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) { |
| 54 | return groupMemberMapper.selectListByGroupId(groupId); | 62 | return groupMemberMapper.selectListByGroupId(groupId); |
| 55 | } | 63 | } |
| @@ -209,4 +217,30 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { | @@ -209,4 +217,30 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { | ||
| 209 | .setQuitTime(LocalDateTime.now())); | 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 | package cn.iocoder.yudao.module.im.service.group; | 1 | package cn.iocoder.yudao.module.im.service.group; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 3 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; | 4 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; |
| 4 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; | 5 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| 5 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; | 6 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; |
| 6 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; | 11 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 8 | import jakarta.validation.Valid; | 12 | import jakarta.validation.Valid; |
| 9 | 13 | ||
| 14 | +import java.util.Collection; | ||
| 10 | import java.util.List; | 15 | import java.util.List; |
| 16 | +import java.util.Map; | ||
| 11 | 17 | ||
| 12 | /** | 18 | /** |
| 13 | * 群 Service 接口 | 19 | * 群 Service 接口 |
| @@ -59,6 +65,14 @@ public interface ImGroupService { | @@ -59,6 +65,14 @@ public interface ImGroupService { | ||
| 59 | ImGroupDO getGroup(Long id); | 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 | * @param groupId 群编号 | 78 | * @param groupId 群编号 |
| @@ -122,4 +136,30 @@ public interface ImGroupService { | @@ -122,4 +136,30 @@ public interface ImGroupService { | ||
| 122 | */ | 136 | */ |
| 123 | void removeGroupMember(Long userId, @Valid ImGroupMemberRemoveReqVO removeReqVO); | 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,11 +7,16 @@ import cn.hutool.core.util.ObjUtil; | ||
| 7 | import cn.hutool.core.util.StrUtil; | 7 | import cn.hutool.core.util.StrUtil; |
| 8 | import cn.hutool.extra.spring.SpringUtil; | 8 | import cn.hutool.extra.spring.SpringUtil; |
| 9 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 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 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 12 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; | 13 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; |
| 12 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; | 14 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| 13 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; | 15 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; |
| 14 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; | 20 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 16 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; | 21 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 17 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; | 22 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| @@ -35,6 +40,7 @@ import java.util.*; | @@ -35,6 +40,7 @@ import java.util.*; | ||
| 35 | import java.util.stream.Collectors; | 40 | import java.util.stream.Collectors; |
| 36 | 41 | ||
| 37 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 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 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | 44 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 39 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP; | 45 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP; |
| 40 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; | 46 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; |
| @@ -256,6 +262,14 @@ public class ImGroupServiceImpl implements ImGroupService { | @@ -256,6 +262,14 @@ public class ImGroupServiceImpl implements ImGroupService { | ||
| 256 | } | 262 | } |
| 257 | 263 | ||
| 258 | @Override | 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 | public List<ImGroupDO> getMyGroupList(Long userId) { | 273 | public List<ImGroupDO> getMyGroupList(Long userId) { |
| 260 | // 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态) | 274 | // 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态) |
| 261 | List<ImGroupMemberDO> members = groupMemberService.getActiveGroupMemberListByUserId(userId); | 275 | List<ImGroupMemberDO> members = groupMemberService.getActiveGroupMemberListByUserId(userId); |
| @@ -271,6 +285,53 @@ public class ImGroupServiceImpl implements ImGroupService { | @@ -271,6 +285,53 @@ public class ImGroupServiceImpl implements ImGroupService { | ||
| 271 | return groupMapper.selectByIds(groupIds); | 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 | private ImGroupServiceImpl getSelf() { | 335 | private ImGroupServiceImpl getSelf() { |
| 275 | return SpringUtil.getBean(getClass()); | 336 | return SpringUtil.getBean(getClass()); |
| 276 | } | 337 | } |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
| 1 | package cn.iocoder.yudao.module.im.service.message; | 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 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; | 6 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; |
| 4 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; | 7 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; |
| 5 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| @@ -112,5 +115,17 @@ public interface ImGroupMessageService { | @@ -112,5 +115,17 @@ public interface ImGroupMessageService { | ||
| 112 | */ | 115 | */ |
| 113 | void sendTipGroupMessage(Long senderId, Long groupId, Set<Long> memberUserIds, String content); | 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,13 +6,17 @@ import cn.hutool.core.util.IdUtil; | ||
| 6 | import cn.hutool.core.util.ObjUtil; | 6 | import cn.hutool.core.util.ObjUtil; |
| 7 | import cn.hutool.extra.spring.SpringUtil; | 7 | import cn.hutool.extra.spring.SpringUtil; |
| 8 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 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 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 11 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 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 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; | 15 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; |
| 12 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; | 18 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 14 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; | 19 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| 15 | -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; | ||
| 16 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; | 20 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; |
| 17 | import cn.iocoder.yudao.module.im.dal.redis.message.GroupMessageReadRedisDAO; | 21 | import cn.iocoder.yudao.module.im.dal.redis.message.GroupMessageReadRedisDAO; |
| 18 | import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; | 22 | import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; |
| @@ -23,6 +27,9 @@ import cn.iocoder.yudao.module.im.service.group.ImGroupService; | @@ -23,6 +27,9 @@ import cn.iocoder.yudao.module.im.service.group.ImGroupService; | ||
| 23 | import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; | 27 | import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; |
| 24 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; | 28 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 25 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; | 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 | import jakarta.annotation.Resource; | 33 | import jakarta.annotation.Resource; |
| 27 | import lombok.extern.slf4j.Slf4j; | 34 | import lombok.extern.slf4j.Slf4j; |
| 28 | import org.springframework.scheduling.annotation.Async; | 35 | import org.springframework.scheduling.annotation.Async; |
| @@ -70,6 +77,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | @@ -70,6 +77,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | ||
| 70 | @Resource | 77 | @Resource |
| 71 | private ImWebSocketService imWebSocketService; | 78 | private ImWebSocketService imWebSocketService; |
| 72 | 79 | ||
| 80 | + @Resource | ||
| 81 | + private AdminUserApi adminUserApi; | ||
| 82 | + | ||
| 73 | @Override | 83 | @Override |
| 74 | public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) { | 84 | public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) { |
| 75 | // 1.1 幂等校验:根据 senderId + clientMessageId 查重 | 85 | // 1.1 幂等校验:根据 senderId + clientMessageId 查重 |
| @@ -542,6 +552,35 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | @@ -542,6 +552,35 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { | ||
| 542 | ImGroupMessageDTO.ofSend(tipMessage)); | 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 | private ImGroupMessageServiceImpl getSelf() { | 584 | private ImGroupMessageServiceImpl getSelf() { |
| 546 | return SpringUtil.getBean(getClass()); | 585 | return SpringUtil.getBean(getClass()); |
| 547 | } | 586 | } |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
| 1 | package cn.iocoder.yudao.module.im.service.message; | 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 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; | 6 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; |
| 4 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; | 7 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 5 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| @@ -85,4 +88,16 @@ public interface ImPrivateMessageService { | @@ -85,4 +88,16 @@ public interface ImPrivateMessageService { | ||
| 85 | */ | 88 | */ |
| 86 | void sendTipPrivateMessage(Long senderId, Long receiverId, String content); | 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 | package cn.iocoder.yudao.module.im.service.message; | 1 | package cn.iocoder.yudao.module.im.service.message; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 3 | import cn.hutool.core.lang.Assert; | 4 | import cn.hutool.core.lang.Assert; |
| 4 | import cn.hutool.core.util.IdUtil; | 5 | import cn.hutool.core.util.IdUtil; |
| 5 | import cn.hutool.core.util.ObjUtil; | 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 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 9 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 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 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; | 13 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; |
| 9 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; | 14 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 10 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; | 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,6 +21,8 @@ import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; | ||
| 16 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; | 21 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 17 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; | 22 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; |
| 18 | import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; | 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 | import jakarta.annotation.Resource; | 26 | import jakarta.annotation.Resource; |
| 20 | import lombok.extern.slf4j.Slf4j; | 27 | import lombok.extern.slf4j.Slf4j; |
| 21 | import org.springframework.stereotype.Service; | 28 | import org.springframework.stereotype.Service; |
| @@ -23,9 +30,13 @@ import org.springframework.transaction.annotation.Transactional; | @@ -23,9 +30,13 @@ import org.springframework.transaction.annotation.Transactional; | ||
| 23 | import org.springframework.validation.annotation.Validated; | 30 | import org.springframework.validation.annotation.Validated; |
| 24 | 31 | ||
| 25 | import java.time.LocalDateTime; | 32 | import java.time.LocalDateTime; |
| 33 | +import java.util.HashSet; | ||
| 26 | import java.util.List; | 34 | import java.util.List; |
| 35 | +import java.util.Map; | ||
| 36 | +import java.util.Set; | ||
| 27 | 37 | ||
| 28 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 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 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; | 40 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; |
| 30 | import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; | 41 | import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; |
| 31 | 42 | ||
| @@ -50,6 +61,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { | @@ -50,6 +61,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { | ||
| 50 | @Resource | 61 | @Resource |
| 51 | private ImWebSocketService imWebSocketService; | 62 | private ImWebSocketService imWebSocketService; |
| 52 | 63 | ||
| 64 | + @Resource | ||
| 65 | + private AdminUserApi adminUserApi; | ||
| 66 | + | ||
| 53 | @Override | 67 | @Override |
| 54 | public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) { | 68 | public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) { |
| 55 | // 1.1 幂等校验:根据 senderId + clientMessageId 查重 | 69 | // 1.1 幂等校验:根据 senderId + clientMessageId 查重 |
| @@ -167,6 +181,35 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { | @@ -167,6 +181,35 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { | ||
| 167 | return privateMessageMapper.selectHistoryList(userId, reqVO.getReceiverId(), reqVO.getMaxId(), reqVO.getLimit()); | 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 | @Override | 213 | @Override |
| 171 | public void sendTipPrivateMessage(Long senderId, Long receiverId, String content) { | 214 | public void sendTipPrivateMessage(Long senderId, Long receiverId, String content) { |
| 172 | // 1. 插入 TIP_TEXT 消息 | 215 | // 1. 插入 TIP_TEXT 消息 |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java
| 1 | package cn.iocoder.yudao.module.im.service.sensitiveword; | 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 | * IM 敏感词 Service 接口 | 12 | * IM 敏感词 Service 接口 |
| 5 | * | 13 | * |
| @@ -14,4 +22,36 @@ public interface ImSensitiveWordService { | @@ -14,4 +22,36 @@ public interface ImSensitiveWordService { | ||
| 14 | */ | 22 | */ |
| 15 | void validateText(String text); | 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 | package cn.iocoder.yudao.module.im.service.sensitiveword; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; | 10 | import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; |
| 5 | import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper; | 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 | import jakarta.annotation.Resource; | 13 | import jakarta.annotation.Resource; |
| 8 | import lombok.extern.slf4j.Slf4j; | 14 | import lombok.extern.slf4j.Slf4j; |
| 9 | -import org.springframework.scheduling.annotation.Scheduled; | ||
| 10 | import org.springframework.stereotype.Service; | 15 | import org.springframework.stereotype.Service; |
| 11 | import org.springframework.validation.annotation.Validated; | 16 | import org.springframework.validation.annotation.Validated; |
| 12 | 17 | ||
| 13 | -import java.util.Collections; | ||
| 14 | import java.util.List; | 18 | import java.util.List; |
| 15 | -import java.util.stream.Collectors; | ||
| 16 | 19 | ||
| 17 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 20 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 18 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED; | 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 | * IM 敏感词 Service 实现类 | 26 | * IM 敏感词 Service 实现类 |
| 27 | + * <p> | ||
| 28 | + * 词库匹配交给 houbb sensitive-word 库(trie 树 + 全/半角 / 大小写 / 繁简体 / 数字风格规范化) | ||
| 23 | * | 29 | * |
| 24 | * @author 芋道源码 | 30 | * @author 芋道源码 |
| 25 | */ | 31 | */ |
| @@ -29,48 +35,122 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSIT | @@ -29,48 +35,122 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSIT | ||
| 29 | public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { | 35 | public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { |
| 30 | 36 | ||
| 31 | @Resource | 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 | @Override | 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 | return; | 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 |