From 8a4ad959d6e67755f424d858246cf6c1ffb1294c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 6 May 2026 19:42:19 +0800 Subject: [PATCH] ✨ feat(im): 初始化表情包 v0.0:第一把 review --- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFacePackController.java | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFacePackUserRespVO.java | 46 ++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemRespVO.java | 26 ++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemSaveReqVO.java | 34 ++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.java | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.java | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemPageReqVO.java | 23 +++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemRespVO.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemSaveReqVO.java | 46 ++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackPageReqVO.java | 28 ++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackRespVO.java | 30 ++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackSaveReqVO.java | 33 +++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackItemDO.java | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackMapper.java | 36 ++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java | 37 +++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java | 24 +++++++++++++++++------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImpl.java | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImpl.java | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java | 38 ++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java | 40 ++++++++++++++++++++++++++++++++++++++++ 27 files changed, 1415 insertions(+), 7 deletions(-) create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFacePackController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFacePackUserRespVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemRespVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemSaveReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemPageReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemRespVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemSaveReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackPageReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackRespVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackSaveReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackItemDO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackMapper.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImpl.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImpl.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFacePackController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFacePackController.java new file mode 100644 index 0000000..1e9dbb4 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFacePackController.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.im.controller.admin.face; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFacePackUserRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import cn.iocoder.yudao.module.im.service.face.ImFacePackItemService; +import cn.iocoder.yudao.module.im.service.face.ImFacePackService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; + +@Tag(name = "管理后台 - IM 表情包") +@RestController +@RequestMapping("/im/face-pack") +@Validated +public class ImFacePackController { + + @Resource + private ImFacePackService facePackService; + @Resource + private ImFacePackItemService facePackItemService; + + @GetMapping("/list") + @Operation(summary = "获得启用的表情包列表(含表情)") + public CommonResult> getFacePackList() { + // 1.1 拉所有启用表情包 + List packs = facePackService.getEnabledFacePackList(); + if (packs.isEmpty()) { + return success(List.of()); + } + // 1.2 拉这些包下所有启用表情,按 packId 分组 + List items = facePackItemService.getEnabledItemListByPackIds( + convertList(packs, ImFacePackDO::getId)); + Map> itemsByPackId = convertMultiMap(items, ImFacePackItemDO::getPackId); + + // 2. 拼装:BeanUtils 把 pack 字段映射 + 自己塞 items + List result = convertList(packs, pack -> { + ImFacePackUserRespVO vo = BeanUtils.toBean(pack, ImFacePackUserRespVO.class); + vo.setItems(BeanUtils.toBean(itemsByPackId.getOrDefault(pack.getId(), List.of()), ImFacePackUserRespVO.Item.class)); + return vo; + }); + return success(result); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java new file mode 100644 index 0000000..612609d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.im.controller.admin.face; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFaceUserItemRespVO; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFaceUserItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import cn.iocoder.yudao.module.im.service.face.ImFaceUserItemService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - IM 个人表情") +@RestController +@RequestMapping("/im/face-user-item") +@Validated +public class ImFaceUserItemController { + + @Resource + private ImFaceUserItemService faceUserItemService; + + @GetMapping("/list") + @Operation(summary = "获得我的个人表情列表") + public CommonResult> getMyFaceUserItemList() { + List items = faceUserItemService.getMyFaceUserItemList(getLoginUserId()); + return success(BeanUtils.toBean(items, ImFaceUserItemRespVO.class)); + } + + @PostMapping("/create") + @Operation(summary = "添加个人表情") + public CommonResult createFaceUserItem(@Valid @RequestBody ImFaceUserItemSaveReqVO reqVO) { + return success(faceUserItemService.createFaceUserItem(getLoginUserId(), reqVO)); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除个人表情") + @Parameter(name = "id", description = "编号", required = true, example = "4096") + public CommonResult deleteFaceUserItem(@RequestParam("id") Long id) { + faceUserItemService.deleteFaceUserItem(getLoginUserId(), id); + return success(true); + } + + // TODO @AI:个人应该没批量删除把?应该就一个一个删除呢。 + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除个人表情") + @Parameter(name = "ids", description = "编号列表", required = true) + public CommonResult deleteFaceUserItemList(@RequestParam("ids") List ids) { + faceUserItemService.deleteFaceUserItemList(getLoginUserId(), ids); + return success(true); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFacePackUserRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFacePackUserRespVO.java new file mode 100644 index 0000000..30bca68 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFacePackUserRespVO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.im.controller.admin.face.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "IM 表情包(用户端) Response VO") +@Data +public class ImFacePackUserRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子") + private String name; + + @Schema(description = "表情包图标 URL", example = "https://cdn.example.com/face/pack/cat.png") + private String iconUrl; + + @Schema(description = "表情列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "IM 表情包项(用户端)") + @Data + public static class Item { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long id; + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/pack/cat-001.png") + private String url; + + @Schema(description = "表情名", example = "狗头") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer height; + + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemRespVO.java new file mode 100644 index 0000000..0357f8c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemRespVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.im.controller.admin.face.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "IM 个人表情 Response VO") +@Data +public class ImFaceUserItemRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") + private Long id; + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/user/abc.gif") + private String url; + + @Schema(description = "表情名", example = "狗头") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer height; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemSaveReqVO.java new file mode 100644 index 0000000..0d5c9ec --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemSaveReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.im.controller.admin.face.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "IM 个人表情新增 Request VO") +@Data +public class ImFaceUserItemSaveReqVO { + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/user/abc.gif") + @NotBlank(message = "表情图 URL 不能为空") + @Size(max = 512, message = "表情图 URL 长度不能超过 512") + private String url; + + @Schema(description = "表情名", example = "狗头") + @Size(max = 64, message = "表情名长度不能超过 64") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "渲染宽度不能为空") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "渲染高度不能为空") + private Integer height; + + @Schema(description = "来源消息编号(从消息「添加到表情」时传入;自己上传则不传)", example = "10086") + private Long sourceMessageId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.java new file mode 100644 index 0000000..637cd47 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import cn.iocoder.yudao.module.im.service.face.ImFacePackItemService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 表情包项") +@RestController +@RequestMapping("/im/manager/face-pack-item") +@Validated +public class ImFacePackItemManagerController { + + @Resource + private ImFacePackItemService facePackItemService; + + @PostMapping("/create") + @Operation(summary = "新增表情") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:create')") + public CommonResult createFacePackItem(@Valid @RequestBody ImFacePackItemSaveReqVO reqVO) { + return success(facePackItemService.createFacePackItem(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改表情") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:update')") + public CommonResult updateFacePackItem(@Valid @RequestBody ImFacePackItemSaveReqVO reqVO) { + facePackItemService.updateFacePackItem(reqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除表情") + @Parameter(name = "id", description = "编号", required = true, example = "2048") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:delete')") + public CommonResult deleteFacePackItem(@RequestParam("id") Long id) { + facePackItemService.deleteFacePackItem(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除表情") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:delete')") + public CommonResult deleteFacePackItemList(@RequestParam("ids") List ids) { + facePackItemService.deleteFacePackItemList(ids); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得表情分页") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:query')") + public CommonResult> getFacePackItemPage(@Valid ImFacePackItemPageReqVO pageReqVO) { + PageResult pageResult = facePackItemService.getFacePackItemPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ImFacePackItemRespVO.class)); + } + + @GetMapping("/get") + @Operation(summary = "获得表情详情") + @Parameter(name = "id", description = "编号", required = true, example = "2048") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:query')") + public CommonResult getFacePackItem(@RequestParam("id") Long id) { + ImFacePackItemDO item = facePackItemService.getFacePackItem(id); + return success(BeanUtils.toBean(item, ImFacePackItemRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.java new file mode 100644 index 0000000..0fabe93 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import cn.iocoder.yudao.module.im.service.face.ImFacePackService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 表情包") +@RestController +@RequestMapping("/im/manager/face-pack") +@Validated +public class ImFacePackManagerController { + + @Resource + private ImFacePackService facePackService; + + @PostMapping("/create") + @Operation(summary = "新增表情包") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:create')") + public CommonResult createFacePack(@Valid @RequestBody ImFacePackSaveReqVO reqVO) { + return success(facePackService.createFacePack(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改表情包") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:update')") + public CommonResult updateFacePack(@Valid @RequestBody ImFacePackSaveReqVO reqVO) { + facePackService.updateFacePack(reqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除表情包") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:delete')") + public CommonResult deleteFacePack(@RequestParam("id") Long id) { + facePackService.deleteFacePack(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除表情包") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:delete')") + public CommonResult deleteFacePackList(@RequestParam("ids") List ids) { + facePackService.deleteFacePackList(ids); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得表情包分页") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:query')") + public CommonResult> getFacePackPage(@Valid ImFacePackPageReqVO pageReqVO) { + PageResult pageResult = facePackService.getFacePackPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ImFacePackRespVO.class)); + } + + @GetMapping("/get") + @Operation(summary = "获得表情包详情") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:query')") + public CommonResult getFacePack(@RequestParam("id") Long id) { + ImFacePackDO pack = facePackService.getFacePack(id); + return success(BeanUtils.toBean(pack, ImFacePackRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemPageReqVO.java new file mode 100644 index 0000000..39fd7a6 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemPageReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +// TODO @AI:拆分到 vo/item 目录下,如果是 item 的话;更简练; +@Schema(description = "管理后台 - IM 表情包项分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImFacePackItemPageReqVO extends PageParam { + + @Schema(description = "所属表情包编号", example = "1024") + private Long packId; + + @Schema(description = "表情名,模糊匹配", example = "狗") + private String name; + + @Schema(description = "状态", example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemRespVO.java new file mode 100644 index 0000000..3ff0949 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemRespVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 表情包项 Response VO") +@Data +public class ImFacePackItemRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long id; + + @Schema(description = "所属表情包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long packId; + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/pack/cat-001.png") + private String url; + + @Schema(description = "表情名", example = "狗头") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer height; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemSaveReqVO.java new file mode 100644 index 0000000..c8a2b9a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemSaveReqVO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 表情包项新增 / 修改 Request VO") +@Data +public class ImFacePackItemSaveReqVO { + + @Schema(description = "编号(修改时必填)", example = "2048") + private Long id; + + @Schema(description = "所属表情包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "表情包编号不能为空") + private Long packId; + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/pack/cat-001.png") + @NotBlank(message = "表情图 URL 不能为空") + @Size(max = 512, message = "表情图 URL 长度不能超过 512") + private String url; + + @Schema(description = "表情名", example = "狗头") + @Size(max = 64, message = "表情名长度不能超过 64") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "渲染宽度不能为空") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "渲染高度不能为空") + private Integer height; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackPageReqVO.java new file mode 100644 index 0000000..13c63c2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackPageReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - IM 表情包分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImFacePackPageReqVO extends PageParam { + + @Schema(description = "表情包名称,模糊匹配", example = "猫") + private String name; + + @Schema(description = "状态", example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackRespVO.java new file mode 100644 index 0000000..33bd208 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackRespVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 表情包 Response VO") +@Data +public class ImFacePackRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子") + private String name; + + @Schema(description = "表情包图标 URL", example = "https://cdn.example.com/face/pack/cat.png") + private String iconUrl; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackSaveReqVO.java new file mode 100644 index 0000000..915b89e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackSaveReqVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 表情包新增 / 修改 Request VO") +@Data +public class ImFacePackSaveReqVO { + + @Schema(description = "编号(修改时必填)", example = "1024") + private Long id; + + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子") + @NotBlank(message = "表情包名称不能为空") + @Size(max = 64, message = "表情包名称长度不能超过 64") + private String name; + + @Schema(description = "表情包图标 URL", example = "https://cdn.example.com/face/pack/cat.png") + @Size(max = 512, message = "图标 URL 长度不能超过 512") + private String iconUrl; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java new file mode 100644 index 0000000..5722e10 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.face; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IM 表情包 DO(运营配置的系统表情包元数据) + * + * @author 芋道源码 + */ +@TableName("im_face_pack") +@KeySequence("im_face_pack_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImFacePackDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 表情包名称 + */ + private String name; + // TODO @AI:icon 一般不带 url;类似 avatar,特有单词; + /** + * 表情包图标 URL + *

+ * 面板底部 tab 栏显示 + */ + private String iconUrl; + /** + * 排序 + */ + private Integer sort; + /** + * 状态 + *

+ * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackItemDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackItemDO.java new file mode 100644 index 0000000..d3c5676 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackItemDO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.face; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IM 表情包项 DO(系统表情包内的单张表情图) + * + * @author 芋道源码 + */ +@TableName("im_face_pack_item") +@KeySequence("im_face_pack_item_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImFacePackItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 所属表情包编号 + */ + private Long packId; + /** + * 表情图 URL + */ + private String url; + /** + * 表情名(可选;如「狗头」「捂脸」) + */ + private String name; + /** + * 渲染宽度(像素) + */ + private Integer width; + /** + * 渲染高度(像素) + */ + private Integer height; + /** + * 排序 + */ + private Integer sort; + /** + * 状态 + *

+ * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java new file mode 100644 index 0000000..8ddd826 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.face; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IM 用户私有表情 DO(个人表情包,对照微信「我的表情」) + * + * @author 芋道源码 + */ +@TableName("im_face_user_item") +@KeySequence("im_face_user_item_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImFaceUserItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 所属用户编号 + * + * // TODO @AI:是不是要关联下 userid + */ + private Long userId; + /** + * 表情图 URL + */ + private String url; + /** + * 表情名(可选) + *

+ * 用户私有表情通常不带名字,留字段以备将来「重命名」交互 + */ + private String name; + /** + * 渲染宽度(像素) + */ + private Integer width; + /** + * 渲染高度(像素) + */ + private Integer height; + /** + * 来源消息编号 + *

+ * 从消息「添加到表情」时记录;自己上传则为 NULL;用于排查 / 后续溯源 + * + * TODO @AI:关联字段; + */ + private Long sourceMessageId; + /** + * 排序 + */ + private Integer sort; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java new file mode 100644 index 0000000..1fa2922 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.im.dal.mysql.face; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * IM 表情包项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImFacePackItemMapper extends BaseMapperX { + + default List selectListByPackIdsAndStatus(Collection packIds, Integer status) { + return selectList(new LambdaQueryWrapperX() + .inIfPresent(ImFacePackItemDO::getPackId, packIds) + .eq(ImFacePackItemDO::getStatus, status) + .orderByAsc(ImFacePackItemDO::getPackId) + .orderByAsc(ImFacePackItemDO::getSort) + .orderByAsc(ImFacePackItemDO::getId)); + } + + default Long selectCountByPackId(Long packId) { + return selectCount(new LambdaQueryWrapperX() + .eq(ImFacePackItemDO::getPackId, packId)); + } + + default Long selectCountByPackIds(Collection packIds) { + return selectCount(new LambdaQueryWrapperX() + .in(ImFacePackItemDO::getPackId, packIds)); + } + + default PageResult selectPage(ImFacePackItemPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImFacePackItemDO::getPackId, reqVO.getPackId()) + .likeIfPresent(ImFacePackItemDO::getName, reqVO.getName()) + .eqIfPresent(ImFacePackItemDO::getStatus, reqVO.getStatus()) + .orderByAsc(ImFacePackItemDO::getSort) + .orderByDesc(ImFacePackItemDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackMapper.java new file mode 100644 index 0000000..f1e339b --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackMapper.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.im.dal.mysql.face; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * IM 表情包 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImFacePackMapper extends BaseMapperX { + + default List selectListByStatusOrderBySort(Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(ImFacePackDO::getStatus, status) + .orderByAsc(ImFacePackDO::getSort) + .orderByAsc(ImFacePackDO::getId)); + } + + default PageResult selectPage(ImFacePackPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ImFacePackDO::getName, reqVO.getName()) + .eqIfPresent(ImFacePackDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(ImFacePackDO::getCreateTime, reqVO.getCreateTime()) + .orderByAsc(ImFacePackDO::getSort) + .orderByDesc(ImFacePackDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java new file mode 100644 index 0000000..6445af0 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.dal.mysql.face; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * IM 用户私有表情 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImFaceUserItemMapper extends BaseMapperX { + + /** 取用户的所有个人表情,按 sort 升序、id 倒序(最近添加在前) */ + default List selectListByUserId(Long userId) { + return selectList(new LambdaQueryWrapperX() + .eq(ImFaceUserItemDO::getUserId, userId) + .orderByAsc(ImFaceUserItemDO::getSort) + .orderByDesc(ImFaceUserItemDO::getId)); + } + + default Long selectCountByUserId(Long userId) { + return selectCount(new LambdaQueryWrapperX() + .eq(ImFaceUserItemDO::getUserId, userId)); + } + + default ImFaceUserItemDO selectByUserIdAndUrl(Long userId, String url) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImFaceUserItemDO::getUserId, userId) + .eq(ImFaceUserItemDO::getUrl, url)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java index 56e007f..198ecb8 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java @@ -49,6 +49,12 @@ public enum ImMessageTypeEnum implements ArrayValuable { * 场景:把好友名片推荐给其他会话,对端点击名片可打开 UserInfoCard */ CARD(108, "名片", true, true), + /** + * 对应 OpenIM:Face 115 + * 对应自己的类:FaceMessage + * 场景:表情贴图(运营配置的系统表情包 + 用户私有表情包);Unicode emoji 仍走 TEXT + */ + FACE(115, "表情", true, true), // ========== 信号类(2101 / 2200 直接复用 OpenIM 段位编号;2201 自有扩展) ========== /** @@ -142,9 +148,10 @@ public enum ImMessageTypeEnum implements ArrayValuable { GROUP_INFO_UPDATE(1502, "群信息变更", true, false), /** * 对应 OpenIM:sdkws.JoinGroupApplicationTips(JoinGroupApplicationNotification 1503) - * TODO @AI 未实现:入群申请;本期不在范围,预留段位以便未来对齐 OpenIM + * 对应自己的类:GroupRequestNotification + * 场景:用户申请加群 / 普通成员邀请待审批,定向私聊推送给群主 + 全部管理员(多端同步);不入群消息流 */ - GROUP_JOIN_APPLICATION(1503, "入群申请", true, false), + GROUP_REQUEST_RECEIVED(1503, "收到新的入群申请", false, false), /** * 对应 OpenIM:sdkws.MemberQuitTips(MemberQuitNotification 1504) * 对应自己的类:GroupMemberQuitNotification @@ -153,14 +160,16 @@ public enum ImMessageTypeEnum implements ArrayValuable { GROUP_MEMBER_QUIT(1504, "成员退群", true, false), /** * 对应 OpenIM:sdkws.GroupApplicationAcceptedTips(GroupApplicationAcceptedNotification 1505) - * TODO @AI 未实现:入群申请通过;本期不在范围 + * 对应自己的类:GroupRequestApprovedNotification + * 场景:群主 / 管理员同意申请,定向私聊推送给申请人 + 群主 + 全部管理员;申请人侧弹 toast,admin 侧 pendingRequestCount-1;不入群消息流 */ - GROUP_APPLICATION_ACCEPTED(1505, "入群申请通过", true, false), + GROUP_REQUEST_APPROVED(1505, "入群申请被同意", false, false), /** * 对应 OpenIM:sdkws.GroupApplicationRejectedTips(GroupApplicationRejectedNotification 1506) - * TODO @AI 未实现:入群申请拒绝;本期不在范围 + * 对应自己的类:GroupRequestRejectedNotification + * 场景:群主 / 管理员拒绝申请,定向私聊推送给申请人 + 群主 + 全部管理员;不入群消息流 */ - GROUP_APPLICATION_REJECTED(1506, "入群申请拒绝", true, false), + GROUP_REQUEST_REJECTED(1506, "入群申请被拒绝", false, false), /** * 对应 OpenIM:sdkws.GroupOwnerTransferredTips(GroupOwnerTransferredNotification 1507) * 对应自己的类:GroupOwnerTransferNotification @@ -181,7 +190,8 @@ public enum ImMessageTypeEnum implements ArrayValuable { GROUP_MEMBER_INVITE(1509, "成员加入", true, false), /** * 对应 OpenIM:sdkws.MemberEnterTips(MemberEnterNotification 1510) - * TODO @AI 未实现:自由进群(链接 / 二维码进群);本期不在范围 + * 对应自己的类:GroupMemberEnterNotification + * 场景:用户经搜索 / 二维码 / 分享链接自由进群(FREE 模式或审批通过后),全员广播;前端按 entrantUserId 局部添加成员 */ GROUP_MEMBER_ENTER(1510, "自由进群", true, false), /** diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java new file mode 100644 index 0000000..d57d0b2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * IM 表情包项 Service 接口 + * + * @author 芋道源码 + */ +public interface ImFacePackItemService { + + // ==================== 用户端 ==================== + + /** + * 【用户端】按 packIds 批量取启用项,给前端聚合接口用 + */ + List getEnabledItemListByPackIds(Collection packIds); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询 + */ + PageResult getFacePackItemPage(ImFacePackItemPageReqVO reqVO); + + /** + * 【管理后台】获取详情 + */ + ImFacePackItemDO getFacePackItem(Long id); + + /** + * 【管理后台】新增,返回新增 id + */ + Long createFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO); + + /** + * 【管理后台】修改 + */ + void updateFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO); + + /** + * 【管理后台】删除 + */ + void deleteFacePackItem(Long id); + + /** + * 【管理后台】批量删除 + */ + void deleteFacePackItemList(List ids); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImpl.java new file mode 100644 index 0000000..8eef861 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImpl.java @@ -0,0 +1,103 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackItemMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_ITEM_NOT_EXISTS; + +/** + * IM 表情包项 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImFacePackItemServiceImpl implements ImFacePackItemService { + + @Resource + private ImFacePackItemMapper facePackItemMapper; + @Resource + private ImFacePackService facePackService; + + // ==================== 用户端 ==================== + + @Override + public List getEnabledItemListByPackIds(Collection packIds) { + if (CollUtil.isEmpty(packIds)) { + return Collections.emptyList(); + } + return facePackItemMapper.selectListByPackIdsAndStatus(packIds, CommonStatusEnum.ENABLE.getStatus()); + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getFacePackItemPage(ImFacePackItemPageReqVO reqVO) { + return facePackItemMapper.selectPage(reqVO); + } + + @Override + public ImFacePackItemDO getFacePackItem(Long id) { + return facePackItemMapper.selectById(id); + } + + @Override + public Long createFacePackItem(ImFacePackItemSaveReqVO reqVO) { + // 校验所属表情包存在 + facePackService.validateFacePackExists(reqVO.getPackId()); + ImFacePackItemDO item = BeanUtils.toBean(reqVO, ImFacePackItemDO.class); + + // TODO @AI:注释风格,和别的模块一致 + facePackItemMapper.insert(item); + return item.getId(); + } + + @Override + public void updateFacePackItem(ImFacePackItemSaveReqVO reqVO) { + // TODO @AI:注释风格,和别的模块一致 + validateFacePackItemExists(reqVO.getId()); + // TODO @AI:注释风格,和别的模块一致 + facePackService.validateFacePackExists(reqVO.getPackId()); + + // TODO @AI:注释风格,和别的模块一致 + ImFacePackItemDO updateObj = BeanUtils.toBean(reqVO, ImFacePackItemDO.class); + facePackItemMapper.updateById(updateObj); + } + + @Override + public void deleteFacePackItem(Long id) { + // TODO @AI:注释风格,和别的模块一致 + validateFacePackItemExists(id); + // TODO @AI:注释风格,和别的模块一致 + facePackItemMapper.deleteById(id); + } + + @Override + public void deleteFacePackItemList(List ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + facePackItemMapper.deleteByIds(ids); + } + + private void validateFacePackItemExists(Long id) { + if (facePackItemMapper.selectById(id) == null) { + throw exception(FACE_PACK_ITEM_NOT_EXISTS); + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java new file mode 100644 index 0000000..7683b6f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * IM 表情包 Service 接口 + * + * @author 芋道源码 + */ +public interface ImFacePackService { + + // TODO @AI:已经区分了 === 就不用写【用户端】【管理后台】了。 + + // ==================== 用户端 ==================== + + /** + * 【用户端】获取所有启用的表情包,按 sort 升序 + */ + List getEnabledFacePackList(); + + /** + * 校验表情包存在 + */ + @SuppressWarnings("UnusedReturnValue") + ImFacePackDO validateFacePackExists(Long id); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询表情包 + */ + PageResult getFacePackPage(ImFacePackPageReqVO reqVO); + + /** + * 【管理后台】获取表情包详情 + */ + ImFacePackDO getFacePack(Long id); + + /** + * 【管理后台】新增表情包,返回新增 id + */ + Long createFacePack(@Valid ImFacePackSaveReqVO reqVO); + + /** + * 【管理后台】修改表情包 + */ + void updateFacePack(@Valid ImFacePackSaveReqVO reqVO); + + /** + * 【管理后台】删除表情包;存在表情时拒绝 + */ + void deleteFacePack(Long id); + + /** + * 【管理后台】批量删除表情包 + */ + void deleteFacePackList(List ids); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImpl.java new file mode 100644 index 0000000..950cb05 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImpl.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackItemMapper; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_HAS_ITEMS; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_NOT_EXISTS; + +/** + * IM 表情包 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImFacePackServiceImpl implements ImFacePackService { + + @Resource + private ImFacePackMapper facePackMapper; + + // TODO @AI:跨模块操作,需要调用对方 service; + @Resource + private ImFacePackItemMapper facePackItemMapper; + + // ==================== 用户端 ==================== + + @Override + public List getEnabledFacePackList() { + return facePackMapper.selectListByStatusOrderBySort(CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public ImFacePackDO validateFacePackExists(Long id) { + ImFacePackDO pack = facePackMapper.selectById(id); + if (pack == null) { + throw exception(FACE_PACK_NOT_EXISTS); + } + return pack; + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getFacePackPage(ImFacePackPageReqVO reqVO) { + return facePackMapper.selectPage(reqVO); + } + + @Override + public ImFacePackDO getFacePack(Long id) { + return facePackMapper.selectById(id); + } + + @Override + public Long createFacePack(ImFacePackSaveReqVO reqVO) { + ImFacePackDO pack = BeanUtils.toBean(reqVO, ImFacePackDO.class); + facePackMapper.insert(pack); + return pack.getId(); + } + + @Override + public void updateFacePack(ImFacePackSaveReqVO reqVO) { + // TODO @AI:注释,参考别的模块 + validateFacePackExists(reqVO.getId()); + + // TODO @AI:注释,参考别的模块 + ImFacePackDO updateObj = BeanUtils.toBean(reqVO, ImFacePackDO.class); + facePackMapper.updateById(updateObj); + } + + @Override + public void deleteFacePack(Long id) { + // 1.1 校验存在 + validateFacePackExists(id); + // 1.2 校验表情包下没有表情;防止误删表情包导致历史 face 消息无法回查归属 + if (facePackItemMapper.selectCountByPackId(id) > 0) { + throw exception(FACE_PACK_HAS_ITEMS); + } + + // 2. 删除 + facePackMapper.deleteById(id); + } + + @Override + public void deleteFacePackList(List ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + // 任一存在表情则拒绝整批删除,避免「只删一半」的中间态 + if (facePackItemMapper.selectCountByPackIds(ids) > 0) { + throw exception(FACE_PACK_HAS_ITEMS); + } + + // 执行删除 + facePackMapper.deleteByIds(ids); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java new file mode 100644 index 0000000..8284f0f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFaceUserItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * IM 用户私有表情 Service 接口 + * + * @author 芋道源码 + */ +public interface ImFaceUserItemService { + + /** + * 获取我的个人表情列表 + */ + List getMyFaceUserItemList(Long userId); + + /** + * 添加个人表情;返回新增 id(已存在则直接返回旧 id) + *

+ * 同 URL 重复添加时返回旧记录 id,不报错 + */ + Long createFaceUserItem(Long userId, @Valid ImFaceUserItemSaveReqVO reqVO); + + /** + * 删除我的某条个人表情 + */ + void deleteFaceUserItem(Long userId, Long id); + + /** + * 批量删除我的个人表情 + */ + void deleteFaceUserItemList(Long userId, List ids); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java new file mode 100644 index 0000000..7625def --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFaceUserItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFaceUserItemMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_NOT_EXISTS; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_NOT_OWN; + +/** + * IM 用户私有表情 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImFaceUserItemServiceImpl implements ImFaceUserItemService { + + @Resource + private ImFaceUserItemMapper faceUserItemMapper; + + // TODO @AI:传递了 userid,就不用写 myXXX 了; + @Override + public List getMyFaceUserItemList(Long userId) { + return faceUserItemMapper.selectListByUserId(userId); + } + + @Override + public Long createFaceUserItem(Long userId, ImFaceUserItemSaveReqVO reqVO) { + // 1. 同 URL 已添加:直接返回旧 id;避免一张图反复添加占额度,并保证「添加到表情」幂等 + // TODO @AI:是不是提示下,已经有这个标签库了。。。 + ImFaceUserItemDO exist = faceUserItemMapper.selectByUserIdAndUrl(userId, reqVO.getUrl()); + if (exist != null) { + return exist.getId(); + } + + // 2. 入库 + // TODO @AI:链式调用 + ImFaceUserItemDO item = BeanUtils.toBean(reqVO, ImFaceUserItemDO.class); + item.setUserId(userId); + item.setSort(0); + faceUserItemMapper.insert(item); + return item.getId(); + } + + @Override + public void deleteFaceUserItem(Long userId, Long id) { + ImFaceUserItemDO item = faceUserItemMapper.selectById(id); + if (item == null) { + throw exception(FACE_USER_ITEM_NOT_EXISTS); + } + // 防止 A 用户传 B 用户的表情 id 删除别人的表情 + // TODO @AI:notEquals; + if (!item.getUserId().equals(userId)) { + throw exception(FACE_USER_ITEM_NOT_OWN); + } + + // TODO @AI:注释 + faceUserItemMapper.deleteById(id); + } + + @Override + public void deleteFaceUserItemList(Long userId, List ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + // 一次性查全部待删项;任一不属于当前用户则整批拒绝 + // TODO @AI:过滤掉,不属于自己的; + List items = faceUserItemMapper.selectByIds(ids); + // TODO @AI:不存在的,直接跳过; + if (CollUtil.size(items) != CollUtil.size(ids)) { + throw exception(FACE_USER_ITEM_NOT_EXISTS); + } + for (ImFaceUserItemDO item : items) { + if (!item.getUserId().equals(userId)) { + throw exception(FACE_USER_ITEM_NOT_OWN); + } + } + + // TODO @AI:注释 + faceUserItemMapper.deleteByIds(ids); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java new file mode 100644 index 0000000..2c1b044 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.im.service.websocket.dto.message; + +import lombok.Data; + +/** + * 表情消息内容 + *

+ * 承载「贴图 / 自定义表情包」类消息;Unicode emoji(😀😂)仍走 TEXT + */ +@Data +public class FaceMessage { + + /** + * 表情图 URL + */ + private String url; + // TODO @AI:openim 有这个字段么?不是一般 url 就足够了呀。 + /** + * 渲染宽度(像素) + *

+ * 客户端渲染时按此尺寸占位,避免图片加载完成后的布局抖动 + */ + private Integer width; + /** + * 渲染高度(像素) + */ + private Integer height; + /** + * 表情名(可选) + *

+ * 系统表情包通常带名字(如「狗头」「捂脸」),用户私有表情包通常为空 + */ + private String name; + + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + +} -- libgit2 0.21.4