Commit bda892277df5e14541f5c2426b20868b4d882d8e
1 parent
bb9a2b53
fix(im):加固管理端入参和统计边界
- 校验敏感词、表情包、表情项状态枚举和批量删除数量上限 - 限制私聊、群聊增量拉取 size 最小值 - 收紧表情包项 Mapper 的 in 查询并在 Service 层处理空好友集合 - 兼容统计聚合数值类型并明确当前有效群总数口径 - 固定 LiveKit token 签名编码和 IM web 包说明 - 修正群禁言权限错误码顺序并补充封禁、踢人、禁言回归测试 - 流转 IM bug 文档并记录本轮不修项语义
Showing
21 changed files
with
147 additions
and
31 deletions
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.java
| ... | ... | @@ -13,6 +13,7 @@ import io.swagger.v3.oas.annotations.Parameter; |
| 13 | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 14 | 14 | import jakarta.annotation.Resource; |
| 15 | 15 | import jakarta.validation.Valid; |
| 16 | +import jakarta.validation.constraints.Size; | |
| 16 | 17 | import org.springframework.security.access.prepost.PreAuthorize; |
| 17 | 18 | import org.springframework.validation.annotation.Validated; |
| 18 | 19 | import org.springframework.web.bind.annotation.*; |
| ... | ... | @@ -58,7 +59,8 @@ public class ImFacePackItemManagerController { |
| 58 | 59 | @Operation(summary = "批量删除表情") |
| 59 | 60 | @Parameter(name = "ids", description = "编号列表", required = true) |
| 60 | 61 | @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:delete')") |
| 61 | - public CommonResult<Boolean> deleteFacePackItemList(@RequestParam("ids") List<Long> ids) { | |
| 62 | + public CommonResult<Boolean> deleteFacePackItemList(@RequestParam("ids") | |
| 63 | + @Size(max = 100, message = "批量删除最多 100 条") List<Long> ids) { | |
| 62 | 64 | facePackItemService.deleteFacePackItemList(ids); |
| 63 | 65 | return success(true); |
| 64 | 66 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.java
| ... | ... | @@ -13,6 +13,7 @@ import io.swagger.v3.oas.annotations.Parameter; |
| 13 | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 14 | 14 | import jakarta.annotation.Resource; |
| 15 | 15 | import jakarta.validation.Valid; |
| 16 | +import jakarta.validation.constraints.Size; | |
| 16 | 17 | import org.springframework.security.access.prepost.PreAuthorize; |
| 17 | 18 | import org.springframework.validation.annotation.Validated; |
| 18 | 19 | import org.springframework.web.bind.annotation.*; |
| ... | ... | @@ -58,7 +59,8 @@ public class ImFacePackManagerController { |
| 58 | 59 | @Operation(summary = "批量删除表情包") |
| 59 | 60 | @Parameter(name = "ids", description = "编号列表", required = true) |
| 60 | 61 | @PreAuthorize("@ss.hasPermission('im:manager:face-pack:delete')") |
| 61 | - public CommonResult<Boolean> deleteFacePackList(@RequestParam("ids") List<Long> ids) { | |
| 62 | + public CommonResult<Boolean> deleteFacePackList(@RequestParam("ids") | |
| 63 | + @Size(max = 100, message = "批量删除最多 100 条") List<Long> ids) { | |
| 62 | 64 | facePackService.deleteFacePackList(ids); |
| 63 | 65 | return success(true); |
| 64 | 66 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemPageReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 5 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 4 | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 7 | import lombok.Data; |
| 6 | 8 | import lombok.EqualsAndHashCode; |
| ... | ... | @@ -17,6 +19,7 @@ public class ImFacePackItemPageReqVO extends PageParam { |
| 17 | 19 | private String name; |
| 18 | 20 | |
| 19 | 21 | @Schema(description = "状态", example = "0") |
| 22 | + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") | |
| 20 | 23 | private Integer status; // 参见 CommonStatusEnum 枚举类 |
| 21 | 24 | |
| 22 | 25 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemSaveReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 4 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 3 | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 6 | import jakarta.validation.constraints.Max; |
| 5 | 7 | import jakarta.validation.constraints.Min; |
| ... | ... | @@ -47,6 +49,7 @@ public class ImFacePackItemSaveReqVO { |
| 47 | 49 | |
| 48 | 50 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| 49 | 51 | @NotNull(message = "状态不能为空") |
| 52 | + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") | |
| 50 | 53 | private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) |
| 51 | 54 | |
| 52 | 55 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackPageReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 5 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 4 | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 7 | import lombok.Data; |
| 6 | 8 | import lombok.EqualsAndHashCode; |
| ... | ... | @@ -19,6 +21,7 @@ public class ImFacePackPageReqVO extends PageParam { |
| 19 | 21 | private String name; |
| 20 | 22 | |
| 21 | 23 | @Schema(description = "状态", example = "0") |
| 24 | + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") | |
| 22 | 25 | private Integer status; // 参见 CommonStatusEnum 枚举类 |
| 23 | 26 | |
| 24 | 27 | @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackSaveReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 4 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 3 | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 6 | import jakarta.validation.constraints.NotBlank; |
| 5 | 7 | import jakarta.validation.constraints.NotNull; |
| ... | ... | @@ -28,6 +30,7 @@ public class ImFacePackSaveReqVO { |
| 28 | 30 | |
| 29 | 31 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| 30 | 32 | @NotNull(message = "状态不能为空") |
| 33 | + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") | |
| 31 | 34 | private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) |
| 32 | 35 | |
| 33 | 36 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java
| ... | ... | @@ -18,6 +18,7 @@ import io.swagger.v3.oas.annotations.Parameter; |
| 18 | 18 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 19 | 19 | import jakarta.annotation.Resource; |
| 20 | 20 | import jakarta.validation.Valid; |
| 21 | +import jakarta.validation.constraints.Size; | |
| 21 | 22 | import org.springframework.security.access.prepost.PreAuthorize; |
| 22 | 23 | import org.springframework.validation.annotation.Validated; |
| 23 | 24 | import org.springframework.web.bind.annotation.*; |
| ... | ... | @@ -67,7 +68,9 @@ public class ImSensitiveWordManagerController { |
| 67 | 68 | @Operation(summary = "批量删除敏感词") |
| 68 | 69 | @Parameter(name = "ids", description = "编号列表", required = true) |
| 69 | 70 | @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')") |
| 70 | - public CommonResult<Boolean> deleteSensitiveWordList(@RequestParam("ids") List<Long> ids) { | |
| 71 | + public CommonResult<Boolean> deleteSensitiveWordList( | |
| 72 | + @RequestParam("ids") | |
| 73 | + @Size(max = 100, message = "批量删除最多 100 条") List<Long> ids) { | |
| 71 | 74 | sensitiveWordService.deleteSensitiveWordList(ids); |
| 72 | 75 | return success(true); |
| 73 | 76 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 5 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 4 | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 7 | import lombok.Data; |
| 6 | 8 | import lombok.EqualsAndHashCode; |
| ... | ... | @@ -19,6 +21,7 @@ public class ImSensitiveWordPageReqVO extends PageParam { |
| 19 | 21 | private String word; |
| 20 | 22 | |
| 21 | 23 | @Schema(description = "状态", example = "0") |
| 24 | + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") | |
| 22 | 25 | private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) |
| 23 | 26 | |
| 24 | 27 | @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 4 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 3 | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 6 | import jakarta.validation.constraints.NotBlank; |
| 5 | 7 | import jakarta.validation.constraints.NotNull; |
| ... | ... | @@ -20,6 +22,7 @@ public class ImSensitiveWordSaveReqVO { |
| 20 | 22 | |
| 21 | 23 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| 22 | 24 | @NotNull(message = "状态不能为空") |
| 25 | + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") | |
| 23 | 26 | private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) |
| 24 | 27 | |
| 25 | 28 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java
| ... | ... | @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.Parameter; |
| 12 | 12 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 13 | 13 | import jakarta.annotation.Resource; |
| 14 | 14 | import jakarta.validation.Valid; |
| 15 | +import jakarta.validation.constraints.Min; | |
| 15 | 16 | import org.springframework.validation.annotation.Validated; |
| 16 | 17 | import org.springframework.web.bind.annotation.*; |
| 17 | 18 | |
| ... | ... | @@ -40,8 +41,9 @@ public class ImGroupMessageController { |
| 40 | 41 | @Operation(summary = "拉取群聊消息(增量)") |
| 41 | 42 | @Parameter(name = "minId", description = "最小消息 id", required = true, example = "0") |
| 42 | 43 | @Parameter(name = "size", description = "拉取数量", required = true, example = "100") |
| 43 | - public CommonResult<List<ImGroupMessageRespVO>> pullGroupMessageList(@RequestParam("minId") Long minId, | |
| 44 | - @RequestParam("size") Integer size) { | |
| 44 | + public CommonResult<List<ImGroupMessageRespVO>> pullGroupMessageList( | |
| 45 | + @RequestParam("minId") Long minId, | |
| 46 | + @RequestParam("size") @Min(value = 1, message = "拉取数量最小值为 1") Integer size) { | |
| 45 | 47 | List<ImGroupMessageDO> messages = groupMessageService.pullGroupMessageList(getLoginUserId(), minId, size); |
| 46 | 48 | return success(BeanUtils.toBean(messages, ImGroupMessageRespVO.class)); |
| 47 | 49 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java
| ... | ... | @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.Parameter; |
| 12 | 12 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 13 | 13 | import jakarta.annotation.Resource; |
| 14 | 14 | import jakarta.validation.Valid; |
| 15 | +import jakarta.validation.constraints.Min; | |
| 15 | 16 | import org.springframework.validation.annotation.Validated; |
| 16 | 17 | import org.springframework.web.bind.annotation.*; |
| 17 | 18 | |
| ... | ... | @@ -41,8 +42,9 @@ public class ImPrivateMessageController { |
| 41 | 42 | @Operation(summary = "拉取私聊消息(增量)") |
| 42 | 43 | @Parameter(name = "minId", description = "最小消息 id", required = true, example = "0") |
| 43 | 44 | @Parameter(name = "size", description = "拉取数量", required = true, example = "100") |
| 44 | - public CommonResult<List<ImPrivateMessageRespVO>> pullPrivateMessageList(@RequestParam("minId") Long minId, | |
| 45 | - @RequestParam("size") Integer size) { | |
| 45 | + public CommonResult<List<ImPrivateMessageRespVO>> pullPrivateMessageList( | |
| 46 | + @RequestParam("minId") Long minId, | |
| 47 | + @RequestParam("size") @Min(value = 1, message = "拉取数量最小值为 1") Integer size) { | |
| 46 | 48 | List<ImPrivateMessageDO> messages = privateMessageService.pullPrivateMessageList(getLoginUserId(), minId, size); |
| 47 | 49 | return success(BeanUtils.toBean(messages, ImPrivateMessageRespVO.class)); |
| 48 | 50 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java
| ... | ... | @@ -20,7 +20,7 @@ public interface ImFacePackItemMapper extends BaseMapperX<ImFacePackItemDO> { |
| 20 | 20 | |
| 21 | 21 | default List<ImFacePackItemDO> selectListByPackIdsAndStatus(Collection<Long> packIds, Integer status) { |
| 22 | 22 | return selectList(new LambdaQueryWrapperX<ImFacePackItemDO>() |
| 23 | - .inIfPresent(ImFacePackItemDO::getPackId, packIds) | |
| 23 | + .in(ImFacePackItemDO::getPackId, packIds) | |
| 24 | 24 | .eq(ImFacePackItemDO::getStatus, status) |
| 25 | 25 | .orderByAsc(ImFacePackItemDO::getPackId) |
| 26 | 26 | .orderByAsc(ImFacePackItemDO::getSort) | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java
| ... | ... | @@ -73,7 +73,7 @@ public interface ImStatisticsManagerMapper { |
| 73 | 73 | // ==================== 群 ==================== |
| 74 | 74 | |
| 75 | 75 | /** |
| 76 | - * 群总数(im_group) | |
| 76 | + * 当前有效群总数 | |
| 77 | 77 | */ |
| 78 | 78 | @Select("SELECT COUNT(*) FROM im_group WHERE deleted = 0 AND status = 0") |
| 79 | 79 | Long selectTotalGroupCount(); | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java
| ... | ... | @@ -90,7 +90,7 @@ public class LiveKitClient { |
| 90 | 90 | .setNotBefore(new Date(nowSec * 1000)) |
| 91 | 91 | .setExpiresAt(new Date((nowSec + ttlSec) * 1000)) |
| 92 | 92 | .setPayload("video", video) |
| 93 | - .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes())); | |
| 93 | + .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes(StandardCharsets.UTF_8))); | |
| 94 | 94 | if (StrUtil.isNotEmpty(displayName)) { |
| 95 | 95 | jwt.setPayload("name", displayName); |
| 96 | 96 | } |
| ... | ... | @@ -219,7 +219,7 @@ public class LiveKitClient { |
| 219 | 219 | .setNotBefore(new Date(nowSec * 1000)) |
| 220 | 220 | .setExpiresAt(new Date((nowSec + ADMIN_TOKEN_TTL.getSeconds()) * 1000)) |
| 221 | 221 | .setPayload("video", MapUtil.of("roomAdmin", true)) |
| 222 | - .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes())) | |
| 222 | + .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes(StandardCharsets.UTF_8))) | |
| 223 | 223 | .sign(); |
| 224 | 224 | } |
| 225 | 225 | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/web/package-info.java
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.friend; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.util.BooleanUtil; |
| 4 | 5 | import cn.hutool.extra.spring.SpringUtil; |
| 5 | 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| ... | ... | @@ -28,6 +29,7 @@ import org.springframework.validation.annotation.Validated; |
| 28 | 29 | |
| 29 | 30 | import java.time.LocalDateTime; |
| 30 | 31 | import java.util.Collection; |
| 32 | +import java.util.Collections; | |
| 31 | 33 | import java.util.List; |
| 32 | 34 | |
| 33 | 35 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| ... | ... | @@ -100,6 +102,9 @@ public class ImFriendServiceImpl implements ImFriendService { |
| 100 | 102 | |
| 101 | 103 | @Override |
| 102 | 104 | public List<ImFriendDO> getActiveFriendList(Long userId, Collection<Long> friendUserIds) { |
| 105 | + if (CollUtil.isEmpty(friendUserIds)) { | |
| 106 | + return Collections.emptyList(); | |
| 107 | + } | |
| 103 | 108 | return friendMapper.selectListByUserIdAndFriendUserIdsAndStatus(userId, friendUserIds, |
| 104 | 109 | CommonStatusEnum.ENABLE.getStatus()); |
| 105 | 110 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
| ... | ... | @@ -669,6 +669,10 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 669 | 669 | * 三档分层禁言权限校验 |
| 670 | 670 | */ |
| 671 | 671 | private void validateMutePermission(ImGroupMemberDO operator, ImGroupMemberDO target) { |
| 672 | + // 普通成员不能禁言任何人 | |
| 673 | + if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole())) { | |
| 674 | + throw exception(GROUP_NOT_OWNER_OR_ADMIN); | |
| 675 | + } | |
| 672 | 676 | // 群主不可被禁言 |
| 673 | 677 | if (ImGroupMemberRoleEnum.isOwner(target.getRole())) { |
| 674 | 678 | throw exception(GROUP_MUTE_OWNER_DENIED); |
| ... | ... | @@ -677,10 +681,6 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 677 | 681 | if (ImGroupMemberRoleEnum.isAdmin(target.getRole()) && !ImGroupMemberRoleEnum.isOwner(operator.getRole())) { |
| 678 | 682 | throw exception(GROUP_MUTE_ADMIN_DENIED); |
| 679 | 683 | } |
| 680 | - // 普通成员不能禁言任何人 | |
| 681 | - if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole())) { | |
| 682 | - throw exception(GROUP_NOT_OWNER_OR_ADMIN); | |
| 683 | - } | |
| 684 | 684 | } |
| 685 | 685 | |
| 686 | 686 | private ImGroupServiceImpl getSelf() { | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImpl.java
| ... | ... | @@ -71,7 +71,7 @@ public class ImStatisticsManagerServiceImpl implements ImStatisticsManagerServic |
| 71 | 71 | List<Map<String, Object>> rows = statisticsMapper.selectGroupSizeDistribution(); |
| 72 | 72 | return convertMap(rows, |
| 73 | 73 | row -> (String) row.get("range"), |
| 74 | - row -> (Long) row.get("count")); | |
| 74 | + row -> Convert.toLong(row.get("count"))); | |
| 75 | 75 | } |
| 76 | 76 | |
| 77 | 77 | // ==================== 消息 ==================== |
| ... | ... | @@ -102,16 +102,16 @@ public class ImStatisticsManagerServiceImpl implements ImStatisticsManagerServic |
| 102 | 102 | public Map<Integer, Long> getMessageTypeCountMap(LocalDateTime beginTime, LocalDateTime endTime) { |
| 103 | 103 | List<Map<String, Object>> rows = statisticsMapper.selectMessageTypeDistribution(beginTime, endTime); |
| 104 | 104 | return convertMap(rows, |
| 105 | - row -> (Integer) row.get("type"), | |
| 106 | - row -> (Long) row.get("count")); | |
| 105 | + row -> Convert.toInt(row.get("type")), | |
| 106 | + row -> Convert.toLong(row.get("count"))); | |
| 107 | 107 | } |
| 108 | 108 | |
| 109 | 109 | @Override |
| 110 | 110 | public Map<Long, Long> getTopSenderCountMap(LocalDateTime beginTime, LocalDateTime endTime, int limit) { |
| 111 | 111 | List<Map<String, Object>> rows = statisticsMapper.selectTopSenders(beginTime, endTime, limit); |
| 112 | 112 | return convertMap(rows, |
| 113 | - row -> (Long) row.get("userId"), | |
| 114 | - row -> (Long) row.get("messageCount")); | |
| 113 | + row -> Convert.toLong(row.get("userId")), | |
| 114 | + row -> Convert.toLong(row.get("messageCount"))); | |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | 117 | /** |
| ... | ... | @@ -120,7 +120,7 @@ public class ImStatisticsManagerServiceImpl implements ImStatisticsManagerServic |
| 120 | 120 | private static Map<LocalDateTime, Long> toDailyCountMap(List<Map<String, Object>> rows) { |
| 121 | 121 | return convertMap(rows, |
| 122 | 122 | row -> Convert.convert(LocalDate.class, row.get("date")).atStartOfDay(), |
| 123 | - row -> (Long) row.get("count")); | |
| 123 | + row -> Convert.toLong(row.get("count"))); | |
| 124 | 124 | } |
| 125 | 125 | |
| 126 | 126 | } | ... | ... |
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java
| ... | ... | @@ -17,6 +17,7 @@ import org.mockito.Mock; |
| 17 | 17 | import org.springframework.dao.DuplicateKeyException; |
| 18 | 18 | |
| 19 | 19 | import java.time.LocalDateTime; |
| 20 | +import java.util.Collections; | |
| 20 | 21 | import java.util.List; |
| 21 | 22 | |
| 22 | 23 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; |
| ... | ... | @@ -213,4 +214,12 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { |
| 213 | 214 | assertEquals(2, result.size()); |
| 214 | 215 | } |
| 215 | 216 | |
| 217 | + @Test | |
| 218 | + public void testGetActiveFriendList_emptySkip() { | |
| 219 | + List<ImFriendDO> result = friendService.getActiveFriendList(1L, Collections.emptyList()); | |
| 220 | + | |
| 221 | + assertTrue(result.isEmpty()); | |
| 222 | + verify(imFriendMapper, never()).selectListByUserIdAndFriendUserIdsAndStatus(anyLong(), anyCollection(), anyInt()); | |
| 223 | + } | |
| 224 | + | |
| 216 | 225 | } | ... | ... |
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java
| ... | ... | @@ -7,10 +7,12 @@ import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 7 | 7 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupAdminAddReqVO; |
| 8 | 8 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupAdminRemoveReqVO; |
| 9 | 9 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; |
| 10 | +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupMuteMemberReqVO; | |
| 10 | 11 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupTransferOwnerReqVO; |
| 11 | 12 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| 12 | 13 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; |
| 13 | 14 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; |
| 15 | +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; | |
| 14 | 16 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 15 | 17 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 16 | 18 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| ... | ... | @@ -932,6 +934,76 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { |
| 932 | 934 | } |
| 933 | 935 | } |
| 934 | 936 | |
| 937 | + // ========== banGroup ========== | |
| 938 | + | |
| 939 | + @Test | |
| 940 | + public void testBanGroup_dissolved() { | |
| 941 | + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { | |
| 942 | + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) | |
| 943 | + .thenReturn(groupService); | |
| 944 | + | |
| 945 | + ImGroupDO group = ImGroupDO.builder().id(10L).status(CommonStatusEnum.DISABLE.getStatus()).build(); | |
| 946 | + when(groupMapper.selectById(10L)).thenReturn(group); | |
| 947 | + | |
| 948 | + ImGroupManagerBanReqVO reqVO = new ImGroupManagerBanReqVO(); | |
| 949 | + reqVO.setId(10L).setReason("违规"); | |
| 950 | + | |
| 951 | + ServiceException exception = assertThrows(ServiceException.class, | |
| 952 | + () -> groupService.banGroup(1L, reqVO)); | |
| 953 | + assertEquals(GROUP_DISSOLVED.getCode(), exception.getCode()); | |
| 954 | + verify(groupMapper, never()).updateById(any(ImGroupDO.class)); | |
| 955 | + verify(groupMessageService, never()).sendGroupMessage(anyLong(), any(ImGroupMessageSendDTO.class)); | |
| 956 | + } | |
| 957 | + } | |
| 958 | + | |
| 959 | + @Test | |
| 960 | + public void testBanGroup_alreadyBannedSkip() { | |
| 961 | + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { | |
| 962 | + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) | |
| 963 | + .thenReturn(groupService); | |
| 964 | + | |
| 965 | + ImGroupDO group = ImGroupDO.builder().id(10L).banned(true) | |
| 966 | + .status(CommonStatusEnum.ENABLE.getStatus()).build(); | |
| 967 | + when(groupMapper.selectById(10L)).thenReturn(group); | |
| 968 | + | |
| 969 | + ImGroupManagerBanReqVO reqVO = new ImGroupManagerBanReqVO(); | |
| 970 | + reqVO.setId(10L).setReason("违规"); | |
| 971 | + | |
| 972 | + groupService.banGroup(1L, reqVO); | |
| 973 | + | |
| 974 | + verify(groupMapper, never()).updateById(any(ImGroupDO.class)); | |
| 975 | + verify(groupMessageService, never()).sendGroupMessage(anyLong(), any(ImGroupMessageSendDTO.class)); | |
| 976 | + } | |
| 977 | + } | |
| 978 | + | |
| 979 | + // ========== muteMember ========== | |
| 980 | + | |
| 981 | + @Test | |
| 982 | + public void testMuteMember_normalCannotMuteAdmin() { | |
| 983 | + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { | |
| 984 | + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) | |
| 985 | + .thenReturn(groupService); | |
| 986 | + | |
| 987 | + ImGroupDO group = ImGroupDO.builder().id(10L).status(CommonStatusEnum.ENABLE.getStatus()).build(); | |
| 988 | + when(groupMapper.selectById(10L)).thenReturn(group); | |
| 989 | + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( | |
| 990 | + ImGroupMemberDO.builder().groupId(10L).userId(1L) | |
| 991 | + .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()); | |
| 992 | + when(groupMemberService.validateMemberInGroup(10L, 2L)).thenReturn( | |
| 993 | + ImGroupMemberDO.builder().groupId(10L).userId(2L) | |
| 994 | + .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build()); | |
| 995 | + | |
| 996 | + ImGroupMuteMemberReqVO reqVO = new ImGroupMuteMemberReqVO(); | |
| 997 | + reqVO.setGroupId(10L).setUserId(2L).setMutedSeconds(60); | |
| 998 | + | |
| 999 | + ServiceException exception = assertThrows(ServiceException.class, | |
| 1000 | + () -> groupService.muteMember(1L, reqVO)); | |
| 1001 | + assertEquals(GROUP_NOT_OWNER_OR_ADMIN.getCode(), exception.getCode()); | |
| 1002 | + verify(groupMemberService, never()).updateGroupMemberMuteEndTime(anyLong(), anyLong(), any()); | |
| 1003 | + verify(groupMessageService, never()).sendGroupMessage(anyLong(), any(ImGroupMessageSendDTO.class)); | |
| 1004 | + } | |
| 1005 | + } | |
| 1006 | + | |
| 935 | 1007 | // ========== validateGroupExists ========== |
| 936 | 1008 | |
| 937 | 1009 | @Test | ... | ... |
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImplTest.java
| ... | ... | @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; |
| 6 | 6 | import org.mockito.InjectMocks; |
| 7 | 7 | import org.mockito.Mock; |
| 8 | 8 | |
| 9 | +import java.math.BigInteger; | |
| 9 | 10 | import java.time.LocalDate; |
| 10 | 11 | import java.time.LocalDateTime; |
| 11 | 12 | import java.util.Arrays; |
| ... | ... | @@ -75,10 +76,10 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { |
| 75 | 76 | |
| 76 | 77 | @Test |
| 77 | 78 | public void testGetNewUserDailyCountMap_dateConvert() { |
| 78 | - // 准备:date 字段以 SQL Date / String 混入,count 为 Long | |
| 79 | + // 准备:date 字段以 SQL Date / String 混入,count 混入多种 Number | |
| 79 | 80 | when(statisticsMapper.selectNewUserDailyCount(eq(BEGIN), eq(END))).thenReturn(Arrays.asList( |
| 80 | - row("date", java.sql.Date.valueOf("2026-01-10"), "count", 5L), | |
| 81 | - row("date", "2026-01-11", "count", 8L))); | |
| 81 | + row("date", java.sql.Date.valueOf("2026-01-10"), "count", BigInteger.valueOf(5)), | |
| 82 | + row("date", "2026-01-11", "count", 8))); | |
| 82 | 83 | |
| 83 | 84 | // 调用 |
| 84 | 85 | Map<LocalDateTime, Long> result = service.getNewUserDailyCountMap(BEGIN, END); |
| ... | ... | @@ -119,8 +120,8 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { |
| 119 | 120 | @Test |
| 120 | 121 | public void testGetGroupSizeCountMap() { |
| 121 | 122 | when(statisticsMapper.selectGroupSizeDistribution()).thenReturn(Arrays.asList( |
| 122 | - row("range", "1-9 人", "count", 3L), | |
| 123 | - row("range", "10-49 人", "count", 2L))); | |
| 123 | + row("range", "1-9 人", "count", BigInteger.valueOf(3)), | |
| 124 | + row("range", "10-49 人", "count", 2))); | |
| 124 | 125 | |
| 125 | 126 | Map<String, Long> result = service.getGroupSizeCountMap(); |
| 126 | 127 | assertEquals(3L, result.get("1-9 人")); |
| ... | ... | @@ -130,8 +131,8 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { |
| 130 | 131 | @Test |
| 131 | 132 | public void testGetMessageTypeCountMap() { |
| 132 | 133 | when(statisticsMapper.selectMessageTypeDistribution(BEGIN, END)).thenReturn(Arrays.asList( |
| 133 | - row("type", 101, "count", 8L), | |
| 134 | - row("type", 102, "count", 1L))); | |
| 134 | + row("type", 101L, "count", BigInteger.valueOf(8)), | |
| 135 | + row("type", 102, "count", 1))); | |
| 135 | 136 | |
| 136 | 137 | Map<Integer, Long> result = service.getMessageTypeCountMap(BEGIN, END); |
| 137 | 138 | assertEquals(8L, result.get(101)); |
| ... | ... | @@ -141,8 +142,8 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { |
| 141 | 142 | @Test |
| 142 | 143 | public void testGetTopSenderCountMap_passesLimit() { |
| 143 | 144 | when(statisticsMapper.selectTopSenders(BEGIN, END, 3)).thenReturn(Arrays.asList( |
| 144 | - row("userId", 1L, "messageCount", 10L), | |
| 145 | - row("userId", 2L, "messageCount", 5L))); | |
| 145 | + row("userId", 1, "messageCount", BigInteger.valueOf(10)), | |
| 146 | + row("userId", 2L, "messageCount", 5))); | |
| 146 | 147 | |
| 147 | 148 | Map<Long, Long> result = service.getTopSenderCountMap(BEGIN, END, 3); |
| 148 | 149 | assertEquals(10L, result.get(1L)); | ... | ... |