Commit f5bec3eec687e356b31ac21648dfa3beb5c25dbf

Authored by YunaiV
1 parent ae71b713

✨ feat(im): 初始化表情包 v0.3:第四把 review(增加表情管理的界面)

Showing 18 changed files with 273 additions and 71 deletions
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java
@@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.im.controller.admin.face; @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.im.controller.admin.face;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 -import cn.iocoder.yudao.module.im.controller.admin.face.vo.userItem.ImFaceUserItemRespVO;  
6 -import cn.iocoder.yudao.module.im.controller.admin.face.vo.userItem.ImFaceUserItemSaveReqVO; 5 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemRespVO;
  6 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemSaveReqVO;
7 import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; 7 import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
8 import cn.iocoder.yudao.module.im.service.face.ImFaceUserItemService; 8 import cn.iocoder.yudao.module.im.service.face.ImFaceUserItemService;
9 import io.swagger.v3.oas.annotations.Operation; 9 import io.swagger.v3.oas.annotations.Operation;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/pack/ImFacePackUserRespVO.java
@@ -15,8 +15,8 @@ public class ImFacePackUserRespVO { @@ -15,8 +15,8 @@ public class ImFacePackUserRespVO {
15 @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子") 15 @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子")
16 private String name; 16 private String name;
17 17
18 - @Schema(description = "表情包图标 URL", example = "https://cdn.example.com/face/pack/cat.png")  
19 - private String iconUrl; 18 + @Schema(description = "表情包图标", example = "https://cdn.example.com/face/pack/cat.png")
  19 + private String icon;
20 20
21 @Schema(description = "表情列表", requiredMode = Schema.RequiredMode.REQUIRED) 21 @Schema(description = "表情列表", requiredMode = Schema.RequiredMode.REQUIRED)
22 private List<Item> items; 22 private List<Item> items;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/userItem/ImFaceUserItemRespVO.java
1 -package cn.iocoder.yudao.module.im.controller.admin.face.vo.userItem; 1 +package cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/userItem/ImFaceUserItemSaveReqVO.java
1 -package cn.iocoder.yudao.module.im.controller.admin.face.vo.userItem; 1 +// TODO @AI:java 规范,应该是 useritem
  2 +package cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem;
2 3
3 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotBlank; 5 import jakarta.validation.constraints.NotBlank;
@@ -28,7 +29,4 @@ public class ImFaceUserItemSaveReqVO { @@ -28,7 +29,4 @@ public class ImFaceUserItemSaveReqVO {
28 @NotNull(message = "渲染高度不能为空") 29 @NotNull(message = "渲染高度不能为空")
29 private Integer height; 30 private Integer height;
30 31
31 - @Schema(description = "来源消息编号(从消息「添加到表情」时传入;自己上传则不传)", example = "10086")  
32 - private Long sourceMessageId;  
33 -  
34 } 32 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFaceUserItemManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face;
  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.collection.CollectionUtils;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerPageReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerRespVO;
  9 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
  10 +import cn.iocoder.yudao.module.im.service.face.ImFaceUserItemService;
  11 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  12 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  13 +import io.swagger.v3.oas.annotations.Operation;
  14 +import io.swagger.v3.oas.annotations.Parameter;
  15 +import io.swagger.v3.oas.annotations.tags.Tag;
  16 +import jakarta.annotation.Resource;
  17 +import jakarta.validation.Valid;
  18 +import org.springframework.security.access.prepost.PreAuthorize;
  19 +import org.springframework.validation.annotation.Validated;
  20 +import org.springframework.web.bind.annotation.*;
  21 +
  22 +import java.util.List;
  23 +import java.util.Map;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  26 +
  27 +@Tag(name = "管理后台 - IM 用户表情")
  28 +@RestController
  29 +@RequestMapping("/im/manager/face-user-item")
  30 +@Validated
  31 +public class ImFaceUserItemManagerController {
  32 +
  33 + @Resource
  34 + private ImFaceUserItemService faceUserItemService;
  35 + @Resource
  36 + private AdminUserApi adminUserApi;
  37 +
  38 + @GetMapping("/page")
  39 + @Operation(summary = "获得用户表情分页")
  40 + @PreAuthorize("@ss.hasPermission('im:manager:face-user-item:query')")
  41 + public CommonResult<PageResult<ImFaceUserItemManagerRespVO>> getFaceUserItemPage(
  42 + @Valid ImFaceUserItemManagerPageReqVO pageReqVO) {
  43 + PageResult<ImFaceUserItemDO> pageResult = faceUserItemService.getFaceUserItemPage(pageReqVO);
  44 + // 关联回填用户昵称
  45 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  46 + CollectionUtils.convertSet(pageResult.getList(), ImFaceUserItemDO::getUserId));
  47 + List<ImFaceUserItemManagerRespVO> voList = CollectionUtils.convertList(pageResult.getList(), item -> {
  48 + ImFaceUserItemManagerRespVO vo = BeanUtils.toBean(item, ImFaceUserItemManagerRespVO.class);
  49 + AdminUserRespDTO user = userMap.get(item.getUserId());
  50 + if (user != null) {
  51 + vo.setUserNickname(user.getNickname());
  52 + }
  53 + return vo;
  54 + });
  55 + return success(new PageResult<>(voList, pageResult.getTotal()));
  56 + }
  57 +
  58 + @DeleteMapping("/delete")
  59 + @Operation(summary = "删除用户表情")
  60 + @Parameter(name = "id", description = "编号", required = true, example = "4096")
  61 + @PreAuthorize("@ss.hasPermission('im:manager:face-user-item:delete')")
  62 + public CommonResult<Boolean> deleteFaceUserItem(@RequestParam("id") Long id) {
  63 + faceUserItemService.deleteFaceUserItem(id);
  64 + return success(true);
  65 + }
  66 +
  67 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemPageReqVO.java
@@ -5,7 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -5,7 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 5 import lombok.Data;
6 import lombok.EqualsAndHashCode; 6 import lombok.EqualsAndHashCode;
7 7
8 -// TODO @AI:拆分到 vo/item 目录下,如果是 item 的话;更简练;  
9 @Schema(description = "管理后台 - IM 表情包项分页 Request VO") 8 @Schema(description = "管理后台 - IM 表情包项分页 Request VO")
10 @Data 9 @Data
11 @EqualsAndHashCode(callSuper = true) 10 @EqualsAndHashCode(callSuper = true)
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackRespVO.java
@@ -15,8 +15,8 @@ public class ImFacePackRespVO { @@ -15,8 +15,8 @@ public class ImFacePackRespVO {
15 @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子") 15 @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子")
16 private String name; 16 private String name;
17 17
18 - @Schema(description = "表情包图标 URL", example = "https://cdn.example.com/face/pack/cat.png")  
19 - private String iconUrl; 18 + @Schema(description = "表情包图标", example = "https://cdn.example.com/face/pack/cat.png")
  19 + private String icon;
20 20
21 @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") 21 @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
22 private Integer sort; 22 private Integer sort;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackSaveReqVO.java
@@ -18,9 +18,9 @@ public class ImFacePackSaveReqVO { @@ -18,9 +18,9 @@ public class ImFacePackSaveReqVO {
18 @Size(max = 64, message = "表情包名称长度不能超过 64") 18 @Size(max = 64, message = "表情包名称长度不能超过 64")
19 private String name; 19 private String name;
20 20
21 - @Schema(description = "表情包图标 URL", example = "https://cdn.example.com/face/pack/cat.png")  
22 - @Size(max = 512, message = "图标 URL 长度不能超过 512")  
23 - private String iconUrl; 21 + @Schema(description = "表情包图标", example = "https://cdn.example.com/face/pack/cat.png")
  22 + @Size(max = 512, message = "图标长度不能超过 512")
  23 + private String icon;
24 24
25 @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") 25 @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
26 @NotNull(message = "排序不能为空") 26 @NotNull(message = "排序不能为空")
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/useritem/ImFaceUserItemManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem;
  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 ImFaceUserItemManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "所属用户编号", example = "1024")
  19 + private Long userId;
  20 +
  21 + @Schema(description = "表情名,模糊匹配", example = "狗")
  22 + private String name;
  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/face/vo/useritem/ImFaceUserItemManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem;
  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 ImFaceUserItemManagerRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
  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 = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  22 + example = "https://cdn.example.com/face/user/abc.gif")
  23 + private String url;
  24 +
  25 + @Schema(description = "表情名", example = "狗头")
  26 + private String name;
  27 +
  28 + @Schema(description = "渲染宽度(像素)", example = "200")
  29 + private Integer width;
  30 +
  31 + @Schema(description = "渲染高度(像素)", example = "200")
  32 + private Integer height;
  33 +
  34 + @Schema(description = "添加时间", requiredMode = Schema.RequiredMode.REQUIRED)
  35 + private LocalDateTime createTime;
  36 +
  37 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java
@@ -31,13 +31,12 @@ public class ImFacePackDO extends BaseDO { @@ -31,13 +31,12 @@ public class ImFacePackDO extends BaseDO {
31 * 表情包名称 31 * 表情包名称
32 */ 32 */
33 private String name; 33 private String name;
34 - // TODO @AI:icon 一般不带 url;类似 avatar,特有单词;  
35 /** 34 /**
36 - * 表情包图标 URL 35 + * 表情包图标
37 * <p> 36 * <p>
38 * 面板底部 tab 栏显示 37 * 面板底部 tab 栏显示
39 */ 38 */
40 - private String iconUrl; 39 + private String icon;
41 /** 40 /**
42 * 排序 41 * 排序
43 */ 42 */
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java
@@ -28,8 +28,8 @@ public class ImFaceUserItemDO extends BaseDO { @@ -28,8 +28,8 @@ public class ImFaceUserItemDO extends BaseDO {
28 private Long id; 28 private Long id;
29 /** 29 /**
30 * 所属用户编号 30 * 所属用户编号
31 - *  
32 - * // TODO @AI:是不是要关联下 userid 31 + * <p>
  32 + * 关联 AdminUserDO 的 id 编号
33 */ 33 */
34 private Long userId; 34 private Long userId;
35 /** 35 /**
@@ -51,14 +51,6 @@ public class ImFaceUserItemDO extends BaseDO { @@ -51,14 +51,6 @@ public class ImFaceUserItemDO extends BaseDO {
51 */ 51 */
52 private Integer height; 52 private Integer height;
53 /** 53 /**
54 - * 来源消息编号  
55 - * <p>  
56 - * 从消息「添加到表情」时记录;自己上传则为 NULL;用于排查 / 后续溯源  
57 - *  
58 - * TODO @AI:关联字段;  
59 - */  
60 - private Long sourceMessageId;  
61 - /**  
62 * 排序 54 * 排序
63 */ 55 */
64 private Integer sort; 56 private Integer sort;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java
1 package cn.iocoder.yudao.module.im.dal.mysql.face; 1 package cn.iocoder.yudao.module.im.dal.mysql.face;
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.face.vo.useritem.ImFaceUserItemManagerPageReqVO;
5 import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; 7 import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
6 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
7 9
@@ -15,7 +17,6 @@ import java.util.List; @@ -15,7 +17,6 @@ import java.util.List;
15 @Mapper 17 @Mapper
16 public interface ImFaceUserItemMapper extends BaseMapperX<ImFaceUserItemDO> { 18 public interface ImFaceUserItemMapper extends BaseMapperX<ImFaceUserItemDO> {
17 19
18 - /** 取用户的所有个人表情,按 sort 升序、id 倒序(最近添加在前) */  
19 default List<ImFaceUserItemDO> selectListByUserId(Long userId) { 20 default List<ImFaceUserItemDO> selectListByUserId(Long userId) {
20 return selectList(new LambdaQueryWrapperX<ImFaceUserItemDO>() 21 return selectList(new LambdaQueryWrapperX<ImFaceUserItemDO>()
21 .eq(ImFaceUserItemDO::getUserId, userId) 22 .eq(ImFaceUserItemDO::getUserId, userId)
@@ -23,15 +24,18 @@ public interface ImFaceUserItemMapper extends BaseMapperX&lt;ImFaceUserItemDO&gt; { @@ -23,15 +24,18 @@ public interface ImFaceUserItemMapper extends BaseMapperX&lt;ImFaceUserItemDO&gt; {
23 .orderByDesc(ImFaceUserItemDO::getId)); 24 .orderByDesc(ImFaceUserItemDO::getId));
24 } 25 }
25 26
26 - default Long selectCountByUserId(Long userId) {  
27 - return selectCount(new LambdaQueryWrapperX<ImFaceUserItemDO>()  
28 - .eq(ImFaceUserItemDO::getUserId, userId));  
29 - }  
30 -  
31 default ImFaceUserItemDO selectByUserIdAndUrl(Long userId, String url) { 27 default ImFaceUserItemDO selectByUserIdAndUrl(Long userId, String url) {
32 return selectOne(new LambdaQueryWrapperX<ImFaceUserItemDO>() 28 return selectOne(new LambdaQueryWrapperX<ImFaceUserItemDO>()
33 .eq(ImFaceUserItemDO::getUserId, userId) 29 .eq(ImFaceUserItemDO::getUserId, userId)
34 .eq(ImFaceUserItemDO::getUrl, url)); 30 .eq(ImFaceUserItemDO::getUrl, url));
35 } 31 }
36 32
  33 + default PageResult<ImFaceUserItemDO> selectPage(ImFaceUserItemManagerPageReqVO reqVO) {
  34 + return selectPage(reqVO, new LambdaQueryWrapperX<ImFaceUserItemDO>()
  35 + .eqIfPresent(ImFaceUserItemDO::getUserId, reqVO.getUserId())
  36 + .likeIfPresent(ImFaceUserItemDO::getName, reqVO.getName())
  37 + .betweenIfPresent(ImFaceUserItemDO::getCreateTime, reqVO.getCreateTime())
  38 + .orderByDesc(ImFaceUserItemDO::getId));
  39 + }
  40 +
37 } 41 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java
@@ -9,8 +9,6 @@ import jakarta.validation.Valid; @@ -9,8 +9,6 @@ import jakarta.validation.Valid;
9 import java.util.Collection; 9 import java.util.Collection;
10 import java.util.List; 10 import java.util.List;
11 11
12 -// TODO @AI:已经分了用户端、和管理后台,注释上就不用写【用户端】【【管理后台】】了。。。  
13 -// TODO @AI:注释的风格,参考下别的模块,一些 params、return 都要写呀;  
14 /** 12 /**
15 * IM 表情包项 Service 接口 13 * IM 表情包项 Service 接口
16 * 14 *
@@ -21,49 +19,73 @@ public interface ImFacePackItemService { @@ -21,49 +19,73 @@ public interface ImFacePackItemService {
21 // ==================== 用户端 ==================== 19 // ==================== 用户端 ====================
22 20
23 /** 21 /**
24 - * 【用户端】按 packIds 批量取启用项,给前端聚合接口用 22 + * 按 packIds 批量取启用项,给前端聚合接口用
  23 + *
  24 + * @param packIds 表情包编号列表
  25 + * @return 启用状态的表情列表
25 */ 26 */
26 List<ImFacePackItemDO> getEnabledItemListByPackIds(Collection<Long> packIds); 27 List<ImFacePackItemDO> getEnabledItemListByPackIds(Collection<Long> packIds);
27 28
28 /** 29 /**
29 * 取某个表情包下的表情数量;ImFacePackService 删除前校验「包下无项」用 30 * 取某个表情包下的表情数量;ImFacePackService 删除前校验「包下无项」用
  31 + *
  32 + * @param packId 表情包编号
  33 + * @return 数量
30 */ 34 */
31 Long getFacePackItemCount(Long packId); 35 Long getFacePackItemCount(Long packId);
32 36
33 /** 37 /**
34 * 取多个表情包下的表情数量合计;ImFacePackService 批量删除前校验用 38 * 取多个表情包下的表情数量合计;ImFacePackService 批量删除前校验用
  39 + *
  40 + * @param packIds 表情包编号列表
  41 + * @return 数量合计
35 */ 42 */
36 Long getFacePackItemCount(Collection<Long> packIds); 43 Long getFacePackItemCount(Collection<Long> packIds);
37 44
38 // ==================== 管理后台 ==================== 45 // ==================== 管理后台 ====================
39 46
40 /** 47 /**
41 - * 【管理后台】分页查询 48 + * 分页查询表情包项
  49 + *
  50 + * @param reqVO 分页查询条件
  51 + * @return 表情包项分页
42 */ 52 */
43 PageResult<ImFacePackItemDO> getFacePackItemPage(ImFacePackItemPageReqVO reqVO); 53 PageResult<ImFacePackItemDO> getFacePackItemPage(ImFacePackItemPageReqVO reqVO);
44 54
45 /** 55 /**
46 - * 【管理后台】获取详情 56 + * 获取表情包项详情
  57 + *
  58 + * @param id 表情包项编号
  59 + * @return 表情包项 DO
47 */ 60 */
48 ImFacePackItemDO getFacePackItem(Long id); 61 ImFacePackItemDO getFacePackItem(Long id);
49 62
50 /** 63 /**
51 - * 【管理后台】新增,返回新增 id 64 + * 新增表情包项
  65 + *
  66 + * @param reqVO 新增请求
  67 + * @return 新增表情包项编号
52 */ 68 */
53 Long createFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO); 69 Long createFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO);
54 70
55 /** 71 /**
56 - * 【管理后台】修改 72 + * 修改表情包项
  73 + *
  74 + * @param reqVO 修改请求
57 */ 75 */
58 void updateFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO); 76 void updateFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO);
59 77
60 /** 78 /**
61 - * 【管理后台】删除 79 + * 删除表情包项
  80 + *
  81 + * @param id 表情包项编号
62 */ 82 */
63 void deleteFacePackItem(Long id); 83 void deleteFacePackItem(Long id);
64 84
65 /** 85 /**
66 - * 【管理后台】批量删除 86 + * 批量删除表情包项
  87 + *
  88 + * @param ids 表情包项编号列表
67 */ 89 */
68 void deleteFacePackItemList(List<Long> ids); 90 void deleteFacePackItemList(List<Long> ids);
69 91
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java
@@ -15,17 +15,20 @@ import java.util.List; @@ -15,17 +15,20 @@ import java.util.List;
15 */ 15 */
16 public interface ImFacePackService { 16 public interface ImFacePackService {
17 17
18 - // TODO @AI:已经区分了 === 就不用写【用户端】【管理后台】了。  
19 -  
20 // ==================== 用户端 ==================== 18 // ==================== 用户端 ====================
21 19
22 /** 20 /**
23 - * 【用户端】获取所有启用的表情包,按 sort 升序 21 + * 获取所有启用的表情包,按 sort 升序
  22 + *
  23 + * @return 启用的表情包列表
24 */ 24 */
25 List<ImFacePackDO> getEnabledFacePackList(); 25 List<ImFacePackDO> getEnabledFacePackList();
26 26
27 /** 27 /**
28 * 校验表情包存在 28 * 校验表情包存在
  29 + *
  30 + * @param id 表情包编号
  31 + * @return 表情包 DO
29 */ 32 */
30 @SuppressWarnings("UnusedReturnValue") 33 @SuppressWarnings("UnusedReturnValue")
31 ImFacePackDO validateFacePackExists(Long id); 34 ImFacePackDO validateFacePackExists(Long id);
@@ -33,32 +36,47 @@ public interface ImFacePackService { @@ -33,32 +36,47 @@ public interface ImFacePackService {
33 // ==================== 管理后台 ==================== 36 // ==================== 管理后台 ====================
34 37
35 /** 38 /**
36 - * 【管理后台】分页查询表情包 39 + * 分页查询表情包
  40 + *
  41 + * @param reqVO 分页查询条件
  42 + * @return 表情包分页
37 */ 43 */
38 PageResult<ImFacePackDO> getFacePackPage(ImFacePackPageReqVO reqVO); 44 PageResult<ImFacePackDO> getFacePackPage(ImFacePackPageReqVO reqVO);
39 45
40 /** 46 /**
41 - * 【管理后台】获取表情包详情 47 + * 获取表情包详情
  48 + *
  49 + * @param id 表情包编号
  50 + * @return 表情包 DO
42 */ 51 */
43 ImFacePackDO getFacePack(Long id); 52 ImFacePackDO getFacePack(Long id);
44 53
45 /** 54 /**
46 - * 【管理后台】新增表情包,返回新增 id 55 + * 新增表情包
  56 + *
  57 + * @param reqVO 新增请求
  58 + * @return 新增表情包编号
47 */ 59 */
48 Long createFacePack(@Valid ImFacePackSaveReqVO reqVO); 60 Long createFacePack(@Valid ImFacePackSaveReqVO reqVO);
49 61
50 /** 62 /**
51 - * 【管理后台】修改表情包 63 + * 修改表情包
  64 + *
  65 + * @param reqVO 修改请求
52 */ 66 */
53 void updateFacePack(@Valid ImFacePackSaveReqVO reqVO); 67 void updateFacePack(@Valid ImFacePackSaveReqVO reqVO);
54 68
55 /** 69 /**
56 - * 【管理后台】删除表情包;存在表情时拒绝 70 + * 删除表情包;包下存在表情时拒绝,避免历史 face 消息无法回查归属
  71 + *
  72 + * @param id 表情包编号
57 */ 73 */
58 void deleteFacePack(Long id); 74 void deleteFacePack(Long id);
59 75
60 /** 76 /**
61 - * 【管理后台】批量删除表情包 77 + * 批量删除表情包;任一包下存在表情时整批拒绝,避免「只删一半」中间态
  78 + *
  79 + * @param ids 表情包编号列表
62 */ 80 */
63 void deleteFacePackList(List<Long> ids); 81 void deleteFacePackList(List<Long> ids);
64 82
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java
1 package cn.iocoder.yudao.module.im.service.face; 1 package cn.iocoder.yudao.module.im.service.face;
2 2
3 -import cn.iocoder.yudao.module.im.controller.admin.face.vo.userItem.ImFaceUserItemSaveReqVO; 3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemSaveReqVO;
  5 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerPageReqVO;
4 import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; 6 import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
5 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
6 8
@@ -15,19 +17,44 @@ public interface ImFaceUserItemService { @@ -15,19 +17,44 @@ public interface ImFaceUserItemService {
15 17
16 /** 18 /**
17 * 获取指定用户的个人表情列表 19 * 获取指定用户的个人表情列表
  20 + *
  21 + * @param userId 用户编号
  22 + * @return 个人表情列表
18 */ 23 */
19 List<ImFaceUserItemDO> getFaceUserItemList(Long userId); 24 List<ImFaceUserItemDO> getFaceUserItemList(Long userId);
20 25
21 /** 26 /**
22 - * 添加个人表情;返回新增记录 id  
23 - * <p>  
24 - * 同 URL 重复添加抛 FACE_USER_ITEM_DUPLICATED;表上 (user_id, url) 唯一约束兜底并发 race 27 + * 添加个人表情
  28 + *
  29 + * @param userId 用户编号
  30 + * @param reqVO 添加请求
  31 + * @return 新增表情编号
25 */ 32 */
26 Long createFaceUserItem(Long userId, @Valid ImFaceUserItemSaveReqVO reqVO); 33 Long createFaceUserItem(Long userId, @Valid ImFaceUserItemSaveReqVO reqVO);
27 34
28 /** 35 /**
29 * 删除指定用户的某条个人表情 36 * 删除指定用户的某条个人表情
  37 + *
  38 + * @param userId 用户编号
  39 + * @param id 表情编号
30 */ 40 */
31 void deleteFaceUserItem(Long userId, Long id); 41 void deleteFaceUserItem(Long userId, Long id);
32 42
  43 + // ==================== 管理后台 ====================
  44 +
  45 + /**
  46 + * 分页查询所有用户的个人表情;管理后台审计 / 删除违规图用
  47 + *
  48 + * @param reqVO 分页查询条件
  49 + * @return 个人表情分页
  50 + */
  51 + PageResult<ImFaceUserItemDO> getFaceUserItemPage(ImFaceUserItemManagerPageReqVO reqVO);
  52 +
  53 + /**
  54 + * 管理后台直接删除某条个人表情;不做归属校验
  55 + *
  56 + * @param id 表情编号
  57 + */
  58 + void deleteFaceUserItem(Long id);
  59 +
33 } 60 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java
1 package cn.iocoder.yudao.module.im.service.face; 1 package cn.iocoder.yudao.module.im.service.face;
2 2
3 import cn.hutool.core.util.ObjectUtil; 3 import cn.hutool.core.util.ObjectUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 -import cn.iocoder.yudao.module.im.controller.admin.face.vo.userItem.ImFaceUserItemSaveReqVO; 6 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemSaveReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerPageReqVO;
6 import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; 8 import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
7 import cn.iocoder.yudao.module.im.dal.mysql.face.ImFaceUserItemMapper; 9 import cn.iocoder.yudao.module.im.dal.mysql.face.ImFaceUserItemMapper;
8 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
9 -import org.springframework.dao.DuplicateKeyException;  
10 import org.springframework.stereotype.Service; 11 import org.springframework.stereotype.Service;
11 import org.springframework.validation.annotation.Validated; 12 import org.springframework.validation.annotation.Validated;
12 13
@@ -41,15 +42,9 @@ public class ImFaceUserItemServiceImpl implements ImFaceUserItemService { @@ -41,15 +42,9 @@ public class ImFaceUserItemServiceImpl implements ImFaceUserItemService {
41 throw exception(FACE_USER_ITEM_DUPLICATED); 42 throw exception(FACE_USER_ITEM_DUPLICATED);
42 } 43 }
43 44
44 - // 2. 入库;表 (user_id, url, tenant_id) 唯一约束兜底并发 race —— 两个请求同时过 step 1 时 DB 拦截  
45 - // TODO @AI:不加唯一索引。不然删除后恢复麻烦。按照我说的来。  
46 - ImFaceUserItemDO item = BeanUtils.toBean(reqVO, ImFaceUserItemDO.class)  
47 - .setUserId(userId);  
48 - try {  
49 - faceUserItemMapper.insert(item);  
50 - } catch (DuplicateKeyException ignored) {  
51 - throw exception(FACE_USER_ITEM_DUPLICATED);  
52 - } 45 + // 2. 入库
  46 + ImFaceUserItemDO item = BeanUtils.toBean(reqVO, ImFaceUserItemDO.class).setUserId(userId);
  47 + faceUserItemMapper.insert(item);
53 return item.getId(); 48 return item.getId();
54 } 49 }
55 50
@@ -69,4 +64,22 @@ public class ImFaceUserItemServiceImpl implements ImFaceUserItemService { @@ -69,4 +64,22 @@ public class ImFaceUserItemServiceImpl implements ImFaceUserItemService {
69 faceUserItemMapper.deleteById(id); 64 faceUserItemMapper.deleteById(id);
70 } 65 }
71 66
  67 + // ==================== 管理后台 ====================
  68 +
  69 + @Override
  70 + public PageResult<ImFaceUserItemDO> getFaceUserItemPage(ImFaceUserItemManagerPageReqVO reqVO) {
  71 + return faceUserItemMapper.selectPage(reqVO);
  72 + }
  73 +
  74 + @Override
  75 + public void deleteFaceUserItem(Long id) {
  76 + // 1. 校验存在
  77 + if (faceUserItemMapper.selectById(id) == null) {
  78 + throw exception(FACE_USER_ITEM_NOT_EXISTS);
  79 + }
  80 +
  81 + // 2. 删除
  82 + faceUserItemMapper.deleteById(id);
  83 + }
  84 +
72 } 85 }
yudao-module-im/src/test/resources/sql/create_tables.sql
@@ -149,7 +149,7 @@ CREATE TABLE IF NOT EXISTS &quot;im_group_request&quot; ( @@ -149,7 +149,7 @@ CREATE TABLE IF NOT EXISTS &quot;im_group_request&quot; (
149 CREATE TABLE IF NOT EXISTS "im_face_pack" ( 149 CREATE TABLE IF NOT EXISTS "im_face_pack" (
150 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', 150 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号',
151 "name" varchar(64) NOT NULL COMMENT '表情包名称', 151 "name" varchar(64) NOT NULL COMMENT '表情包名称',
152 - "icon_url" varchar(512) DEFAULT NULL COMMENT '表情包图标 URL(面板底部 tab 显示)', 152 + "icon" varchar(512) DEFAULT NULL COMMENT '表情包图标(面板底部 tab 显示)',
153 "sort" int NOT NULL DEFAULT 0 COMMENT '排序', 153 "sort" int NOT NULL DEFAULT 0 COMMENT '排序',
154 "status" tinyint NOT NULL COMMENT '状态', 154 "status" tinyint NOT NULL COMMENT '状态',
155 "creator" varchar(64) DEFAULT '', 155 "creator" varchar(64) DEFAULT '',
@@ -186,7 +186,6 @@ CREATE TABLE IF NOT EXISTS &quot;im_face_user_item&quot; ( @@ -186,7 +186,6 @@ CREATE TABLE IF NOT EXISTS &quot;im_face_user_item&quot; (
186 "name" varchar(64) DEFAULT NULL COMMENT '表情名(可选)', 186 "name" varchar(64) DEFAULT NULL COMMENT '表情名(可选)',
187 "width" int NOT NULL DEFAULT 0 COMMENT '渲染宽度(像素)', 187 "width" int NOT NULL DEFAULT 0 COMMENT '渲染宽度(像素)',
188 "height" int NOT NULL DEFAULT 0 COMMENT '渲染高度(像素)', 188 "height" int NOT NULL DEFAULT 0 COMMENT '渲染高度(像素)',
189 - "source_message_id" bigint DEFAULT NULL COMMENT '来源消息编号(从消息「添加到表情」时记录;自己上传则为 NULL)',  
190 "sort" int NOT NULL DEFAULT 0 COMMENT '排序', 189 "sort" int NOT NULL DEFAULT 0 COMMENT '排序',
191 "creator" varchar(64) DEFAULT '', 190 "creator" varchar(64) DEFAULT '',
192 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 191 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -194,6 +193,5 @@ CREATE TABLE IF NOT EXISTS &quot;im_face_user_item&quot; ( @@ -194,6 +193,5 @@ CREATE TABLE IF NOT EXISTS &quot;im_face_user_item&quot; (
194 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 193 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
195 "deleted" bit NOT NULL DEFAULT FALSE, 194 "deleted" bit NOT NULL DEFAULT FALSE,
196 "tenant_id" bigint NOT NULL DEFAULT 0, 195 "tenant_id" bigint NOT NULL DEFAULT 0,
197 - PRIMARY KEY ("id"),  
198 - CONSTRAINT "uk_im_face_user_item_user_url" UNIQUE ("user_id", "url", "tenant_id") 196 + PRIMARY KEY ("id")
199 ) COMMENT 'IM 用户私有表情表'; 197 ) COMMENT 'IM 用户私有表情表';