Commit 8a4ad959d6e67755f424d858246cf6c1ffb1294c

Authored by YunaiV
1 parent b320e3c1

✨ feat(im): 初始化表情包 v0.0:第一把 review

Showing 27 changed files with 1415 additions and 7 deletions
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFacePackController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.face;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFacePackUserRespVO;
  6 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO;
  7 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO;
  8 +import cn.iocoder.yudao.module.im.service.face.ImFacePackItemService;
  9 +import cn.iocoder.yudao.module.im.service.face.ImFacePackService;
  10 +import io.swagger.v3.oas.annotations.Operation;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.GetMapping;
  15 +import org.springframework.web.bind.annotation.RequestMapping;
  16 +import org.springframework.web.bind.annotation.RestController;
  17 +
  18 +import java.util.List;
  19 +import java.util.Map;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  23 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
  24 +
  25 +@Tag(name = "管理后台 - IM 表情包")
  26 +@RestController
  27 +@RequestMapping("/im/face-pack")
  28 +@Validated
  29 +public class ImFacePackController {
  30 +
  31 + @Resource
  32 + private ImFacePackService facePackService;
  33 + @Resource
  34 + private ImFacePackItemService facePackItemService;
  35 +
  36 + @GetMapping("/list")
  37 + @Operation(summary = "获得启用的表情包列表(含表情)")
  38 + public CommonResult<List<ImFacePackUserRespVO>> getFacePackList() {
  39 + // 1.1 拉所有启用表情包
  40 + List<ImFacePackDO> packs = facePackService.getEnabledFacePackList();
  41 + if (packs.isEmpty()) {
  42 + return success(List.of());
  43 + }
  44 + // 1.2 拉这些包下所有启用表情,按 packId 分组
  45 + List<ImFacePackItemDO> items = facePackItemService.getEnabledItemListByPackIds(
  46 + convertList(packs, ImFacePackDO::getId));
  47 + Map<Long, List<ImFacePackItemDO>> itemsByPackId = convertMultiMap(items, ImFacePackItemDO::getPackId);
  48 +
  49 + // 2. 拼装:BeanUtils 把 pack 字段映射 + 自己塞 items
  50 + List<ImFacePackUserRespVO> result = convertList(packs, pack -> {
  51 + ImFacePackUserRespVO vo = BeanUtils.toBean(pack, ImFacePackUserRespVO.class);
  52 + vo.setItems(BeanUtils.toBean(itemsByPackId.getOrDefault(pack.getId(), List.of()), ImFacePackUserRespVO.Item.class));
  53 + return vo;
  54 + });
  55 + return success(result);
  56 + }
  57 +
  58 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.face;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFaceUserItemRespVO;
  6 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFaceUserItemSaveReqVO;
  7 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
  8 +import cn.iocoder.yudao.module.im.service.face.ImFaceUserItemService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.Parameter;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import jakarta.validation.Valid;
  14 +import org.springframework.validation.annotation.Validated;
  15 +import org.springframework.web.bind.annotation.*;
  16 +
  17 +import java.util.List;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
  21 +
  22 +@Tag(name = "管理后台 - IM 个人表情")
  23 +@RestController
  24 +@RequestMapping("/im/face-user-item")
  25 +@Validated
  26 +public class ImFaceUserItemController {
  27 +
  28 + @Resource
  29 + private ImFaceUserItemService faceUserItemService;
  30 +
  31 + @GetMapping("/list")
  32 + @Operation(summary = "获得我的个人表情列表")
  33 + public CommonResult<List<ImFaceUserItemRespVO>> getMyFaceUserItemList() {
  34 + List<ImFaceUserItemDO> items = faceUserItemService.getMyFaceUserItemList(getLoginUserId());
  35 + return success(BeanUtils.toBean(items, ImFaceUserItemRespVO.class));
  36 + }
  37 +
  38 + @PostMapping("/create")
  39 + @Operation(summary = "添加个人表情")
  40 + public CommonResult<Long> createFaceUserItem(@Valid @RequestBody ImFaceUserItemSaveReqVO reqVO) {
  41 + return success(faceUserItemService.createFaceUserItem(getLoginUserId(), reqVO));
  42 + }
  43 +
  44 + @DeleteMapping("/delete")
  45 + @Operation(summary = "删除个人表情")
  46 + @Parameter(name = "id", description = "编号", required = true, example = "4096")
  47 + public CommonResult<Boolean> deleteFaceUserItem(@RequestParam("id") Long id) {
  48 + faceUserItemService.deleteFaceUserItem(getLoginUserId(), id);
  49 + return success(true);
  50 + }
  51 +
  52 + // TODO @AI:个人应该没批量删除把?应该就一个一个删除呢。
  53 + @DeleteMapping("/delete-list")
  54 + @Operation(summary = "批量删除个人表情")
  55 + @Parameter(name = "ids", description = "编号列表", required = true)
  56 + public CommonResult<Boolean> deleteFaceUserItemList(@RequestParam("ids") List<Long> ids) {
  57 + faceUserItemService.deleteFaceUserItemList(getLoginUserId(), ids);
  58 + return success(true);
  59 + }
  60 +
  61 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFacePackUserRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.face.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +@Schema(description = "IM 表情包(用户端) Response VO")
  9 +@Data
  10 +public class ImFacePackUserRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子")
  16 + private String name;
  17 +
  18 + @Schema(description = "表情包图标 URL", example = "https://cdn.example.com/face/pack/cat.png")
  19 + private String iconUrl;
  20 +
  21 + @Schema(description = "表情列表", requiredMode = Schema.RequiredMode.REQUIRED)
  22 + private List<Item> items;
  23 +
  24 + @Schema(description = "IM 表情包项(用户端)")
  25 + @Data
  26 + public static class Item {
  27 +
  28 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  29 + private Long id;
  30 +
  31 + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  32 + example = "https://cdn.example.com/face/pack/cat-001.png")
  33 + private String url;
  34 +
  35 + @Schema(description = "表情名", example = "狗头")
  36 + private String name;
  37 +
  38 + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  39 + private Integer width;
  40 +
  41 + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  42 + private Integer height;
  43 +
  44 + }
  45 +
  46 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.face.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "IM 个人表情 Response VO")
  7 +@Data
  8 +public class ImFaceUserItemRespVO {
  9 +
  10 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
  11 + private Long id;
  12 +
  13 + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  14 + example = "https://cdn.example.com/face/user/abc.gif")
  15 + private String url;
  16 +
  17 + @Schema(description = "表情名", example = "狗头")
  18 + private String name;
  19 +
  20 + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  21 + private Integer width;
  22 +
  23 + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  24 + private Integer height;
  25 +
  26 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/ImFaceUserItemSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.face.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "IM 个人表情新增 Request VO")
  10 +@Data
  11 +public class ImFaceUserItemSaveReqVO {
  12 +
  13 + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  14 + example = "https://cdn.example.com/face/user/abc.gif")
  15 + @NotBlank(message = "表情图 URL 不能为空")
  16 + @Size(max = 512, message = "表情图 URL 长度不能超过 512")
  17 + private String url;
  18 +
  19 + @Schema(description = "表情名", example = "狗头")
  20 + @Size(max = 64, message = "表情名长度不能超过 64")
  21 + private String name;
  22 +
  23 + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  24 + @NotNull(message = "渲染宽度不能为空")
  25 + private Integer width;
  26 +
  27 + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  28 + @NotNull(message = "渲染高度不能为空")
  29 + private Integer height;
  30 +
  31 + @Schema(description = "来源消息编号(从消息「添加到表情」时传入;自己上传则不传)", example = "10086")
  32 + private Long sourceMessageId;
  33 +
  34 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.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.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemPageReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemRespVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemSaveReqVO;
  9 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO;
  10 +import cn.iocoder.yudao.module.im.service.face.ImFacePackItemService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.*;
  19 +
  20 +import java.util.List;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  23 +
  24 +@Tag(name = "管理后台 - IM 表情包项")
  25 +@RestController
  26 +@RequestMapping("/im/manager/face-pack-item")
  27 +@Validated
  28 +public class ImFacePackItemManagerController {
  29 +
  30 + @Resource
  31 + private ImFacePackItemService facePackItemService;
  32 +
  33 + @PostMapping("/create")
  34 + @Operation(summary = "新增表情")
  35 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:create')")
  36 + public CommonResult<Long> createFacePackItem(@Valid @RequestBody ImFacePackItemSaveReqVO reqVO) {
  37 + return success(facePackItemService.createFacePackItem(reqVO));
  38 + }
  39 +
  40 + @PutMapping("/update")
  41 + @Operation(summary = "修改表情")
  42 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:update')")
  43 + public CommonResult<Boolean> updateFacePackItem(@Valid @RequestBody ImFacePackItemSaveReqVO reqVO) {
  44 + facePackItemService.updateFacePackItem(reqVO);
  45 + return success(true);
  46 + }
  47 +
  48 + @DeleteMapping("/delete")
  49 + @Operation(summary = "删除表情")
  50 + @Parameter(name = "id", description = "编号", required = true, example = "2048")
  51 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:delete')")
  52 + public CommonResult<Boolean> deleteFacePackItem(@RequestParam("id") Long id) {
  53 + facePackItemService.deleteFacePackItem(id);
  54 + return success(true);
  55 + }
  56 +
  57 + @DeleteMapping("/delete-list")
  58 + @Operation(summary = "批量删除表情")
  59 + @Parameter(name = "ids", description = "编号列表", required = true)
  60 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:delete')")
  61 + public CommonResult<Boolean> deleteFacePackItemList(@RequestParam("ids") List<Long> ids) {
  62 + facePackItemService.deleteFacePackItemList(ids);
  63 + return success(true);
  64 + }
  65 +
  66 + @GetMapping("/page")
  67 + @Operation(summary = "获得表情分页")
  68 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:query')")
  69 + public CommonResult<PageResult<ImFacePackItemRespVO>> getFacePackItemPage(@Valid ImFacePackItemPageReqVO pageReqVO) {
  70 + PageResult<ImFacePackItemDO> pageResult = facePackItemService.getFacePackItemPage(pageReqVO);
  71 + return success(BeanUtils.toBean(pageResult, ImFacePackItemRespVO.class));
  72 + }
  73 +
  74 + @GetMapping("/get")
  75 + @Operation(summary = "获得表情详情")
  76 + @Parameter(name = "id", description = "编号", required = true, example = "2048")
  77 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:query')")
  78 + public CommonResult<ImFacePackItemRespVO> getFacePackItem(@RequestParam("id") Long id) {
  79 + ImFacePackItemDO item = facePackItemService.getFacePackItem(id);
  80 + return success(BeanUtils.toBean(item, ImFacePackItemRespVO.class));
  81 + }
  82 +
  83 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.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.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackPageReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackRespVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackSaveReqVO;
  9 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO;
  10 +import cn.iocoder.yudao.module.im.service.face.ImFacePackService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.*;
  19 +
  20 +import java.util.List;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  23 +
  24 +@Tag(name = "管理后台 - IM 表情包")
  25 +@RestController
  26 +@RequestMapping("/im/manager/face-pack")
  27 +@Validated
  28 +public class ImFacePackManagerController {
  29 +
  30 + @Resource
  31 + private ImFacePackService facePackService;
  32 +
  33 + @PostMapping("/create")
  34 + @Operation(summary = "新增表情包")
  35 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:create')")
  36 + public CommonResult<Long> createFacePack(@Valid @RequestBody ImFacePackSaveReqVO reqVO) {
  37 + return success(facePackService.createFacePack(reqVO));
  38 + }
  39 +
  40 + @PutMapping("/update")
  41 + @Operation(summary = "修改表情包")
  42 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:update')")
  43 + public CommonResult<Boolean> updateFacePack(@Valid @RequestBody ImFacePackSaveReqVO reqVO) {
  44 + facePackService.updateFacePack(reqVO);
  45 + return success(true);
  46 + }
  47 +
  48 + @DeleteMapping("/delete")
  49 + @Operation(summary = "删除表情包")
  50 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  51 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:delete')")
  52 + public CommonResult<Boolean> deleteFacePack(@RequestParam("id") Long id) {
  53 + facePackService.deleteFacePack(id);
  54 + return success(true);
  55 + }
  56 +
  57 + @DeleteMapping("/delete-list")
  58 + @Operation(summary = "批量删除表情包")
  59 + @Parameter(name = "ids", description = "编号列表", required = true)
  60 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:delete')")
  61 + public CommonResult<Boolean> deleteFacePackList(@RequestParam("ids") List<Long> ids) {
  62 + facePackService.deleteFacePackList(ids);
  63 + return success(true);
  64 + }
  65 +
  66 + @GetMapping("/page")
  67 + @Operation(summary = "获得表情包分页")
  68 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:query')")
  69 + public CommonResult<PageResult<ImFacePackRespVO>> getFacePackPage(@Valid ImFacePackPageReqVO pageReqVO) {
  70 + PageResult<ImFacePackDO> pageResult = facePackService.getFacePackPage(pageReqVO);
  71 + return success(BeanUtils.toBean(pageResult, ImFacePackRespVO.class));
  72 + }
  73 +
  74 + @GetMapping("/get")
  75 + @Operation(summary = "获得表情包详情")
  76 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  77 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:query')")
  78 + public CommonResult<ImFacePackRespVO> getFacePack(@RequestParam("id") Long id) {
  79 + ImFacePackDO pack = facePackService.getFacePack(id);
  80 + return success(BeanUtils.toBean(pack, ImFacePackRespVO.class));
  81 + }
  82 +
  83 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +
  8 +// TODO @AI:拆分到 vo/item 目录下,如果是 item 的话;更简练;
  9 +@Schema(description = "管理后台 - IM 表情包项分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +public class ImFacePackItemPageReqVO extends PageParam {
  13 +
  14 + @Schema(description = "所属表情包编号", example = "1024")
  15 + private Long packId;
  16 +
  17 + @Schema(description = "表情名,模糊匹配", example = "狗")
  18 + private String name;
  19 +
  20 + @Schema(description = "状态", example = "0")
  21 + private Integer status; // 参见 CommonStatusEnum 枚举类
  22 +
  23 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 表情包项 Response VO")
  9 +@Data
  10 +public class ImFacePackItemRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  13 + private Long id;
  14 +
  15 + @Schema(description = "所属表情包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long packId;
  17 +
  18 + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  19 + example = "https://cdn.example.com/face/pack/cat-001.png")
  20 + private String url;
  21 +
  22 + @Schema(description = "表情名", example = "狗头")
  23 + private String name;
  24 +
  25 + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  26 + private Integer width;
  27 +
  28 + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  29 + private Integer height;
  30 +
  31 + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  32 + private Integer sort;
  33 +
  34 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  35 + private Integer status; // 参见 CommonStatusEnum 枚举类
  36 +
  37 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  38 + private LocalDateTime createTime;
  39 +
  40 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackItemSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - IM 表情包项新增 / 修改 Request VO")
  10 +@Data
  11 +public class ImFacePackItemSaveReqVO {
  12 +
  13 + @Schema(description = "编号(修改时必填)", example = "2048")
  14 + private Long id;
  15 +
  16 + @Schema(description = "所属表情包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + @NotNull(message = "表情包编号不能为空")
  18 + private Long packId;
  19 +
  20 + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  21 + example = "https://cdn.example.com/face/pack/cat-001.png")
  22 + @NotBlank(message = "表情图 URL 不能为空")
  23 + @Size(max = 512, message = "表情图 URL 长度不能超过 512")
  24 + private String url;
  25 +
  26 + @Schema(description = "表情名", example = "狗头")
  27 + @Size(max = 64, message = "表情名长度不能超过 64")
  28 + private String name;
  29 +
  30 + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  31 + @NotNull(message = "渲染宽度不能为空")
  32 + private Integer width;
  33 +
  34 + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  35 + @NotNull(message = "渲染高度不能为空")
  36 + private Integer height;
  37 +
  38 + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  39 + @NotNull(message = "排序不能为空")
  40 + private Integer sort;
  41 +
  42 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  43 + @NotNull(message = "状态不能为空")
  44 + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)
  45 +
  46 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 表情包分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImFacePackPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "表情包名称,模糊匹配", example = "猫")
  19 + private String name;
  20 +
  21 + @Schema(description = "状态", example = "0")
  22 + private Integer status; // 参见 CommonStatusEnum 枚举类
  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/ImFacePackRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 表情包 Response VO")
  9 +@Data
  10 +public class ImFacePackRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子")
  16 + private String name;
  17 +
  18 + @Schema(description = "表情包图标 URL", example = "https://cdn.example.com/face/pack/cat.png")
  19 + private String iconUrl;
  20 +
  21 + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  22 + private Integer sort;
  23 +
  24 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  25 + private Integer status; // 参见 CommonStatusEnum 枚举类
  26 +
  27 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  28 + private LocalDateTime createTime;
  29 +
  30 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/ImFacePackSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - IM 表情包新增 / 修改 Request VO")
  10 +@Data
  11 +public class ImFacePackSaveReqVO {
  12 +
  13 + @Schema(description = "编号(修改时必填)", example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子")
  17 + @NotBlank(message = "表情包名称不能为空")
  18 + @Size(max = 64, message = "表情包名称长度不能超过 64")
  19 + private String name;
  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;
  24 +
  25 + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  26 + @NotNull(message = "排序不能为空")
  27 + private Integer sort;
  28 +
  29 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  30 + @NotNull(message = "状态不能为空")
  31 + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)
  32 +
  33 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.dataobject.face;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableId;
  7 +import com.baomidou.mybatisplus.annotation.TableName;
  8 +import lombok.*;
  9 +
  10 +/**
  11 + * IM 表情包 DO(运营配置的系统表情包元数据)
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@TableName("im_face_pack")
  16 +@KeySequence("im_face_pack_seq")
  17 +@Data
  18 +@EqualsAndHashCode(callSuper = true)
  19 +@ToString(callSuper = true)
  20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class ImFacePackDO extends BaseDO {
  24 +
  25 + /**
  26 + * 编号
  27 + */
  28 + @TableId
  29 + private Long id;
  30 + /**
  31 + * 表情包名称
  32 + */
  33 + private String name;
  34 + // TODO @AI:icon 一般不带 url;类似 avatar,特有单词;
  35 + /**
  36 + * 表情包图标 URL
  37 + * <p>
  38 + * 面板底部 tab 栏显示
  39 + */
  40 + private String iconUrl;
  41 + /**
  42 + * 排序
  43 + */
  44 + private Integer sort;
  45 + /**
  46 + * 状态
  47 + * <p>
  48 + * 枚举 {@link CommonStatusEnum}
  49 + */
  50 + private Integer status;
  51 +
  52 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackItemDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.dataobject.face;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableId;
  7 +import com.baomidou.mybatisplus.annotation.TableName;
  8 +import lombok.*;
  9 +
  10 +/**
  11 + * IM 表情包项 DO(系统表情包内的单张表情图)
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@TableName("im_face_pack_item")
  16 +@KeySequence("im_face_pack_item_seq")
  17 +@Data
  18 +@EqualsAndHashCode(callSuper = true)
  19 +@ToString(callSuper = true)
  20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class ImFacePackItemDO extends BaseDO {
  24 +
  25 + /**
  26 + * 编号
  27 + */
  28 + @TableId
  29 + private Long id;
  30 + /**
  31 + * 所属表情包编号
  32 + */
  33 + private Long packId;
  34 + /**
  35 + * 表情图 URL
  36 + */
  37 + private String url;
  38 + /**
  39 + * 表情名(可选;如「狗头」「捂脸」)
  40 + */
  41 + private String name;
  42 + /**
  43 + * 渲染宽度(像素)
  44 + */
  45 + private Integer width;
  46 + /**
  47 + * 渲染高度(像素)
  48 + */
  49 + private Integer height;
  50 + /**
  51 + * 排序
  52 + */
  53 + private Integer sort;
  54 + /**
  55 + * 状态
  56 + * <p>
  57 + * 枚举 {@link CommonStatusEnum}
  58 + */
  59 + private Integer status;
  60 +
  61 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.dataobject.face;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import com.baomidou.mybatisplus.annotation.KeySequence;
  5 +import com.baomidou.mybatisplus.annotation.TableId;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
  7 +import lombok.*;
  8 +
  9 +/**
  10 + * IM 用户私有表情 DO(个人表情包,对照微信「我的表情」)
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@TableName("im_face_user_item")
  15 +@KeySequence("im_face_user_item_seq")
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +@ToString(callSuper = true)
  19 +@Builder
  20 +@NoArgsConstructor
  21 +@AllArgsConstructor
  22 +public class ImFaceUserItemDO extends BaseDO {
  23 +
  24 + /**
  25 + * 编号
  26 + */
  27 + @TableId
  28 + private Long id;
  29 + /**
  30 + * 所属用户编号
  31 + *
  32 + * // TODO @AI:是不是要关联下 userid
  33 + */
  34 + private Long userId;
  35 + /**
  36 + * 表情图 URL
  37 + */
  38 + private String url;
  39 + /**
  40 + * 表情名(可选)
  41 + * <p>
  42 + * 用户私有表情通常不带名字,留字段以备将来「重命名」交互
  43 + */
  44 + private String name;
  45 + /**
  46 + * 渲染宽度(像素)
  47 + */
  48 + private Integer width;
  49 + /**
  50 + * 渲染高度(像素)
  51 + */
  52 + private Integer height;
  53 + /**
  54 + * 来源消息编号
  55 + * <p>
  56 + * 从消息「添加到表情」时记录;自己上传则为 NULL;用于排查 / 后续溯源
  57 + *
  58 + * TODO @AI:关联字段;
  59 + */
  60 + private Long sourceMessageId;
  61 + /**
  62 + * 排序
  63 + */
  64 + private Integer sort;
  65 +
  66 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.mysql.face;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemPageReqVO;
  7 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.Collection;
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * IM 表情包项 Mapper
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Mapper
  19 +public interface ImFacePackItemMapper extends BaseMapperX<ImFacePackItemDO> {
  20 +
  21 + default List<ImFacePackItemDO> selectListByPackIdsAndStatus(Collection<Long> packIds, Integer status) {
  22 + return selectList(new LambdaQueryWrapperX<ImFacePackItemDO>()
  23 + .inIfPresent(ImFacePackItemDO::getPackId, packIds)
  24 + .eq(ImFacePackItemDO::getStatus, status)
  25 + .orderByAsc(ImFacePackItemDO::getPackId)
  26 + .orderByAsc(ImFacePackItemDO::getSort)
  27 + .orderByAsc(ImFacePackItemDO::getId));
  28 + }
  29 +
  30 + default Long selectCountByPackId(Long packId) {
  31 + return selectCount(new LambdaQueryWrapperX<ImFacePackItemDO>()
  32 + .eq(ImFacePackItemDO::getPackId, packId));
  33 + }
  34 +
  35 + default Long selectCountByPackIds(Collection<Long> packIds) {
  36 + return selectCount(new LambdaQueryWrapperX<ImFacePackItemDO>()
  37 + .in(ImFacePackItemDO::getPackId, packIds));
  38 + }
  39 +
  40 + default PageResult<ImFacePackItemDO> selectPage(ImFacePackItemPageReqVO reqVO) {
  41 + return selectPage(reqVO, new LambdaQueryWrapperX<ImFacePackItemDO>()
  42 + .eqIfPresent(ImFacePackItemDO::getPackId, reqVO.getPackId())
  43 + .likeIfPresent(ImFacePackItemDO::getName, reqVO.getName())
  44 + .eqIfPresent(ImFacePackItemDO::getStatus, reqVO.getStatus())
  45 + .orderByAsc(ImFacePackItemDO::getSort)
  46 + .orderByDesc(ImFacePackItemDO::getId));
  47 + }
  48 +
  49 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.mysql.face;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackPageReqVO;
  7 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * IM 表情包 Mapper
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Mapper
  18 +public interface ImFacePackMapper extends BaseMapperX<ImFacePackDO> {
  19 +
  20 + default List<ImFacePackDO> selectListByStatusOrderBySort(Integer status) {
  21 + return selectList(new LambdaQueryWrapperX<ImFacePackDO>()
  22 + .eq(ImFacePackDO::getStatus, status)
  23 + .orderByAsc(ImFacePackDO::getSort)
  24 + .orderByAsc(ImFacePackDO::getId));
  25 + }
  26 +
  27 + default PageResult<ImFacePackDO> selectPage(ImFacePackPageReqVO reqVO) {
  28 + return selectPage(reqVO, new LambdaQueryWrapperX<ImFacePackDO>()
  29 + .likeIfPresent(ImFacePackDO::getName, reqVO.getName())
  30 + .eqIfPresent(ImFacePackDO::getStatus, reqVO.getStatus())
  31 + .betweenIfPresent(ImFacePackDO::getCreateTime, reqVO.getCreateTime())
  32 + .orderByAsc(ImFacePackDO::getSort)
  33 + .orderByDesc(ImFacePackDO::getId));
  34 + }
  35 +
  36 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.mysql.face;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * IM 用户私有表情 Mapper
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Mapper
  16 +public interface ImFaceUserItemMapper extends BaseMapperX<ImFaceUserItemDO> {
  17 +
  18 + /** 取用户的所有个人表情,按 sort 升序、id 倒序(最近添加在前) */
  19 + default List<ImFaceUserItemDO> selectListByUserId(Long userId) {
  20 + return selectList(new LambdaQueryWrapperX<ImFaceUserItemDO>()
  21 + .eq(ImFaceUserItemDO::getUserId, userId)
  22 + .orderByAsc(ImFaceUserItemDO::getSort)
  23 + .orderByDesc(ImFaceUserItemDO::getId));
  24 + }
  25 +
  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) {
  32 + return selectOne(new LambdaQueryWrapperX<ImFaceUserItemDO>()
  33 + .eq(ImFaceUserItemDO::getUserId, userId)
  34 + .eq(ImFaceUserItemDO::getUrl, url));
  35 + }
  36 +
  37 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java
... ... @@ -49,6 +49,12 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; {
49 49 * 场景:把好友名片推荐给其他会话,对端点击名片可打开 UserInfoCard
50 50 */
51 51 CARD(108, "名片", true, true),
  52 + /**
  53 + * 对应 OpenIM:Face 115
  54 + * 对应自己的类:FaceMessage
  55 + * 场景:表情贴图(运营配置的系统表情包 + 用户私有表情包);Unicode emoji 仍走 TEXT
  56 + */
  57 + FACE(115, "表情", true, true),
52 58  
53 59 // ========== 信号类(2101 / 2200 直接复用 OpenIM 段位编号;2201 自有扩展) ==========
54 60 /**
... ... @@ -142,9 +148,10 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; {
142 148 GROUP_INFO_UPDATE(1502, "群信息变更", true, false),
143 149 /**
144 150 * 对应 OpenIM:sdkws.JoinGroupApplicationTips(JoinGroupApplicationNotification 1503)
145   - * TODO @AI 未实现:入群申请;本期不在范围,预留段位以便未来对齐 OpenIM
  151 + * 对应自己的类:GroupRequestNotification
  152 + * 场景:用户申请加群 / 普通成员邀请待审批,定向私聊推送给群主 + 全部管理员(多端同步);不入群消息流
146 153 */
147   - GROUP_JOIN_APPLICATION(1503, "入群申请", true, false),
  154 + GROUP_REQUEST_RECEIVED(1503, "收到新的入群申请", false, false),
148 155 /**
149 156 * 对应 OpenIM:sdkws.MemberQuitTips(MemberQuitNotification 1504)
150 157 * 对应自己的类:GroupMemberQuitNotification
... ... @@ -153,14 +160,16 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; {
153 160 GROUP_MEMBER_QUIT(1504, "成员退群", true, false),
154 161 /**
155 162 * 对应 OpenIM:sdkws.GroupApplicationAcceptedTips(GroupApplicationAcceptedNotification 1505)
156   - * TODO @AI 未实现:入群申请通过;本期不在范围
  163 + * 对应自己的类:GroupRequestApprovedNotification
  164 + * 场景:群主 / 管理员同意申请,定向私聊推送给申请人 + 群主 + 全部管理员;申请人侧弹 toast,admin 侧 pendingRequestCount-1;不入群消息流
157 165 */
158   - GROUP_APPLICATION_ACCEPTED(1505, "入群申请通过", true, false),
  166 + GROUP_REQUEST_APPROVED(1505, "入群申请被同意", false, false),
159 167 /**
160 168 * 对应 OpenIM:sdkws.GroupApplicationRejectedTips(GroupApplicationRejectedNotification 1506)
161   - * TODO @AI 未实现:入群申请拒绝;本期不在范围
  169 + * 对应自己的类:GroupRequestRejectedNotification
  170 + * 场景:群主 / 管理员拒绝申请,定向私聊推送给申请人 + 群主 + 全部管理员;不入群消息流
162 171 */
163   - GROUP_APPLICATION_REJECTED(1506, "入群申请拒绝", true, false),
  172 + GROUP_REQUEST_REJECTED(1506, "入群申请被拒绝", false, false),
164 173 /**
165 174 * 对应 OpenIM:sdkws.GroupOwnerTransferredTips(GroupOwnerTransferredNotification 1507)
166 175 * 对应自己的类:GroupOwnerTransferNotification
... ... @@ -181,7 +190,8 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; {
181 190 GROUP_MEMBER_INVITE(1509, "成员加入", true, false),
182 191 /**
183 192 * 对应 OpenIM:sdkws.MemberEnterTips(MemberEnterNotification 1510)
184   - * TODO @AI 未实现:自由进群(链接 / 二维码进群);本期不在范围
  193 + * 对应自己的类:GroupMemberEnterNotification
  194 + * 场景:用户经搜索 / 二维码 / 分享链接自由进群(FREE 模式或审批通过后),全员广播;前端按 entrantUserId 局部添加成员
185 195 */
186 196 GROUP_MEMBER_ENTER(1510, "自由进群", true, false),
187 197 /**
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.face;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemPageReqVO;
  5 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemSaveReqVO;
  6 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO;
  7 +import jakarta.validation.Valid;
  8 +
  9 +import java.util.Collection;
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * IM 表情包项 Service 接口
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +public interface ImFacePackItemService {
  18 +
  19 + // ==================== 用户端 ====================
  20 +
  21 + /**
  22 + * 【用户端】按 packIds 批量取启用项,给前端聚合接口用
  23 + */
  24 + List<ImFacePackItemDO> getEnabledItemListByPackIds(Collection<Long> packIds);
  25 +
  26 + // ==================== 管理后台 ====================
  27 +
  28 + /**
  29 + * 【管理后台】分页查询
  30 + */
  31 + PageResult<ImFacePackItemDO> getFacePackItemPage(ImFacePackItemPageReqVO reqVO);
  32 +
  33 + /**
  34 + * 【管理后台】获取详情
  35 + */
  36 + ImFacePackItemDO getFacePackItem(Long id);
  37 +
  38 + /**
  39 + * 【管理后台】新增,返回新增 id
  40 + */
  41 + Long createFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO);
  42 +
  43 + /**
  44 + * 【管理后台】修改
  45 + */
  46 + void updateFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO);
  47 +
  48 + /**
  49 + * 【管理后台】删除
  50 + */
  51 + void deleteFacePackItem(Long id);
  52 +
  53 + /**
  54 + * 【管理后台】批量删除
  55 + */
  56 + void deleteFacePackItemList(List<Long> ids);
  57 +
  58 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.face;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemPageReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackItemSaveReqVO;
  9 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO;
  10 +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackItemMapper;
  11 +import jakarta.annotation.Resource;
  12 +import org.springframework.stereotype.Service;
  13 +import org.springframework.validation.annotation.Validated;
  14 +
  15 +import java.util.Collection;
  16 +import java.util.Collections;
  17 +import java.util.List;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  20 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_ITEM_NOT_EXISTS;
  21 +
  22 +/**
  23 + * IM 表情包项 Service 实现类
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +@Service
  28 +@Validated
  29 +public class ImFacePackItemServiceImpl implements ImFacePackItemService {
  30 +
  31 + @Resource
  32 + private ImFacePackItemMapper facePackItemMapper;
  33 + @Resource
  34 + private ImFacePackService facePackService;
  35 +
  36 + // ==================== 用户端 ====================
  37 +
  38 + @Override
  39 + public List<ImFacePackItemDO> getEnabledItemListByPackIds(Collection<Long> packIds) {
  40 + if (CollUtil.isEmpty(packIds)) {
  41 + return Collections.emptyList();
  42 + }
  43 + return facePackItemMapper.selectListByPackIdsAndStatus(packIds, CommonStatusEnum.ENABLE.getStatus());
  44 + }
  45 +
  46 + // ==================== 管理后台 ====================
  47 +
  48 + @Override
  49 + public PageResult<ImFacePackItemDO> getFacePackItemPage(ImFacePackItemPageReqVO reqVO) {
  50 + return facePackItemMapper.selectPage(reqVO);
  51 + }
  52 +
  53 + @Override
  54 + public ImFacePackItemDO getFacePackItem(Long id) {
  55 + return facePackItemMapper.selectById(id);
  56 + }
  57 +
  58 + @Override
  59 + public Long createFacePackItem(ImFacePackItemSaveReqVO reqVO) {
  60 + // 校验所属表情包存在
  61 + facePackService.validateFacePackExists(reqVO.getPackId());
  62 + ImFacePackItemDO item = BeanUtils.toBean(reqVO, ImFacePackItemDO.class);
  63 +
  64 + // TODO @AI:注释风格,和别的模块一致
  65 + facePackItemMapper.insert(item);
  66 + return item.getId();
  67 + }
  68 +
  69 + @Override
  70 + public void updateFacePackItem(ImFacePackItemSaveReqVO reqVO) {
  71 + // TODO @AI:注释风格,和别的模块一致
  72 + validateFacePackItemExists(reqVO.getId());
  73 + // TODO @AI:注释风格,和别的模块一致
  74 + facePackService.validateFacePackExists(reqVO.getPackId());
  75 +
  76 + // TODO @AI:注释风格,和别的模块一致
  77 + ImFacePackItemDO updateObj = BeanUtils.toBean(reqVO, ImFacePackItemDO.class);
  78 + facePackItemMapper.updateById(updateObj);
  79 + }
  80 +
  81 + @Override
  82 + public void deleteFacePackItem(Long id) {
  83 + // TODO @AI:注释风格,和别的模块一致
  84 + validateFacePackItemExists(id);
  85 + // TODO @AI:注释风格,和别的模块一致
  86 + facePackItemMapper.deleteById(id);
  87 + }
  88 +
  89 + @Override
  90 + public void deleteFacePackItemList(List<Long> ids) {
  91 + if (CollUtil.isEmpty(ids)) {
  92 + return;
  93 + }
  94 + facePackItemMapper.deleteByIds(ids);
  95 + }
  96 +
  97 + private void validateFacePackItemExists(Long id) {
  98 + if (facePackItemMapper.selectById(id) == null) {
  99 + throw exception(FACE_PACK_ITEM_NOT_EXISTS);
  100 + }
  101 + }
  102 +
  103 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.face;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackPageReqVO;
  5 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackSaveReqVO;
  6 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO;
  7 +import jakarta.validation.Valid;
  8 +
  9 +import java.util.List;
  10 +
  11 +/**
  12 + * IM 表情包 Service 接口
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public interface ImFacePackService {
  17 +
  18 + // TODO @AI:已经区分了 === 就不用写【用户端】【管理后台】了。
  19 +
  20 + // ==================== 用户端 ====================
  21 +
  22 + /**
  23 + * 【用户端】获取所有启用的表情包,按 sort 升序
  24 + */
  25 + List<ImFacePackDO> getEnabledFacePackList();
  26 +
  27 + /**
  28 + * 校验表情包存在
  29 + */
  30 + @SuppressWarnings("UnusedReturnValue")
  31 + ImFacePackDO validateFacePackExists(Long id);
  32 +
  33 + // ==================== 管理后台 ====================
  34 +
  35 + /**
  36 + * 【管理后台】分页查询表情包
  37 + */
  38 + PageResult<ImFacePackDO> getFacePackPage(ImFacePackPageReqVO reqVO);
  39 +
  40 + /**
  41 + * 【管理后台】获取表情包详情
  42 + */
  43 + ImFacePackDO getFacePack(Long id);
  44 +
  45 + /**
  46 + * 【管理后台】新增表情包,返回新增 id
  47 + */
  48 + Long createFacePack(@Valid ImFacePackSaveReqVO reqVO);
  49 +
  50 + /**
  51 + * 【管理后台】修改表情包
  52 + */
  53 + void updateFacePack(@Valid ImFacePackSaveReqVO reqVO);
  54 +
  55 + /**
  56 + * 【管理后台】删除表情包;存在表情时拒绝
  57 + */
  58 + void deleteFacePack(Long id);
  59 +
  60 + /**
  61 + * 【管理后台】批量删除表情包
  62 + */
  63 + void deleteFacePackList(List<Long> ids);
  64 +
  65 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.face;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackPageReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.ImFacePackSaveReqVO;
  9 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO;
  10 +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackItemMapper;
  11 +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackMapper;
  12 +import jakarta.annotation.Resource;
  13 +import org.springframework.stereotype.Service;
  14 +import org.springframework.validation.annotation.Validated;
  15 +
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  19 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_HAS_ITEMS;
  20 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_NOT_EXISTS;
  21 +
  22 +/**
  23 + * IM 表情包 Service 实现类
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +@Service
  28 +@Validated
  29 +public class ImFacePackServiceImpl implements ImFacePackService {
  30 +
  31 + @Resource
  32 + private ImFacePackMapper facePackMapper;
  33 +
  34 + // TODO @AI:跨模块操作,需要调用对方 service;
  35 + @Resource
  36 + private ImFacePackItemMapper facePackItemMapper;
  37 +
  38 + // ==================== 用户端 ====================
  39 +
  40 + @Override
  41 + public List<ImFacePackDO> getEnabledFacePackList() {
  42 + return facePackMapper.selectListByStatusOrderBySort(CommonStatusEnum.ENABLE.getStatus());
  43 + }
  44 +
  45 + @Override
  46 + public ImFacePackDO validateFacePackExists(Long id) {
  47 + ImFacePackDO pack = facePackMapper.selectById(id);
  48 + if (pack == null) {
  49 + throw exception(FACE_PACK_NOT_EXISTS);
  50 + }
  51 + return pack;
  52 + }
  53 +
  54 + // ==================== 管理后台 ====================
  55 +
  56 + @Override
  57 + public PageResult<ImFacePackDO> getFacePackPage(ImFacePackPageReqVO reqVO) {
  58 + return facePackMapper.selectPage(reqVO);
  59 + }
  60 +
  61 + @Override
  62 + public ImFacePackDO getFacePack(Long id) {
  63 + return facePackMapper.selectById(id);
  64 + }
  65 +
  66 + @Override
  67 + public Long createFacePack(ImFacePackSaveReqVO reqVO) {
  68 + ImFacePackDO pack = BeanUtils.toBean(reqVO, ImFacePackDO.class);
  69 + facePackMapper.insert(pack);
  70 + return pack.getId();
  71 + }
  72 +
  73 + @Override
  74 + public void updateFacePack(ImFacePackSaveReqVO reqVO) {
  75 + // TODO @AI:注释,参考别的模块
  76 + validateFacePackExists(reqVO.getId());
  77 +
  78 + // TODO @AI:注释,参考别的模块
  79 + ImFacePackDO updateObj = BeanUtils.toBean(reqVO, ImFacePackDO.class);
  80 + facePackMapper.updateById(updateObj);
  81 + }
  82 +
  83 + @Override
  84 + public void deleteFacePack(Long id) {
  85 + // 1.1 校验存在
  86 + validateFacePackExists(id);
  87 + // 1.2 校验表情包下没有表情;防止误删表情包导致历史 face 消息无法回查归属
  88 + if (facePackItemMapper.selectCountByPackId(id) > 0) {
  89 + throw exception(FACE_PACK_HAS_ITEMS);
  90 + }
  91 +
  92 + // 2. 删除
  93 + facePackMapper.deleteById(id);
  94 + }
  95 +
  96 + @Override
  97 + public void deleteFacePackList(List<Long> ids) {
  98 + if (CollUtil.isEmpty(ids)) {
  99 + return;
  100 + }
  101 + // 任一存在表情则拒绝整批删除,避免「只删一半」的中间态
  102 + if (facePackItemMapper.selectCountByPackIds(ids) > 0) {
  103 + throw exception(FACE_PACK_HAS_ITEMS);
  104 + }
  105 +
  106 + // 执行删除
  107 + facePackMapper.deleteByIds(ids);
  108 + }
  109 +
  110 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.face;
  2 +
  3 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFaceUserItemSaveReqVO;
  4 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
  5 +import jakarta.validation.Valid;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * IM 用户私有表情 Service 接口
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public interface ImFaceUserItemService {
  15 +
  16 + /**
  17 + * 获取我的个人表情列表
  18 + */
  19 + List<ImFaceUserItemDO> getMyFaceUserItemList(Long userId);
  20 +
  21 + /**
  22 + * 添加个人表情;返回新增 id(已存在则直接返回旧 id)
  23 + * <p>
  24 + * 同 URL 重复添加时返回旧记录 id,不报错
  25 + */
  26 + Long createFaceUserItem(Long userId, @Valid ImFaceUserItemSaveReqVO reqVO);
  27 +
  28 + /**
  29 + * 删除我的某条个人表情
  30 + */
  31 + void deleteFaceUserItem(Long userId, Long id);
  32 +
  33 + /**
  34 + * 批量删除我的个人表情
  35 + */
  36 + void deleteFaceUserItemList(Long userId, List<Long> ids);
  37 +
  38 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.face;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.ImFaceUserItemSaveReqVO;
  6 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
  7 +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFaceUserItemMapper;
  8 +import jakarta.annotation.Resource;
  9 +import org.springframework.stereotype.Service;
  10 +import org.springframework.validation.annotation.Validated;
  11 +
  12 +import java.util.List;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  15 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_NOT_EXISTS;
  16 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_NOT_OWN;
  17 +
  18 +/**
  19 + * IM 用户私有表情 Service 实现类
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +@Service
  24 +@Validated
  25 +public class ImFaceUserItemServiceImpl implements ImFaceUserItemService {
  26 +
  27 + @Resource
  28 + private ImFaceUserItemMapper faceUserItemMapper;
  29 +
  30 + // TODO @AI:传递了 userid,就不用写 myXXX 了;
  31 + @Override
  32 + public List<ImFaceUserItemDO> getMyFaceUserItemList(Long userId) {
  33 + return faceUserItemMapper.selectListByUserId(userId);
  34 + }
  35 +
  36 + @Override
  37 + public Long createFaceUserItem(Long userId, ImFaceUserItemSaveReqVO reqVO) {
  38 + // 1. 同 URL 已添加:直接返回旧 id;避免一张图反复添加占额度,并保证「添加到表情」幂等
  39 + // TODO @AI:是不是提示下,已经有这个标签库了。。。
  40 + ImFaceUserItemDO exist = faceUserItemMapper.selectByUserIdAndUrl(userId, reqVO.getUrl());
  41 + if (exist != null) {
  42 + return exist.getId();
  43 + }
  44 +
  45 + // 2. 入库
  46 + // TODO @AI:链式调用
  47 + ImFaceUserItemDO item = BeanUtils.toBean(reqVO, ImFaceUserItemDO.class);
  48 + item.setUserId(userId);
  49 + item.setSort(0);
  50 + faceUserItemMapper.insert(item);
  51 + return item.getId();
  52 + }
  53 +
  54 + @Override
  55 + public void deleteFaceUserItem(Long userId, Long id) {
  56 + ImFaceUserItemDO item = faceUserItemMapper.selectById(id);
  57 + if (item == null) {
  58 + throw exception(FACE_USER_ITEM_NOT_EXISTS);
  59 + }
  60 + // 防止 A 用户传 B 用户的表情 id 删除别人的表情
  61 + // TODO @AI:notEquals;
  62 + if (!item.getUserId().equals(userId)) {
  63 + throw exception(FACE_USER_ITEM_NOT_OWN);
  64 + }
  65 +
  66 + // TODO @AI:注释
  67 + faceUserItemMapper.deleteById(id);
  68 + }
  69 +
  70 + @Override
  71 + public void deleteFaceUserItemList(Long userId, List<Long> ids) {
  72 + if (CollUtil.isEmpty(ids)) {
  73 + return;
  74 + }
  75 + // 一次性查全部待删项;任一不属于当前用户则整批拒绝
  76 + // TODO @AI:过滤掉,不属于自己的;
  77 + List<ImFaceUserItemDO> items = faceUserItemMapper.selectByIds(ids);
  78 + // TODO @AI:不存在的,直接跳过;
  79 + if (CollUtil.size(items) != CollUtil.size(ids)) {
  80 + throw exception(FACE_USER_ITEM_NOT_EXISTS);
  81 + }
  82 + for (ImFaceUserItemDO item : items) {
  83 + if (!item.getUserId().equals(userId)) {
  84 + throw exception(FACE_USER_ITEM_NOT_OWN);
  85 + }
  86 + }
  87 +
  88 + // TODO @AI:注释
  89 + faceUserItemMapper.deleteByIds(ids);
  90 + }
  91 +
  92 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.websocket.dto.message;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 表情消息内容
  7 + * <p>
  8 + * 承载「贴图 / 自定义表情包」类消息;Unicode emoji(😀😂)仍走 TEXT
  9 + */
  10 +@Data
  11 +public class FaceMessage {
  12 +
  13 + /**
  14 + * 表情图 URL
  15 + */
  16 + private String url;
  17 + // TODO @AI:openim 有这个字段么?不是一般 url 就足够了呀。
  18 + /**
  19 + * 渲染宽度(像素)
  20 + * <p>
  21 + * 客户端渲染时按此尺寸占位,避免图片加载完成后的布局抖动
  22 + */
  23 + private Integer width;
  24 + /**
  25 + * 渲染高度(像素)
  26 + */
  27 + private Integer height;
  28 + /**
  29 + * 表情名(可选)
  30 + * <p>
  31 + * 系统表情包通常带名字(如「狗头」「捂脸」),用户私有表情包通常为空
  32 + */
  33 + private String name;
  34 +
  35 + /**
  36 + * 引用消息(可选)
  37 + */
  38 + private QuoteMessage quote;
  39 +
  40 +}
... ...