Commit ec9862cd29f140aeb764a931ab2fad35a08ee065

Authored by YunaiV
2 parents bfca0820 bd29116e

Merge branch 'feature/im-dev' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17

# Conflicts:
#	pom.xml
#	yudao-dependencies/pom.xml
#	yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
#	yudao-server/pom.xml
Showing 330 changed files with 27718 additions and 68 deletions

Too many changes.

To preserve performance only 100 of 330 files are displayed.

... ... @@ -26,6 +26,7 @@
26 26 <!-- <module>yudao-module-iot</module>-->
27 27 <!-- <module>yudao-module-mes</module>-->
28 28 <!-- <module>yudao-module-wms</module>-->
  29 +<!-- <module>yudao-module-im</module>-->
29 30 <!-- 请参考 https://doc.iocoder.cn/ai/build/ 文档,完成 AI 模块的启动!!! -->
30 31 <!-- <module>yudao-module-ai</module>-->
31 32 </modules>
... ...
sql/mysql/im/alter_message_content_8192.sql 0 → 100644
  1 +-- 消息表 content 列长度与 MESSAGE_CONTENT_MAX_LENGTH 保持一致
  2 +
  3 +ALTER TABLE `im_private_message` MODIFY COLUMN `content` varchar(8192) DEFAULT NULL COMMENT '消息内容,JSON 格式';
  4 +ALTER TABLE `im_group_message` MODIFY COLUMN `content` varchar(8192) DEFAULT NULL COMMENT '消息内容,JSON 格式';
... ...
yudao-dependencies/pom.xml
... ... @@ -50,6 +50,8 @@
50 50 <!-- 工具类相关 -->
51 51 <anji-plus-captcha.version>1.4.0</anji-plus-captcha.version>
52 52 <jsoup.version>1.22.2</jsoup.version>
  53 + <sensitive-word.version>0.29.5</sensitive-word.version>
  54 + <pinyin4j.version>2.5.1</pinyin4j.version>
53 55 <lombok.version>1.18.46</lombok.version>
54 56 <mapstruct.version>1.6.3</mapstruct.version>
55 57 <hutool-5.version>5.8.44</hutool-5.version>
... ... @@ -559,6 +561,18 @@
559 561 <version>${jsoup.version}</version>
560 562 </dependency>
561 563  
  564 + <dependency>
  565 + <groupId>com.github.houbb</groupId>
  566 + <artifactId>sensitive-word</artifactId> <!-- 敏感词检测:trie 树高效匹配 -->
  567 + <version>${sensitive-word.version}</version>
  568 + </dependency>
  569 +
  570 + <dependency>
  571 + <groupId>com.belerweb</groupId>
  572 + <artifactId>pinyin4j</artifactId> <!-- 汉字转拼音:作为 hutool PinyinUtil 的底层引擎 -->
  573 + <version>${pinyin4j.version}</version>
  574 + </dependency>
  575 +
562 576 <!-- Vert.x -->
563 577 <dependency>
564 578 <groupId>io.vertx</groupId>
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
... ... @@ -124,6 +124,22 @@ public class CollectionUtils {
124 124 return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
125 125 }
126 126  
  127 + public static <T, U> Set<U> convertLinkedSet(Collection<T> from, Function<T, U> func) {
  128 + if (CollUtil.isEmpty(from)) {
  129 + return new LinkedHashSet<>();
  130 + }
  131 + return from.stream().map(func).filter(Objects::nonNull)
  132 + .collect(Collectors.toCollection(LinkedHashSet::new));
  133 + }
  134 +
  135 + public static <T, U> Set<U> convertLinkedSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
  136 + if (CollUtil.isEmpty(from)) {
  137 + return new LinkedHashSet<>();
  138 + }
  139 + return from.stream().filter(filter).map(func).filter(Objects::nonNull)
  140 + .collect(Collectors.toCollection(LinkedHashSet::new));
  141 + }
  142 +
127 143 public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
128 144 if (CollUtil.isEmpty(from)) {
129 145 return new HashMap<>();
... ... @@ -372,4 +388,14 @@ public class CollectionUtils {
372 388 return false;
373 389 }
374 390  
  391 + /**
  392 + * 把单元素 head 与集合 tail 合并成新 List(head 在前,tail 顺序保留)
  393 + */
  394 + public static <T> List<T> of(T head, Collection<T> tail) {
  395 + List<T> list = new ArrayList<>();
  396 + list.add(head);
  397 + CollUtil.addAll(list, tail);
  398 + return list;
  399 + }
  400 +
375 401 }
376 402 \ No newline at end of file
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
... ... @@ -236,6 +236,23 @@ public class LocalDateTimeUtils {
236 236 return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);
237 237 }
238 238  
  239 + /**
  240 + * 获取最近 N 天的 0 点时刻序列(升序,含今天)
  241 + * <p>
  242 + * 例:getLatestDays(3) 返回 [前天 00:00, 昨天 00:00, 今天 00:00]
  243 + *
  244 + * @param days 天数(含今天)
  245 + * @return 升序的 LocalDateTime 列表
  246 + */
  247 + public static List<LocalDateTime> getLatestDays(int days) {
  248 + LocalDateTime today = getToday();
  249 + List<LocalDateTime> dates = new ArrayList<>(days);
  250 + for (int i = days - 1; i >= 0; i--) {
  251 + dates.add(today.minusDays(i));
  252 + }
  253 + return dates;
  254 + }
  255 +
239 256 public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime,
240 257 LocalDateTime endTime,
241 258 Integer interval) {
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
... ... @@ -200,4 +200,14 @@ public class HttpUtils {
200 200 }
201 201 }
202 202  
  203 + /**
  204 + * WebSocket URL 切换成 HTTP URL:ws:// → http://;wss:// → https://;其它格式原样保留
  205 + *
  206 + * @param url 原始 URL
  207 + * @return 切换协议后的 URL
  208 + */
  209 + public static String wsUrlToHttp(String url) {
  210 + return StrUtil.startWithIgnoreCase(url, "ws") ? "http" + url.substring(2) : url;
  211 + }
  212 +
203 213 }
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
... ... @@ -22,6 +22,7 @@ import java.lang.reflect.Type;
22 22 import java.time.LocalDateTime;
23 23 import java.util.ArrayList;
24 24 import java.util.List;
  25 +import java.util.Map;
25 26  
26 27 /**
27 28 * JSON 工具类
... ... @@ -174,6 +175,23 @@ public class JsonUtils {
174 175 }
175 176  
176 177 /**
  178 + * 解析 JSON 字符串成 Map,空字符串或解析失败返回 null
  179 + *
  180 + * @param text JSON 字符串
  181 + * @return Map 对象
  182 + */
  183 + public static Map<String, Object> parseMap(String text) {
  184 + if (StrUtil.isEmpty(text)) {
  185 + return null;
  186 + }
  187 + try {
  188 + return objectMapper.readValue(text, new TypeReference<Map<String, Object>>() {});
  189 + } catch (IOException e) {
  190 + return null;
  191 + }
  192 + }
  193 +
  194 + /**
177 195 * 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null
178 196 *
179 197 * @param text 字符串
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.string;
3 3 import cn.hutool.core.text.StrPool;
4 4 import cn.hutool.core.util.ArrayUtil;
5 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.extra.pinyin.PinyinUtil;
6 7 import org.aspectj.lang.JoinPoint;
7 8  
8 9 import java.util.Arrays;
... ... @@ -79,6 +80,25 @@ public class StrUtils {
79 80 }
80 81  
81 82 /**
  83 + * 转小写拼音,字之间以空格分隔,便于调用方按需拼接 / 取首字母 / 拼音搜索
  84 + *
  85 + * 例:「老张」→ "lao zhang"、「ZhangSan」→ "zhangsan"
  86 + * 英文 / 数字 / 符号原样返回,空值返回 null
  87 + *
  88 + * 注意:底层依赖 hutool-extra 的 {@link PinyinUtil},需要业务模块自行引入拼音引擎依赖
  89 + * (pinyin4j / TinyPinyin / Bopomofo4j 任选其一),否则运行时会抛 NoClassDefFoundError
  90 + *
  91 + * @param str 字符串
  92 + * @return 拼音串(保留空格分隔)
  93 + */
  94 + public static String toPinyin(String str) {
  95 + if (StrUtil.isBlank(str)) {
  96 + return null;
  97 + }
  98 + return PinyinUtil.getPinyin(str);
  99 + }
  100 +
  101 + /**
82 102 * 拼接方法的参数
83 103 *
84 104 * 特殊:排除一些无法序列化的参数,如 ServletRequest、ServletResponse、MultipartFile
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
... ... @@ -192,7 +192,12 @@ public class YudaoTenantAutoConfiguration {
192 192 RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,
193 193 BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize()));
194 194 // 创建 TenantRedisCacheManager 对象
195   - return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties.getIgnoreCaches());
  195 + TenantRedisCacheManager cacheManager = new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration,
  196 + tenantProperties.getIgnoreCaches());
  197 + // 开启事务感知:@Transactional 方法内的 @CacheEvict / @CachePut 自动延迟到 afterCommit,
  198 + // 避免事务未提交就清缓存被并发读穿写脏值;无事务时立即生效,行为不变
  199 + cacheManager.setTransactionAware(true);
  200 + return cacheManager;
196 201 }
197 202  
198 203 }
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
... ... @@ -170,6 +170,17 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
170 170 return CollUtil.getFirst(list);
171 171 }
172 172  
  173 + /**
  174 + * 获取满足条件的最新一条记录
  175 + * <p>
  176 + * 目的:解决并发场景下,插入多条记录后,使用 selectOne 会报错的问题
  177 + *
  178 + * @param queryWrapper 查询条件
  179 + * @return 最新一条;不存在返回 null
  180 + */
  181 + default T selectLastOne(LambdaQueryWrapper<T> queryWrapper) {
  182 + return CollUtil.getLast(selectList(queryWrapper));
  183 + }
173 184  
174 185 default Long selectCount() {
175 186 return selectCount(new QueryWrapper<>());
... ...
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java
... ... @@ -75,8 +75,12 @@ public class YudaoCacheAutoConfiguration {
75 75 RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
76 76 RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,
77 77 BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize()));
78   - // 创建 TenantRedisCacheManager 对象
79   - return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
  78 + // 创建 TimeoutRedisCacheManager 对象
  79 + TimeoutRedisCacheManager cacheManager = new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
  80 + // 开启事务感知:@Transactional 方法内的 @CacheEvict / @CachePut 自动延迟到 afterCommit,
  81 + // 避免事务未提交就清缓存被并发读穿写脏值;无事务时立即生效,行为不变
  82 + cacheManager.setTransactionAware(true);
  83 + return cacheManager;
80 84 }
81 85  
82 86 }
... ...
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java
... ... @@ -76,7 +76,7 @@ public class JsonWebSocketMessageHandler extends TextWebSocketHandler {
76 76 Long tenantId = WebSocketFrameworkUtils.getTenantId(session);
77 77 TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));
78 78 } catch (Throwable ex) {
79   - log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload());
  79 + log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload(), ex);
80 80 }
81 81 }
82 82  
... ...
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/security/LoginUserHandshakeInterceptor.java
... ... @@ -27,9 +27,10 @@ public class LoginUserHandshakeInterceptor implements HandshakeInterceptor {
27 27 public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
28 28 WebSocketHandler wsHandler, Map<String, Object> attributes) {
29 29 LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
30   - if (loginUser != null) {
31   - WebSocketFrameworkUtils.setLoginUser(loginUser, attributes);
  30 + if (loginUser == null) {
  31 + return false;
32 32 }
  33 + WebSocketFrameworkUtils.setLoginUser(loginUser, attributes);
33 34 return true;
34 35 }
35 36  
... ...
yudao-module-im/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <parent>
  6 + <groupId>cn.iocoder.boot</groupId>
  7 + <artifactId>yudao</artifactId>
  8 + <version>${revision}</version>
  9 + </parent>
  10 + <modelVersion>4.0.0</modelVersion>
  11 + <artifactId>yudao-module-im</artifactId>
  12 + <packaging>jar</packaging>
  13 +
  14 + <name>${project.artifactId}</name>
  15 + <description>
  16 + im 模块,我们放即时通讯业务。
  17 + 例如说:单聊、群聊、消息收发、消息撤回、消息已读等等
  18 + </description>
  19 +
  20 + <dependencies>
  21 + <dependency>
  22 + <groupId>cn.iocoder.boot</groupId>
  23 + <artifactId>yudao-module-system</artifactId>
  24 + <version>${revision}</version>
  25 + </dependency>
  26 +
  27 + <dependency>
  28 + <groupId>cn.iocoder.boot</groupId>
  29 + <artifactId>yudao-common</artifactId>
  30 + </dependency>
  31 +
  32 + <!-- Web 相关 -->
  33 + <dependency>
  34 + <groupId>cn.iocoder.boot</groupId>
  35 + <artifactId>yudao-spring-boot-starter-web</artifactId>
  36 + </dependency>
  37 +
  38 + <dependency>
  39 + <groupId>cn.iocoder.boot</groupId>
  40 + <artifactId>yudao-spring-boot-starter-security</artifactId>
  41 + </dependency>
  42 +
  43 + <dependency>
  44 + <groupId>cn.iocoder.boot</groupId>
  45 + <artifactId>yudao-spring-boot-starter-websocket</artifactId>
  46 + </dependency>
  47 +
  48 + <!-- DB 相关 -->
  49 + <dependency>
  50 + <groupId>cn.iocoder.boot</groupId>
  51 + <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
  52 + </dependency>
  53 +
  54 + <dependency>
  55 + <groupId>cn.iocoder.boot</groupId>
  56 + <artifactId>yudao-spring-boot-starter-redis</artifactId>
  57 + </dependency>
  58 +
  59 + <!-- 工具类相关 -->
  60 + <dependency>
  61 + <groupId>cn.iocoder.boot</groupId>
  62 + <artifactId>yudao-spring-boot-starter-excel</artifactId>
  63 + </dependency>
  64 +
  65 + <!-- 敏感词检测:sensitive-word(trie 树高效匹配,支持忽略大小写 / 全半角 / 数字风格),版本由 yudao-dependencies 统一管理 -->
  66 + <dependency>
  67 + <groupId>com.github.houbb</groupId>
  68 + <artifactId>sensitive-word</artifactId>
  69 + </dependency>
  70 +
  71 + <!-- 汉字转拼音:作为 hutool PinyinUtil 的底层引擎,业务侧调用 StrUtils.toPinyin 时按需引入 -->
  72 + <dependency>
  73 + <groupId>com.belerweb</groupId>
  74 + <artifactId>pinyin4j</artifactId>
  75 + </dependency>
  76 +
  77 + <!-- Test 测试相关 -->
  78 + <dependency>
  79 + <groupId>cn.iocoder.boot</groupId>
  80 + <artifactId>yudao-spring-boot-starter-test</artifactId>
  81 + </dependency>
  82 +
  83 + </dependencies>
  84 +
  85 +</project>
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/api/package-info.java 0 → 100644
  1 +/**
  2 + * @author anhaohao
  3 + * @since 2024/3/9 下午8:59
  4 + */
  5 +package cn.iocoder.yudao.module.im.api;
0 6 \ No newline at end of file
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/channel/ImChannelMaterialController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.channel;
  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.manager.channel.vo.material.ImChannelMaterialRespVO;
  6 +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO;
  7 +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService;
  8 +import io.swagger.v3.oas.annotations.Operation;
  9 +import io.swagger.v3.oas.annotations.Parameter;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import jakarta.annotation.Resource;
  12 +import org.springframework.validation.annotation.Validated;
  13 +import org.springframework.web.bind.annotation.GetMapping;
  14 +import org.springframework.web.bind.annotation.RequestMapping;
  15 +import org.springframework.web.bind.annotation.RequestParam;
  16 +import org.springframework.web.bind.annotation.RestController;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "用户 APP - IM 频道素材")
  21 +@RestController
  22 +@RequestMapping("/im/channel/material")
  23 +@Validated
  24 +public class ImChannelMaterialController {
  25 +
  26 + @Resource
  27 + private ImChannelMaterialService channelMaterialService;
  28 +
  29 + @GetMapping("/get")
  30 + @Operation(summary = "获取素材详情;用于客户端点击图文卡片渲染详情页")
  31 + @Parameter(name = "id", description = "素材编号", required = true, example = "1024")
  32 + public CommonResult<ImChannelMaterialRespVO> getMaterial(@RequestParam("id") Long id) {
  33 + ImChannelMaterialDO material = channelMaterialService.validateMaterialExists(id);
  34 + return success(BeanUtils.toBean(material, ImChannelMaterialRespVO.class));
  35 + }
  36 +
  37 +}
... ...
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.pack.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.useritem.ImFaceUserItemRespVO;
  6 +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemSaveReqVO;
  7 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
  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>> getFaceUserItemList() {
  34 + List<ImFaceUserItemDO> items = faceUserItemService.getFaceUserItemList(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 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/pack/ImFacePackUserRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.face.vo.pack;
  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 = "表情包图标", example = "https://cdn.example.com/face/pack/cat.png")
  19 + private String icon;
  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/userItem/ImFaceUserItemRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem;
  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/userItem/ImFaceUserItemSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.Max;
  5 +import jakarta.validation.constraints.Min;
  6 +import jakarta.validation.constraints.NotBlank;
  7 +import jakarta.validation.constraints.NotNull;
  8 +import jakarta.validation.constraints.Size;
  9 +import lombok.Data;
  10 +
  11 +@Schema(description = "IM 个人表情新增 Request VO")
  12 +@Data
  13 +public class ImFaceUserItemSaveReqVO {
  14 +
  15 + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  16 + example = "https://cdn.example.com/face/user/abc.gif")
  17 + @NotBlank(message = "表情图 URL 不能为空")
  18 + @Size(max = 512, message = "表情图 URL 长度不能超过 512")
  19 + private String url;
  20 +
  21 + @Schema(description = "表情名", example = "狗头")
  22 + @Size(max = 64, message = "表情名长度不能超过 64")
  23 + private String name;
  24 +
  25 + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  26 + @NotNull(message = "渲染宽度不能为空")
  27 + @Min(value = 1, message = "渲染宽度不能小于 1 像素")
  28 + @Max(value = 2048, message = "渲染宽度不能大于 2048 像素")
  29 + private Integer width;
  30 +
  31 + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  32 + @NotNull(message = "渲染高度不能为空")
  33 + @Min(value = 1, message = "渲染高度不能小于 1 像素")
  34 + @Max(value = 2048, message = "渲染高度不能大于 2048 像素")
  35 + private Integer height;
  36 +
  37 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.friend;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.framework.common.util.string.StrUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendRespVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
  11 +import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
  12 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  13 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  14 +import io.swagger.v3.oas.annotations.Operation;
  15 +import io.swagger.v3.oas.annotations.Parameter;
  16 +import io.swagger.v3.oas.annotations.Parameters;
  17 +import io.swagger.v3.oas.annotations.tags.Tag;
  18 +import jakarta.annotation.Resource;
  19 +import jakarta.validation.Valid;
  20 +import jakarta.validation.constraints.NotNull;
  21 +import org.springframework.validation.annotation.Validated;
  22 +import org.springframework.web.bind.annotation.*;
  23 +
  24 +import java.util.Collection;
  25 +import java.util.Collections;
  26 +import java.util.List;
  27 +import java.util.Map;
  28 +
  29 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton;
  32 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
  33 +
  34 +@Tag(name = "管理后台 - IM 好友")
  35 +@RestController
  36 +@RequestMapping("/im/friend")
  37 +@Validated
  38 +public class ImFriendController {
  39 +
  40 + @Resource
  41 + private ImFriendService friendService;
  42 +
  43 + @Resource
  44 + private AdminUserApi adminUserApi;
  45 +
  46 + @GetMapping("/list")
  47 + @Operation(summary = "获得当前登录用户的好友列表")
  48 + public CommonResult<List<ImFriendRespVO>> getMyFriendList() {
  49 + // 含 DISABLE 历史好友:保留给前端展示「已删除好友」的历史对话信息;前端按 status 决定会话级联清理
  50 + List<ImFriendDO> friends = friendService.getFriendList(getLoginUserId());
  51 + return success(buildFriendRespVOList(friends));
  52 + }
  53 +
  54 + @GetMapping("/get")
  55 + @Operation(summary = "获得好友详情")
  56 + @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048")
  57 + public CommonResult<ImFriendRespVO> getFriend(@RequestParam("friendUserId") Long friendUserId) {
  58 + ImFriendDO friend = friendService.getFriend(getLoginUserId(), friendUserId);
  59 + return success(buildFriendRespVO(friend));
  60 + }
  61 +
  62 + @DeleteMapping("/delete")
  63 + @Operation(summary = "删除好友(单向软删除)")
  64 + @Parameters({
  65 + @Parameter(description = "好友的用户编号", required = true, example = "2048"),
  66 + @Parameter(description = "是否级联清理本端相关数据(如私聊会话)")
  67 + })
  68 + public CommonResult<Boolean> deleteFriend(
  69 + @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId,
  70 + @RequestParam(value = "clear", required = false) Boolean clear) {
  71 + friendService.deleteFriend(getLoginUserId(), friendUserId, clear);
  72 + return success(true);
  73 + }
  74 +
  75 + @PutMapping("/update")
  76 + @Operation(summary = "更新好友单边属性(备注 / 免打扰 / 联系人置顶)")
  77 + public CommonResult<Boolean> updateFriend(@Valid @RequestBody ImFriendUpdateReqVO reqVO) {
  78 + friendService.updateFriend(getLoginUserId(), reqVO);
  79 + return success(true);
  80 + }
  81 +
  82 + @PutMapping("/block")
  83 + @Operation(summary = "拉黑好友(必须先是好友;单边屏蔽对方私聊消息)")
  84 + @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048")
  85 + public CommonResult<Boolean> blockFriend(
  86 + @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId) {
  87 + friendService.blockFriend(getLoginUserId(), friendUserId);
  88 + return success(true);
  89 + }
  90 +
  91 + @PutMapping("/unblock")
  92 + @Operation(summary = "移出黑名单")
  93 + @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048")
  94 + public CommonResult<Boolean> unblockFriend(
  95 + @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId) {
  96 + friendService.unblockFriend(getLoginUserId(), friendUserId);
  97 + return success(true);
  98 + }
  99 +
  100 + // ========== 私有方法:VO 组装 ==========
  101 +
  102 + private List<ImFriendRespVO> buildFriendRespVOList(Collection<ImFriendDO> friends) {
  103 + if (CollUtil.isEmpty(friends)) {
  104 + return Collections.emptyList();
  105 + }
  106 + // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1
  107 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  108 + convertList(friends, ImFriendDO::getFriendUserId));
  109 + return convertList(friends, friend -> {
  110 + ImFriendRespVO vo = BeanUtils.toBean(friend, ImFriendRespVO.class);
  111 + MapUtils.findAndThen(userMap, friend.getFriendUserId(), user ->
  112 + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
  113 + // 备注 / 昵称的拼音,给前端做字母分桶 + 拼音搜索
  114 + vo.setDisplayNamePinyin(StrUtils.toPinyin(vo.getDisplayName()))
  115 + .setNicknamePinyin(StrUtils.toPinyin(vo.getNickname()));
  116 + return vo;
  117 + });
  118 + }
  119 +
  120 + private ImFriendRespVO buildFriendRespVO(ImFriendDO friend) {
  121 + if (friend == null) {
  122 + return null;
  123 + }
  124 + return CollUtil.getFirst(buildFriendRespVOList(singleton(friend)));
  125 + }
  126 +
  127 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendRequestController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.friend;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.request.ImFriendRequestApplyReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.request.ImFriendRequestRespVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO;
  11 +import cn.iocoder.yudao.module.im.service.friend.ImFriendRequestService;
  12 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  13 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  14 +import io.swagger.v3.oas.annotations.Operation;
  15 +import io.swagger.v3.oas.annotations.Parameter;
  16 +import io.swagger.v3.oas.annotations.tags.Tag;
  17 +import jakarta.annotation.Resource;
  18 +import jakarta.validation.Valid;
  19 +import jakarta.validation.constraints.Max;
  20 +import jakarta.validation.constraints.Min;
  21 +import jakarta.validation.constraints.NotNull;
  22 +import jakarta.validation.constraints.Size;
  23 +import org.springframework.validation.annotation.Validated;
  24 +import org.springframework.web.bind.annotation.*;
  25 +
  26 +import java.util.Collections;
  27 +import java.util.List;
  28 +import java.util.Map;
  29 +import java.util.Set;
  30 +import java.util.stream.Stream;
  31 +
  32 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  35 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
  36 +
  37 +/**
  38 + * IM 好友申请记录 Controller
  39 + *
  40 + * @author 芋道源码
  41 + */
  42 +@Tag(name = "管理后台 - IM 好友申请")
  43 +@RestController
  44 +@RequestMapping("/im/friend-request")
  45 +@Validated
  46 +public class ImFriendRequestController {
  47 +
  48 + @Resource
  49 + private ImFriendRequestService friendRequestService;
  50 +
  51 + @Resource
  52 + private AdminUserApi adminUserApi;
  53 +
  54 + @PostMapping("/apply")
  55 + @Operation(summary = "发起好友申请")
  56 + public CommonResult<Long> applyFriend(@Valid @RequestBody ImFriendRequestApplyReqVO reqVO) {
  57 + ImFriendRequestDO request = friendRequestService.applyFriend(getLoginUserId(), reqVO);
  58 + return success(request != null ? request.getId() : null);
  59 + }
  60 +
  61 + @PutMapping("/agree")
  62 + @Operation(summary = "同意好友申请")
  63 + @Parameter(name = "id", description = "申请编号", required = true, example = "1024")
  64 + public CommonResult<Boolean> agreeFriendRequest(
  65 + @RequestParam("id") @NotNull(message = "申请编号不能为空") Long id) {
  66 + friendRequestService.agreeFriendRequest(getLoginUserId(), id);
  67 + return success(true);
  68 + }
  69 +
  70 + @PutMapping("/refuse")
  71 + @Operation(summary = "拒绝好友申请")
  72 + public CommonResult<Boolean> refuseFriendRequest(
  73 + @RequestParam("id") @NotNull(message = "申请编号不能为空") Long id,
  74 + @RequestParam(value = "handleContent", required = false)
  75 + @Size(max = 255, message = "处理理由最多 255 个字符") String handleContent) {
  76 + friendRequestService.refuseFriendRequest(getLoginUserId(), id, handleContent);
  77 + return success(true);
  78 + }
  79 +
  80 + @GetMapping("/list")
  81 + @Operation(summary = "查询「我相关」的好友申请列表(游标分页:传 maxId 加载更多)")
  82 + public CommonResult<List<ImFriendRequestRespVO>> getMyFriendRequestList(
  83 + @Parameter(description = "当前列表最旧记录的 id;首页不传")
  84 + @RequestParam(value = "maxId", required = false) Long maxId,
  85 + @Parameter(description = "单次拉取条数", required = true)
  86 + @RequestParam("limit") @Min(1) @Max(200) Integer limit) {
  87 + List<ImFriendRequestDO> list = friendRequestService.getMyFriendRequestList(getLoginUserId(), maxId, limit);
  88 + return success(buildList(list));
  89 + }
  90 +
  91 + @GetMapping("/get")
  92 + @Operation(summary = "按 id 单查「我相关」的申请记录(带越权过滤;WebSocket 通知到达后用)")
  93 + @Parameter(name = "id", description = "申请记录编号", required = true)
  94 + public CommonResult<ImFriendRequestRespVO> getMyFriendRequest(@RequestParam("id") Long id) {
  95 + ImFriendRequestDO request = friendRequestService.getFriendRequest(id);
  96 + // 越权过滤:fromUser / toUser 必有一方是当前用户,否则当不存在返回 null
  97 + Long currentUserId = getLoginUserId();
  98 + if (request == null || (ObjUtil.notEqual(request.getFromUserId(), currentUserId)
  99 + && ObjUtil.notEqual(request.getToUserId(), currentUserId))) {
  100 + return success(null);
  101 + }
  102 + return success(CollUtil.getFirst(buildList(Collections.singletonList(request))));
  103 + }
  104 +
  105 + // ========== 私有方法:VO 组装 ==========
  106 +
  107 + private List<ImFriendRequestRespVO> buildList(List<ImFriendRequestDO> list) {
  108 + if (CollUtil.isEmpty(list)) {
  109 + return Collections.emptyList();
  110 + }
  111 + // 双向 OR 列表,userIds 取 from + to 两组并集
  112 + Set<Long> userIds = convertSetByFlatMap(list,
  113 + request -> Stream.of(request.getFromUserId(), request.getToUserId()));
  114 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  115 + return convertList(list, request -> {
  116 + ImFriendRequestRespVO vo = BeanUtils.toBean(request, ImFriendRequestRespVO.class);
  117 + MapUtils.findAndThen(userMap, request.getFromUserId(), user ->
  118 + vo.setFromNickname(user.getNickname()).setFromAvatar(user.getAvatar()));
  119 + MapUtils.findAndThen(userMap, request.getToUserId(), user ->
  120 + vo.setToNickname(user.getNickname()).setToAvatar(user.getAvatar()));
  121 + return vo;
  122 + });
  123 + }
  124 +
  125 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.friend.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * IM 好友 Response VO
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Schema(description = "管理后台 - IM 好友 Response VO")
  14 +@Data
  15 +public class ImFriendRespVO {
  16 +
  17 + @Schema(description = "关系记录编号", example = "1024")
  18 + private Long id;
  19 +
  20 + @Schema(description = "好友的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  21 + private Long friendUserId;
  22 +
  23 + @Schema(description = "是否免打扰", example = "false")
  24 + private Boolean silent;
  25 +
  26 + @Schema(description = "好友展示备注(仅自己可见)", example = "老张")
  27 + private String displayName;
  28 +
  29 + @Schema(description = "好友展示备注的拼音(小写无空格)", example = "laozhang")
  30 + private String displayNamePinyin;
  31 +
  32 + @Schema(description = "添加来源", example = "1")
  33 + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举
  34 +
  35 + @Schema(description = "是否置顶联系人", example = "false")
  36 + private Boolean pinned;
  37 +
  38 + @Schema(description = "是否拉黑(仅自己可见)", example = "false")
  39 + private Boolean blocked;
  40 +
  41 + @Schema(description = "好友状态", example = "0")
  42 + private Integer status;
  43 +
  44 + @Schema(description = "添加好友时间")
  45 + private LocalDateTime addTime;
  46 +
  47 + @Schema(description = "删除好友时间")
  48 + private LocalDateTime deleteTime;
  49 +
  50 + // ========== 下面是聚合字段,方便前端显示 ==========
  51 +
  52 + @Schema(description = "好友昵称(实时聚合自 AdminUser)", example = "芋道")
  53 + private String nickname;
  54 +
  55 + @Schema(description = "好友昵称的拼音(小写无空格)", example = "yudao")
  56 + private String nicknamePinyin;
  57 +
  58 + @Schema(description = "好友头像(实时聚合自 AdminUser)")
  59 + private String avatar;
  60 +
  61 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.friend.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import jakarta.validation.constraints.Size;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - IM 好友更新 Request VO")
  9 +@Data
  10 +public class ImFriendUpdateReqVO {
  11 +
  12 + @Schema(description = "好友的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  13 + @NotNull(message = "好友用户编号不能为空")
  14 + private Long friendUserId;
  15 +
  16 + @Schema(description = "是否免打扰;不传表示不修改", example = "true")
  17 + private Boolean silent;
  18 +
  19 + @Schema(description = "好友展示备注(仅自己可见);不传表示不修改,传空串表示清空", example = "老张")
  20 + @Size(max = 16, message = "好友备注最多 16 个字符")
  21 + private String displayName;
  22 +
  23 + @Schema(description = "是否置顶联系人;不传表示不修改", example = "true")
  24 + private Boolean pinned;
  25 +
  26 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestApplyReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.friend.vo.request;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.im.enums.friend.ImFriendAddSourceEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import jakarta.validation.constraints.Size;
  8 +import lombok.Data;
  9 +
  10 +/**
  11 + * IM 好友申请 - 发起 Request VO
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Schema(description = "管理后台 - IM 好友申请发起 Request VO")
  16 +@Data
  17 +public class ImFriendRequestApplyReqVO {
  18 +
  19 + @Schema(description = "接收方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  20 + @NotNull(message = "接收方用户编号不能为空")
  21 + private Long toUserId;
  22 +
  23 + @Schema(description = "申请理由", example = "我是芋艿(一种食材)")
  24 + @Size(max = 255, message = "申请理由最多 255 个字符")
  25 + private String applyContent;
  26 +
  27 + @Schema(description = "对接收方的备注(仅自己可见)", example = "老张")
  28 + @Size(max = 16, message = "好友备注最多 16 个字符")
  29 + private String displayName;
  30 +
  31 + @Schema(description = "添加来源", example = "1")
  32 + @InEnum(ImFriendAddSourceEnum.class)
  33 + private Integer addSource;
  34 +
  35 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.friend.vo.request;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * IM 好友申请 Response VO
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Schema(description = "管理后台 - IM 好友申请 Response VO")
  14 +@Data
  15 +public class ImFriendRequestRespVO {
  16 +
  17 + @Schema(description = "申请编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  18 + private Long id;
  19 +
  20 + @Schema(description = "发起方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  21 + private Long fromUserId;
  22 +
  23 + @Schema(description = "接收方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  24 + private Long toUserId;
  25 +
  26 + @Schema(description = "处理结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  27 + private Integer handleResult; // 参见 ImFriendRequestHandleResultEnum 枚举
  28 +
  29 + @Schema(description = "申请理由", example = "我是芋艿(一种食材)")
  30 + private String applyContent;
  31 +
  32 + @Schema(description = "处理理由(接收方拒绝时可选填)", example = "暂不通过")
  33 + private String handleContent;
  34 +
  35 + @Schema(description = "添加来源", example = "1")
  36 + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举
  37 +
  38 + @Schema(description = "处理时间")
  39 + private LocalDateTime handleTime;
  40 +
  41 + @Schema(description = "申请创建时间")
  42 + private LocalDateTime createTime;
  43 +
  44 + // ========== 下面是聚合字段,方便前端显示 ==========
  45 +
  46 + @Schema(description = "发起方昵称(实时聚合自 AdminUser)", example = "芋道")
  47 + private String fromNickname;
  48 +
  49 + @Schema(description = "发起方头像(实时聚合自 AdminUser)")
  50 + private String fromAvatar;
  51 +
  52 + @Schema(description = "接收方昵称(实时聚合自 AdminUser)", example = "老张")
  53 + private String toNickname;
  54 +
  55 + @Schema(description = "接收方头像(实时聚合自 AdminUser)")
  56 + private String toAvatar;
  57 +
  58 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
  7 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageRespVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
  11 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
  12 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
  13 +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
  14 +import cn.iocoder.yudao.module.im.service.group.ImGroupService;
  15 +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService;
  16 +import io.swagger.v3.oas.annotations.Operation;
  17 +import io.swagger.v3.oas.annotations.Parameter;
  18 +import io.swagger.v3.oas.annotations.tags.Tag;
  19 +import jakarta.annotation.Resource;
  20 +import jakarta.validation.Valid;
  21 +import org.springframework.validation.annotation.Validated;
  22 +import org.springframework.web.bind.annotation.*;
  23 +
  24 +import java.util.*;
  25 +import java.util.stream.Stream;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  29 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
  30 +
  31 +@Tag(name = "管理后台 - 群")
  32 +@RestController
  33 +@RequestMapping("/im/group")
  34 +@Validated
  35 +public class ImGroupController {
  36 +
  37 + @Resource
  38 + private ImGroupService groupService;
  39 + @Resource
  40 + private ImGroupMemberService groupMemberService;
  41 + @Resource
  42 + private ImGroupMessageService groupMessageService;
  43 +
  44 + // ==================== 群的写操作 ====================
  45 +
  46 + @PostMapping("/create")
  47 + @Operation(summary = "创建群")
  48 + public CommonResult<ImGroupRespVO> createGroup(@Valid @RequestBody ImGroupCreateReqVO createReqVO) {
  49 + ImGroupDO group = groupService.createGroup(createReqVO, getLoginUserId());
  50 + // 新建群必无 pinnedMessages,跳过关联回填
  51 + return success(BeanUtils.toBean(group, ImGroupRespVO.class));
  52 + }
  53 +
  54 + @PutMapping("/update")
  55 + @Operation(summary = "更新群")
  56 + public CommonResult<ImGroupRespVO> updateGroup(@Valid @RequestBody ImGroupUpdateReqVO updateReqVO) {
  57 + ImGroupDO group = groupService.updateGroup(updateReqVO, getLoginUserId());
  58 + return success(buildGroupRespVO(group, getLoginUserId()));
  59 + }
  60 +
  61 + @DeleteMapping("/dissolve")
  62 + @Operation(summary = "解散群")
  63 + @Parameter(name = "id", description = "群编号", required = true)
  64 + public CommonResult<Boolean> dissolveGroup(@RequestParam("id") Long id) {
  65 + groupService.dissolveGroup(id, getLoginUserId());
  66 + return success(true);
  67 + }
  68 +
  69 + // ==================== 群的读操作 ====================
  70 +
  71 + @GetMapping("/get")
  72 + @Operation(summary = "获得群")
  73 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  74 + public CommonResult<ImGroupRespVO> getGroup(@RequestParam("id") Long id) {
  75 + ImGroupDO group = groupService.getGroup(id);
  76 + return success(buildGroupRespVO(group, getLoginUserId()));
  77 + }
  78 +
  79 + @GetMapping("/list")
  80 + @Operation(summary = "获得当前登录用户的群列表")
  81 + public CommonResult<List<ImGroupRespVO>> getMyGroupList() {
  82 + Long loginUserId = getLoginUserId();
  83 + List<ImGroupDO> groups = groupService.getMyGroupList(loginUserId);
  84 + return success(buildGroupRespVOList(groups, loginUserId));
  85 + }
  86 +
  87 + // ==================== 群成员的写操作 ====================
  88 +
  89 + @PostMapping("/invite")
  90 + @Operation(summary = "邀请用户加入群")
  91 + public CommonResult<Boolean> inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) {
  92 + groupService.inviteGroupMember(getLoginUserId(), inviteReqVO);
  93 + return success(true);
  94 + }
  95 +
  96 + @DeleteMapping("/quit")
  97 + @Operation(summary = "退出群")
  98 + @Parameter(name = "groupId", description = "群编号", required = true)
  99 + public CommonResult<Boolean> quitGroup(@RequestParam("groupId") Long groupId) {
  100 + groupService.quitGroup(groupId, getLoginUserId());
  101 + return success(true);
  102 + }
  103 +
  104 + @DeleteMapping("/kicking")
  105 + @Operation(summary = "移除群成员")
  106 + public CommonResult<Boolean> removeGroupMember(@Valid @RequestBody ImGroupMemberRemoveReqVO removeReqVO) {
  107 + groupService.removeGroupMember(getLoginUserId(), removeReqVO);
  108 + return success(true);
  109 + }
  110 +
  111 + @PutMapping("/add-admin")
  112 + @Operation(summary = "添加群管理员")
  113 + public CommonResult<Boolean> addGroupAdmin(@Valid @RequestBody ImGroupAdminAddReqVO reqVO) {
  114 + groupService.addGroupAdmin(getLoginUserId(), reqVO);
  115 + return success(true);
  116 + }
  117 +
  118 + @PutMapping("/remove-admin")
  119 + @Operation(summary = "撤销群管理员")
  120 + public CommonResult<Boolean> removeGroupAdmin(@Valid @RequestBody ImGroupAdminRemoveReqVO reqVO) {
  121 + groupService.removeGroupAdmin(getLoginUserId(), reqVO);
  122 + return success(true);
  123 + }
  124 +
  125 + @PutMapping("/transfer-owner")
  126 + @Operation(summary = "转让群主")
  127 + public CommonResult<Boolean> transferGroupOwner(@Valid @RequestBody ImGroupTransferOwnerReqVO transferReqVO) {
  128 + groupService.transferGroupOwner(getLoginUserId(), transferReqVO);
  129 + return success(true);
  130 + }
  131 +
  132 + // ==================== 群消息置顶 ====================
  133 +
  134 + @PutMapping("/pin-message")
  135 + @Operation(summary = "置顶群消息(群主 / 管理员)")
  136 + public CommonResult<Boolean> pinGroupMessage(@Valid @RequestBody ImGroupMessagePinReqVO reqVO) {
  137 + groupService.pinGroupMessage(getLoginUserId(), reqVO.getId(), reqVO.getMessageId());
  138 + return success(true);
  139 + }
  140 +
  141 + @PutMapping("/unpin-message")
  142 + @Operation(summary = "取消置顶群消息(群主 / 管理员)")
  143 + public CommonResult<Boolean> unpinGroupMessage(@Valid @RequestBody ImGroupMessagePinReqVO reqVO) {
  144 + groupService.unpinGroupMessage(getLoginUserId(), reqVO.getId(), reqVO.getMessageId());
  145 + return success(true);
  146 + }
  147 +
  148 + // ==================== 群禁言 ====================
  149 +
  150 + @PutMapping("/mute-all")
  151 + @Operation(summary = "全群禁言 / 取消(群主 / 管理员)")
  152 + public CommonResult<Boolean> muteAll(@Valid @RequestBody ImGroupMuteAllReqVO reqVO) {
  153 + groupService.muteAll(getLoginUserId(), reqVO);
  154 + return success(true);
  155 + }
  156 +
  157 + @PutMapping("/mute-member")
  158 + @Operation(summary = "禁言成员")
  159 + public CommonResult<Boolean> muteMember(@Valid @RequestBody ImGroupMuteMemberReqVO reqVO) {
  160 + groupService.muteMember(getLoginUserId(), reqVO);
  161 + return success(true);
  162 + }
  163 +
  164 + @PutMapping("/cancel-mute-member")
  165 + @Operation(summary = "取消成员禁言")
  166 + public CommonResult<Boolean> cancelMuteMember(@Valid @RequestBody ImGroupCancelMuteMemberReqVO reqVO) {
  167 + groupService.cancelMuteMember(getLoginUserId(), reqVO);
  168 + return success(true);
  169 + }
  170 +
  171 + /** 单群转 VO + 关联回填 pinnedMessages(仅当登录用户是该群有效成员) */
  172 + private ImGroupRespVO buildGroupRespVO(ImGroupDO group, Long loginUserId) {
  173 + if (group == null) {
  174 + return null;
  175 + }
  176 + return buildGroupRespVOList(Collections.singletonList(group), loginUserId).get(0);
  177 + }
  178 +
  179 + /**
  180 + * 群列表批量转 VO + 关联回填 pinnedMessages
  181 + * <p>
  182 + * 仅当登录用户是某群的有效成员时才回填该群的 pinnedMessages,避免非成员 / 已退群用户越权拿到置顶消息内容
  183 + */
  184 + private List<ImGroupRespVO> buildGroupRespVOList(List<ImGroupDO> groups, Long loginUserId) {
  185 + if (CollUtil.isEmpty(groups)) {
  186 + return Collections.emptyList();
  187 + }
  188 + // 仅当前用户是有效成员的群才允许回填置顶消息
  189 + Set<Long> activeGroupIds = convertSet(
  190 + groupMemberService.getActiveGroupMemberListByUserId(loginUserId), ImGroupMemberDO::getGroupId);
  191 + Set<Long> allMessageIds = convertSetByFlatMap(groups, group -> activeGroupIds.contains(group.getId())
  192 + ? CollUtil.emptyIfNull(group.getPinnedMessageIds()).stream() : Stream.empty());
  193 + Map<Long, ImGroupMessageDO> messageMap = groupMessageService.getGroupMessageMap(allMessageIds);
  194 + // 转换输出
  195 + return convertList(groups, group -> {
  196 + ImGroupRespVO vo = BeanUtils.toBean(group, ImGroupRespVO.class);
  197 + if (!activeGroupIds.contains(group.getId()) || CollUtil.isEmpty(group.getPinnedMessageIds())) {
  198 + return vo;
  199 + }
  200 + // 按 pin 顺序输出,已被删除的消息(messageMap 没命中)跳过
  201 + List<ImGroupMessageDO> pinnedMesages = convertList(group.getPinnedMessageIds(), messageMap::get);
  202 + return vo.setPinnedMessages(BeanUtils.toBean(pinnedMesages, ImGroupMessageRespVO.class));
  203 + });
  204 + }
  205 +
  206 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  6 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO;
  10 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
  11 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
  12 +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
  13 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  14 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  15 +import io.swagger.v3.oas.annotations.Operation;
  16 +import io.swagger.v3.oas.annotations.Parameter;
  17 +import io.swagger.v3.oas.annotations.Parameters;
  18 +import io.swagger.v3.oas.annotations.tags.Tag;
  19 +import jakarta.annotation.Resource;
  20 +import jakarta.validation.Valid;
  21 +import org.springframework.validation.annotation.Validated;
  22 +import org.springframework.web.bind.annotation.*;
  23 +
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  30 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
  31 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP;
  32 +
  33 +@Tag(name = "管理后台 - 群成员")
  34 +@RestController
  35 +@RequestMapping("/im/group-member")
  36 +@Validated
  37 +public class ImGroupMemberController {
  38 +
  39 + @Resource
  40 + private ImGroupMemberService groupMemberService;
  41 +
  42 + @Resource
  43 + private AdminUserApi adminUserApi;
  44 +
  45 + @PutMapping("/update")
  46 + @Operation(summary = "更新群成员")
  47 + public CommonResult<Boolean> updateGroupMember(@Valid @RequestBody ImGroupMemberUpdateReqVO updateReqVO) {
  48 + groupMemberService.updateGroupMember(getLoginUserId(), updateReqVO);
  49 + return success(true);
  50 + }
  51 +
  52 + @GetMapping("/get")
  53 + @Operation(summary = "获得群成员")
  54 + @Parameters({
  55 + @Parameter(name = "id", description = "编号(与 groupId + userId 二选一)", example = "1024"),
  56 + @Parameter(name = "groupId", description = "群编号(与 userId 配合查)", example = "1"),
  57 + @Parameter(name = "userId", description = "用户编号(与 groupId 配合查)", example = "100")
  58 + })
  59 + public CommonResult<ImGroupMemberRespVO> getGroupMember(@RequestParam(value = "id", required = false) Long id,
  60 + @RequestParam(value = "groupId", required = false) Long groupId,
  61 + @RequestParam(value = "userId", required = false) Long userId) {
  62 + // 1. 查询群成员
  63 + ImGroupMemberDO member;
  64 + if (id != null) {
  65 + member = groupMemberService.getGroupMember(id);
  66 + } else if (groupId != null && userId != null) {
  67 + member = groupMemberService.getGroupMember(groupId, userId);
  68 + } else {
  69 + // 避免 selectByGroupIdAndUserId 收到 null 参数走全表扫 / 抛 SQL 异常
  70 + throw new IllegalArgumentException("参数缺失:需传 id 或 (groupId, userId)");
  71 + }
  72 + if (member == null) {
  73 + return success(null);
  74 + }
  75 +
  76 + // 2. 校验当前登录用户是该成员所在群的有效成员
  77 + Long loginUserId = getLoginUserId();
  78 + groupMemberService.validateMemberInGroup(member.getGroupId(), loginUserId);
  79 +
  80 + // 3. 转化 VO
  81 + ImGroupMemberRespVO memberVO = BeanUtils.toBean(member, ImGroupMemberRespVO.class);
  82 + AdminUserRespDTO user = adminUserApi.getUser(member.getUserId());
  83 + if (user != null) {
  84 + memberVO.setNickname(user.getNickname()).setAvatar(user.getAvatar());
  85 + }
  86 + hidePrivateFieldsIfNotSelf(memberVO, member.getUserId(), loginUserId);
  87 + return success(memberVO);
  88 + }
  89 +
  90 + @GetMapping("/list")
  91 + @Operation(summary = "获得指定群的成员列表")
  92 + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
  93 + public CommonResult<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) {
  94 + // 1.1 查询群成员列表(包含 DISABLE 已退群的成员,不按时间过滤)
  95 + // 说明:保留已退群成员,是为了前端展示历史消息时,仍能通过该接口拿到已退群成员的昵称 / 头像信息,避免显示为空
  96 + List<ImGroupMemberDO> members = groupMemberService.getGroupMemberListByGroupId(groupId);
  97 + // 1.2 校验当前登录用户是否为群的有效成员,非成员不可查看
  98 + Long loginUserId = getLoginUserId();
  99 + if (CollUtil.findOne(members, member -> loginUserId.equals(member.getUserId())
  100 + && CommonStatusEnum.ENABLE.getStatus().equals(member.getStatus())) == null) {
  101 + throw exception(GROUP_MEMBER_NOT_IN_GROUP);
  102 + }
  103 +
  104 + // 2.批量聚合 AdminUser 信息(昵称 / 头像)
  105 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  106 + convertList(members, ImGroupMemberDO::getUserId));
  107 + return success(convertList(members, m -> {
  108 + ImGroupMemberRespVO vo = BeanUtils.toBean(m, ImGroupMemberRespVO.class);
  109 + MapUtils.findAndThen(userMap, m.getUserId(), user ->
  110 + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
  111 + hidePrivateFieldsIfNotSelf(vo, m.getUserId(), loginUserId);
  112 + return vo;
  113 + }));
  114 + }
  115 +
  116 + /**
  117 + * 非本人查看时,置空成员的私人设置字段(groupRemark / silent)
  118 + */
  119 + private void hidePrivateFieldsIfNotSelf(ImGroupMemberRespVO vo, Long memberUserId, Long loginUserId) {
  120 + if (ObjUtil.notEqual(loginUserId, memberUserId)) {
  121 + vo.setGroupRemark(null).setSilent(null);
  122 + }
  123 + }
  124 +
  125 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  6 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.request.ImGroupRequestApplyReqVO;
  10 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.request.ImGroupRequestRespVO;
  11 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
  12 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
  13 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO;
  14 +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
  15 +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
  16 +import cn.iocoder.yudao.module.im.service.group.ImGroupRequestService;
  17 +import cn.iocoder.yudao.module.im.service.group.ImGroupService;
  18 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  19 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  20 +import io.swagger.v3.oas.annotations.Operation;
  21 +import io.swagger.v3.oas.annotations.Parameter;
  22 +import io.swagger.v3.oas.annotations.tags.Tag;
  23 +import jakarta.annotation.Resource;
  24 +import jakarta.validation.Valid;
  25 +import jakarta.validation.constraints.NotNull;
  26 +import jakarta.validation.constraints.Size;
  27 +import org.springframework.validation.annotation.Validated;
  28 +import org.springframework.web.bind.annotation.*;
  29 +
  30 +import java.util.*;
  31 +import java.util.stream.Stream;
  32 +
  33 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  35 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
  36 +
  37 +/**
  38 + * IM 加群申请 Controller
  39 + *
  40 + * @author 芋道源码
  41 + */
  42 +@Tag(name = "管理后台 - IM 加群申请")
  43 +@RestController
  44 +@RequestMapping("/im/group-request")
  45 +@Validated
  46 +public class ImGroupRequestController {
  47 +
  48 + @Resource
  49 + private ImGroupRequestService groupRequestService;
  50 + @Resource
  51 + private ImGroupService groupService;
  52 + @Resource
  53 + private ImGroupMemberService groupMemberService;
  54 +
  55 + @Resource
  56 + private AdminUserApi adminUserApi;
  57 +
  58 + @PostMapping("/apply")
  59 + @Operation(summary = "申请加群")
  60 + public CommonResult<Long> applyJoinGroup(@Valid @RequestBody ImGroupRequestApplyReqVO reqVO) {
  61 + ImGroupRequestDO request = groupRequestService.applyJoinGroup(getLoginUserId(), reqVO);
  62 + return success(request != null ? request.getId() : null);
  63 + }
  64 +
  65 + @PutMapping("/agree")
  66 + @Operation(summary = "同意加群申请(群主或管理员)")
  67 + @Parameter(name = "id", description = "申请编号", required = true, example = "1024")
  68 + public CommonResult<Boolean> agreeGroupRequest(
  69 + @RequestParam("id") @NotNull(message = "申请编号不能为空") Long id) {
  70 + groupRequestService.agreeGroupRequest(getLoginUserId(), id);
  71 + return success(true);
  72 + }
  73 +
  74 + @PutMapping("/refuse")
  75 + @Operation(summary = "拒绝加群申请(群主或管理员)")
  76 + public CommonResult<Boolean> refuseGroupRequest(
  77 + @RequestParam("id") @NotNull(message = "申请编号不能为空") Long id,
  78 + @RequestParam(value = "handleContent", required = false)
  79 + @Size(max = 255, message = "处理理由最多 255 个字符") String handleContent) {
  80 + groupRequestService.refuseGroupRequest(getLoginUserId(), id, handleContent);
  81 + return success(true);
  82 + }
  83 +
  84 + @GetMapping("/unhandled-list")
  85 + @Operation(summary = "查询「我管理的所有群」下的未处理加群申请列表(不分页);前端 store 据此派生横幅红点 + Drawer 列表")
  86 + public CommonResult<List<ImGroupRequestRespVO>> getUnhandledRequestList() {
  87 + List<ImGroupRequestDO> list = groupRequestService.getUnhandledRequestListByOwnerOrAdmin(getLoginUserId());
  88 + return success(buildVOList(list));
  89 + }
  90 +
  91 + @GetMapping("/list-by-group")
  92 + @Operation(summary = "查询指定群下的全部加群申请(含已处理);仅群主 / 管理员可查")
  93 + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
  94 + public CommonResult<List<ImGroupRequestRespVO>> getGroupRequestListByGroupId(
  95 + @RequestParam("groupId") @NotNull(message = "群编号不能为空") Long groupId) {
  96 + List<ImGroupRequestDO> list = groupRequestService.getGroupRequestListByGroupId(getLoginUserId(), groupId);
  97 + return success(buildVOList(list));
  98 + }
  99 +
  100 + @GetMapping("/get")
  101 + @Operation(summary = "按 id 单查申请记录(带越权过滤;WebSocket 通知到达后用)")
  102 + @Parameter(name = "id", description = "申请记录编号", required = true)
  103 + public CommonResult<ImGroupRequestRespVO> getGroupRequest(@RequestParam("id") Long id) {
  104 + ImGroupRequestDO request = groupRequestService.getGroupRequest(id);
  105 + if (request == null) {
  106 + return success(null);
  107 + }
  108 + // 越权过滤:申请人 / 邀请人 / 群主 / 管理员之外,当不存在返回 null
  109 + Long currentUserId = getLoginUserId();
  110 + boolean canSee = ObjUtil.equal(request.getUserId(), currentUserId)
  111 + || ObjUtil.equal(request.getInviterUserId(), currentUserId)
  112 + || isGroupOwnerOrAdmin(request.getGroupId(), currentUserId);
  113 + if (!canSee) {
  114 + return success(null);
  115 + }
  116 +
  117 + // 转换并返回
  118 + return success(CollUtil.getFirst(buildVOList(Collections.singletonList(request))));
  119 + }
  120 +
  121 + /**
  122 + * 当前用户是否该群的有效群主 / 管理员
  123 + */
  124 + private boolean isGroupOwnerOrAdmin(Long groupId, Long userId) {
  125 + ImGroupMemberDO member = groupMemberService.getGroupMember(groupId, userId);
  126 + return member != null
  127 + && !CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())
  128 + && ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole());
  129 + }
  130 +
  131 + /** 申请记录列表批量转 VO + 关联回填用户 / 群信息 */
  132 + private List<ImGroupRequestRespVO> buildVOList(List<ImGroupRequestDO> list) {
  133 + if (CollUtil.isEmpty(list)) {
  134 + return Collections.emptyList();
  135 + }
  136 + // 1. 聚合 user / inviter 用户信息;convertSetByFlatMap 内部已过滤 null
  137 + Set<Long> userIds = convertSetByFlatMap(list,
  138 + request -> Stream.of(request.getUserId(), request.getInviterUserId()));
  139 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  140 + // 2. 聚合群信息(封禁 / 解散群也要回填,便于前端展示历史)
  141 + Set<Long> groupIds = convertSet(list, ImGroupRequestDO::getGroupId);
  142 + Map<Long, ImGroupDO> groupMap = groupService.getGroupMap(groupIds);
  143 + return convertList(list, request -> {
  144 + ImGroupRequestRespVO vo = BeanUtils.toBean(request, ImGroupRequestRespVO.class);
  145 + MapUtils.findAndThen(userMap, request.getUserId(), user ->
  146 + vo.setUserNickname(user.getNickname()).setUserAvatar(user.getAvatar()));
  147 + MapUtils.findAndThen(userMap, request.getInviterUserId(), user ->
  148 + vo.setInviterNickname(user.getNickname()).setInviterAvatar(user.getAvatar()));
  149 + MapUtils.findAndThen(groupMap, request.getGroupId(), group ->
  150 + vo.setGroupName(group.getName()).setGroupAvatar(group.getAvatar()));
  151 + return vo;
  152 + });
  153 + }
  154 +
  155 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupAdminAddReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - 添加群管理员 Request VO")
  11 +@Data
  12 +public class ImGroupAdminAddReqVO {
  13 +
  14 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279")
  15 + @NotNull(message = "群编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "目标用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[101, 102]")
  19 + @NotEmpty(message = "目标用户编号列表不能为空")
  20 + private List<Long> userIds;
  21 +
  22 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupAdminRemoveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - 撤销群管理员 Request VO")
  11 +@Data
  12 +public class ImGroupAdminRemoveReqVO {
  13 +
  14 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279")
  15 + @NotNull(message = "群编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "目标用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[101, 102]")
  19 + @NotEmpty(message = "目标用户编号列表不能为空")
  20 + private List<Long> userIds;
  21 +
  22 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCancelMuteMemberReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 取消成员禁言 Request VO")
  8 +@Data
  9 +public class ImGroupCancelMuteMemberReqVO {
  10 +
  11 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + @NotNull(message = "群编号不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "被取消禁言的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  16 + @NotNull(message = "用户编号不能为空")
  17 + private Long userId;
  18 +
  19 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.Size;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - 群创建 Request VO")
  11 +@Data
  12 +public class ImGroupCreateReqVO {
  13 +
  14 + @Schema(description = "群名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道技术交流群")
  15 + @NotBlank(message = "群名称不能为空")
  16 + @Size(max = 64, message = "群名称长度不能超过 64")
  17 + private String name;
  18 +
  19 + @Schema(description = "初始成员用户编号列表(建群同时邀请的好友,不含创建者自己)", example = "[1024, 2048]")
  20 + private List<Long> memberUserIds;
  21 +
  22 + @Schema(description = "进群是否需群主 / 管理员审批;不传默认 false 自由进群", example = "false")
  23 + private Boolean joinApproval;
  24 +
  25 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMessagePinReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * 管理后台 - 群消息置顶 / 取消置顶 Request VO
  9 + */
  10 +@Schema(description = "管理后台 - 群消息置顶 / 取消置顶 Request VO")
  11 +@Data
  12 +public class ImGroupMessagePinReqVO {
  13 +
  14 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279")
  15 + @NotNull(message = "群编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9527")
  19 + @NotNull(message = "消息编号不能为空")
  20 + private Long messageId;
  21 +
  22 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMuteAllReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 全群禁言 / 取消 Request VO")
  8 +@Data
  9 +public class ImGroupMuteAllReqVO {
  10 +
  11 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + @NotNull(message = "群编号不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "是否全群禁言", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  16 + @NotNull(message = "是否全群禁言不能为空")
  17 + private Boolean mutedAll;
  18 +
  19 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMuteMemberReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.Min;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - 成员禁言 Request VO")
  9 +@Data
  10 +public class ImGroupMuteMemberReqVO {
  11 +
  12 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + @NotNull(message = "群编号不能为空")
  14 + private Long id;
  15 +
  16 + @Schema(description = "被禁言的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  17 + @NotNull(message = "用户编号不能为空")
  18 + private Long userId;
  19 +
  20 + @Schema(description = "禁言时长(秒),0 表示永久禁言", requiredMode = Schema.RequiredMode.REQUIRED, example = "600")
  21 + @NotNull(message = "禁言时长不能为空")
  22 + @Min(value = 0, message = "禁言时长不能小于 0 秒")
  23 + private Integer mutedSeconds;
  24 +
  25 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import lombok.*;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +import java.time.LocalDateTime;
  8 +
  9 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  10 +
  11 +@Schema(description = "管理后台 - 群分页 Request VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class ImGroupPageReqVO extends PageParam {
  16 +
  17 + @Schema(description = "群名称", example = "芋艿")
  18 + private String name;
  19 +
  20 + @Schema(description = "群主用户编号", example = "31460")
  21 + private Long ownerUserId;
  22 +
  23 + @Schema(description = "群公告")
  24 + private String notice;
  25 +
  26 + @Schema(description = "创建时间")
  27 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  28 + private LocalDateTime[] createTime;
  29 +
  30 +}
0 31 \ No newline at end of file
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageRespVO;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.*;
  6 +
  7 +import java.time.LocalDateTime;
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - 群 Response VO")
  11 +@Data
  12 +public class ImGroupRespVO {
  13 +
  14 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003")
  15 + private Long id;
  16 +
  17 + @Schema(description = "群名称", example = "芋艿")
  18 + private String name;
  19 +
  20 + @Schema(description = "群主用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31460")
  21 + private Long ownerUserId;
  22 +
  23 + @Schema(description = "群头像")
  24 + private String avatar;
  25 +
  26 + @Schema(description = "群公告")
  27 + private String notice;
  28 +
  29 + @Schema(description = "是否封禁")
  30 + private Boolean banned;
  31 +
  32 + @Schema(description = "是否全群禁言")
  33 + private Boolean mutedAll;
  34 +
  35 + @Schema(description = "进群是否需群主 / 管理员审批", example = "false")
  36 + private Boolean joinApproval;
  37 +
  38 + @Schema(description = "封禁时间")
  39 + private LocalDateTime bannedTime;
  40 +
  41 + @Schema(description = "群状态", requiredMode = Schema.RequiredMode.REQUIRED)
  42 + private Integer status;
  43 +
  44 + @Schema(description = "解散时间")
  45 + private LocalDateTime dissolvedTime;
  46 +
  47 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  48 + private LocalDateTime createTime;
  49 +
  50 + @Schema(description = "群置顶消息列表,按 pin 顺序(最先置顶的在前);非该群有效成员时为空")
  51 + private List<ImGroupMessageRespVO> pinnedMessages;
  52 +
  53 +}
0 54 \ No newline at end of file
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +import jakarta.validation.constraints.*;
  6 +
  7 +@Schema(description = "管理后台 - 群新增/修改 Request VO")
  8 +@Data
  9 +public class ImGroupSaveReqVO {
  10 +
  11 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003")
  12 + private Long id;
  13 +
  14 + @Schema(description = "群名称", example = "芋艿")
  15 + private String name;
  16 +
  17 + @Schema(description = "群主用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31460")
  18 + @NotNull(message = "群主用户编号不能为空")
  19 + private Long ownerUserId;
  20 +
  21 + @Schema(description = "群头像")
  22 + private String avatar;
  23 +
  24 + @Schema(description = "群公告")
  25 + private String notice;
  26 +
  27 +}
0 28 \ No newline at end of file
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupTransferOwnerReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 群主转让 Request VO")
  8 +@Data
  9 +public class ImGroupTransferOwnerReqVO {
  10 +
  11 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279")
  12 + @NotNull(message = "群编号不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "新群主用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "202")
  16 + @NotNull(message = "新群主用户编号不能为空")
  17 + private Long newOwnerUserId;
  18 +
  19 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import com.fasterxml.jackson.annotation.JsonIgnore;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.AssertTrue;
  7 +import jakarta.validation.constraints.NotNull;
  8 +import jakarta.validation.constraints.Size;
  9 +import lombok.Data;
  10 +
  11 +@Schema(description = "管理后台 - 群更新 Request VO")
  12 +@Data
  13 +public class ImGroupUpdateReqVO {
  14 +
  15 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003")
  16 + @NotNull(message = "群编号不能为空")
  17 + private Long id;
  18 +
  19 + @Schema(description = "群名称", example = "芋道技术交流群")
  20 + @Size(max = 64, message = "群名称长度不能超过 64")
  21 + private String name;
  22 +
  23 + @Schema(description = "群头像")
  24 + @Size(max = 512, message = "群头像长度不能超过 512")
  25 + private String avatar;
  26 +
  27 + @Schema(description = "群公告")
  28 + @Size(max = 2048, message = "群公告长度不能超过 2048")
  29 + private String notice;
  30 +
  31 + @Schema(description = "进群是否需群主 / 管理员审批", example = "true")
  32 + private Boolean joinApproval;
  33 +
  34 + @AssertTrue(message = "群名称不能为空")
  35 + @JsonIgnore
  36 + public boolean isNameValid() {
  37 + return name == null || StrUtil.isNotBlank(name);
  38 + }
  39 +
  40 + @AssertTrue(message = "群头像不能为空")
  41 + @JsonIgnore
  42 + public boolean isAvatarValid() {
  43 + return avatar == null || StrUtil.isNotBlank(avatar);
  44 + }
  45 +
  46 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 群成员邀请 Request VO")
  8 +@Data
  9 +public class ImGroupMemberCreateReqVO {
  10 +
  11 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279")
  12 + @NotNull(message = "群编号不能为空")
  13 + private Long groupId;
  14 +
  15 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730")
  16 + @NotNull(message = "用户编号不能为空")
  17 + private Long userId;
  18 +
  19 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - 群成员邀请 Request VO")
  11 +@Data
  12 +public class ImGroupMemberInviteReqVO {
  13 +
  14 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279")
  15 + @NotNull(message = "群编号不能为空")
  16 + private Long groupId;
  17 +
  18 + @Schema(description = "被邀请的用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
  19 + @NotEmpty(message = "被邀请的用户编号列表不能为空")
  20 + private List<Long> memberUserIds;
  21 +
  22 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRemoveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - 群成员移除 Request VO")
  11 +@Data
  12 +public class ImGroupMemberRemoveReqVO {
  13 +
  14 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "群编号不能为空")
  16 + private Long groupId;
  17 +
  18 + @Schema(description = "被移除的用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
  19 + @NotEmpty(message = "被移除的用户编号列表不能为空")
  20 + private List<Long> memberUserIds;
  21 +
  22 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - 群成员 Response VO")
  9 +@Data
  10 +public class ImGroupMemberRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071")
  13 + private Long id;
  14 +
  15 + @Schema(description = "群编号", example = "13279")
  16 + private Long groupId;
  17 +
  18 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730")
  19 + private Long userId;
  20 +
  21 + @Schema(description = "组内显示名", example = "芋艿")
  22 + private String displayUserName;
  23 +
  24 + @Schema(description = "群备注", example = "核心群")
  25 + private String groupRemark;
  26 +
  27 + @Schema(description = "是否免打扰")
  28 + private Boolean silent;
  29 +
  30 + @Schema(description = "成员状态", example = "0")
  31 + private Integer status;
  32 +
  33 + @Schema(description = "成员角色", example = "3")
  34 + private Integer role; // 参见 ImGroupMemberRoleEnum 枚举类
  35 +
  36 + @Schema(description = "入群时间")
  37 + private LocalDateTime joinTime;
  38 +
  39 + @Schema(description = "退群时间")
  40 + private LocalDateTime quitTime;
  41 +
  42 + @Schema(description = "禁言到期时间")
  43 + private LocalDateTime muteEndTime;
  44 +
  45 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  46 + private LocalDateTime createTime;
  47 +
  48 + // ========== 关联 AdminUser 的字段 ==========
  49 +
  50 + @Schema(description = "用户昵称", example = "芋道")
  51 + private String nickname;
  52 +
  53 + @Schema(description = "用户头像")
  54 + private String avatar;
  55 +
  56 +}
0 57 \ No newline at end of file
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +import lombok.experimental.Accessors;
  7 +
  8 +@Schema(description = "管理后台 - 群成员更新 Request VO")
  9 +@Data
  10 +@Accessors(chain = true)
  11 +public class ImGroupMemberUpdateReqVO {
  12 +
  13 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotNull(message = "群编号不能为空")
  15 + private Long groupId;
  16 +
  17 + @Schema(description = "群内昵称", example = "芋头")
  18 + private String displayUserName;
  19 +
  20 + @Schema(description = "群备注", example = "公司群")
  21 + private String groupRemark;
  22 +
  23 + @Schema(description = "是否免打扰")
  24 + private Boolean silent;
  25 +
  26 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestApplyReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo.request;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import jakarta.validation.constraints.Size;
  8 +import lombok.Data;
  9 +
  10 +@Schema(description = "管理后台 - IM 加群申请发起 Request VO")
  11 +@Data
  12 +public class ImGroupRequestApplyReqVO {
  13 +
  14 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "群编号不能为空")
  16 + private Long groupId;
  17 +
  18 + @Schema(description = "申请理由", example = "我是芋艿(一种食材)")
  19 + @Size(max = 255, message = "申请理由最多 255 个字符")
  20 + private String applyContent;
  21 +
  22 + @Schema(description = "加入来源", example = "1")
  23 + @InEnum(ImGroupAddSourceEnum.class)
  24 + private Integer addSource;
  25 +
  26 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo.request;
  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 ImGroupRequestRespVO {
  11 +
  12 + @Schema(description = "申请编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long groupId;
  17 +
  18 + @Schema(description = "申请人 / 被邀请人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  19 + private Long userId;
  20 +
  21 + @Schema(description = "邀请人用户编号;NULL 表示用户主动申请", example = "200")
  22 + private Long inviterUserId;
  23 +
  24 + @Schema(description = "处理结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  25 + private Integer handleResult; // 参见 ImGroupRequestHandleResultEnum 枚举
  26 +
  27 + @Schema(description = "申请理由", example = "我想加入这个群")
  28 + private String applyContent;
  29 +
  30 + @Schema(description = "处理理由(拒绝时可选填)", example = "暂不通过")
  31 + private String handleContent;
  32 +
  33 + @Schema(description = "处理人用户编号", example = "31460")
  34 + private Long handleUserId;
  35 +
  36 + @Schema(description = "加入来源", example = "1")
  37 + private Integer addSource; // 参见 ImGroupAddSourceEnum 枚举
  38 +
  39 + @Schema(description = "处理时间")
  40 + private LocalDateTime handleTime;
  41 +
  42 + @Schema(description = "申请创建时间")
  43 + private LocalDateTime createTime;
  44 +
  45 + // ========== 下面是聚合字段,方便前端显示 ==========
  46 +
  47 + @Schema(description = "申请人 / 被邀请人昵称(实时聚合自 AdminUser)", example = "芋道")
  48 + private String userNickname;
  49 +
  50 + @Schema(description = "申请人 / 被邀请人头像(实时聚合自 AdminUser)")
  51 + private String userAvatar;
  52 +
  53 + @Schema(description = "邀请人昵称(实时聚合自 AdminUser)", example = "老张")
  54 + private String inviterNickname;
  55 +
  56 + @Schema(description = "邀请人头像(实时聚合自 AdminUser)")
  57 + private String inviterAvatar;
  58 +
  59 + @Schema(description = "群名称(实时聚合自 ImGroup)", example = "芋道技术交流群")
  60 + private String groupName;
  61 +
  62 + @Schema(description = "群头像(实时聚合自 ImGroup)")
  63 + private String groupAvatar;
  64 +
  65 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.channel;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  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.channel.vo.channel.ImChannelPageReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel.ImChannelRespVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel.ImChannelSaveReqVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO;
  11 +import cn.iocoder.yudao.module.im.service.channel.ImChannelService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
  15 +import jakarta.annotation.Resource;
  16 +import jakarta.validation.Valid;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
  20 +
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +
  25 +@Tag(name = "管理后台 - IM 频道")
  26 +@RestController
  27 +@RequestMapping("/im/manager/channel")
  28 +@Validated
  29 +public class ImChannelManagerController {
  30 +
  31 + @Resource
  32 + private ImChannelService channelService;
  33 +
  34 + @PostMapping("/create")
  35 + @Operation(summary = "新增频道")
  36 + @PreAuthorize("@ss.hasPermission('im:manager:channel:create')")
  37 + public CommonResult<Long> createChannel(@Valid @RequestBody ImChannelSaveReqVO reqVO) {
  38 + return success(channelService.createChannel(reqVO));
  39 + }
  40 +
  41 + @PutMapping("/update")
  42 + @Operation(summary = "修改频道")
  43 + @PreAuthorize("@ss.hasPermission('im:manager:channel:update')")
  44 + public CommonResult<Boolean> updateChannel(@Valid @RequestBody ImChannelSaveReqVO reqVO) {
  45 + channelService.updateChannel(reqVO);
  46 + return success(true);
  47 + }
  48 +
  49 + @DeleteMapping("/delete")
  50 + @Operation(summary = "删除频道")
  51 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  52 + @PreAuthorize("@ss.hasPermission('im:manager:channel:delete')")
  53 + public CommonResult<Boolean> deleteChannel(@RequestParam("id") Long id) {
  54 + channelService.deleteChannel(id);
  55 + return success(true);
  56 + }
  57 +
  58 + @GetMapping("/page")
  59 + @Operation(summary = "获得频道分页")
  60 + @PreAuthorize("@ss.hasPermission('im:manager:channel:query')")
  61 + public CommonResult<PageResult<ImChannelRespVO>> getChannelPage(@Valid ImChannelPageReqVO pageReqVO) {
  62 + PageResult<ImChannelDO> pageResult = channelService.getChannelPage(pageReqVO);
  63 + return success(BeanUtils.toBean(pageResult, ImChannelRespVO.class));
  64 + }
  65 +
  66 + @GetMapping("/get")
  67 + @Operation(summary = "获得频道详情")
  68 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  69 + @PreAuthorize("@ss.hasPermission('im:manager:channel:query')")
  70 + public CommonResult<ImChannelRespVO> getChannel(@RequestParam("id") Long id) {
  71 + ImChannelDO channel = channelService.getChannel(id);
  72 + return success(BeanUtils.toBean(channel, ImChannelRespVO.class));
  73 + }
  74 +
  75 + @GetMapping("/simple-list")
  76 + @Operation(summary = "获得启用的频道精简列表;前端表单选择频道时调用")
  77 + public CommonResult<List<ImChannelRespVO>> getSimpleChannelList() {
  78 + // TODO DONE @AI:getChannelListByStatus 统一命名
  79 + List<ImChannelDO> list = channelService.getChannelListByStatus(CommonStatusEnum.ENABLE.getStatus());
  80 + return success(BeanUtils.toBean(list, ImChannelRespVO.class));
  81 + }
  82 +
  83 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelMaterialManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.channel;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialPageReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialRespVO;
  10 +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialSaveReqVO;
  11 +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO;
  12 +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO;
  13 +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService;
  14 +import cn.iocoder.yudao.module.im.service.channel.ImChannelService;
  15 +import io.swagger.v3.oas.annotations.Operation;
  16 +import io.swagger.v3.oas.annotations.Parameter;
  17 +import io.swagger.v3.oas.annotations.tags.Tag;
  18 +import jakarta.annotation.Resource;
  19 +import jakarta.validation.Valid;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.validation.annotation.Validated;
  22 +import org.springframework.web.bind.annotation.*;
  23 +
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  30 +
  31 +@Tag(name = "管理后台 - IM 频道素材")
  32 +@RestController
  33 +@RequestMapping("/im/manager/channel-material")
  34 +@Validated
  35 +public class ImChannelMaterialManagerController {
  36 +
  37 + @Resource
  38 + private ImChannelMaterialService channelMaterialService;
  39 + @Resource
  40 + private ImChannelService channelService;
  41 +
  42 + @PostMapping("/create")
  43 + @Operation(summary = "新增素材")
  44 + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:create')")
  45 + public CommonResult<Long> createMaterial(@Valid @RequestBody ImChannelMaterialSaveReqVO reqVO) {
  46 + return success(channelMaterialService.createMaterial(reqVO));
  47 + }
  48 +
  49 + @PutMapping("/update")
  50 + @Operation(summary = "修改素材")
  51 + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:update')")
  52 + public CommonResult<Boolean> updateMaterial(@Valid @RequestBody ImChannelMaterialSaveReqVO reqVO) {
  53 + channelMaterialService.updateMaterial(reqVO);
  54 + return success(true);
  55 + }
  56 +
  57 + @DeleteMapping("/delete")
  58 + @Operation(summary = "删除素材")
  59 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  60 + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:delete')")
  61 + public CommonResult<Boolean> deleteMaterial(@RequestParam("id") Long id) {
  62 + channelMaterialService.deleteMaterial(id);
  63 + return success(true);
  64 + }
  65 +
  66 + @GetMapping("/page")
  67 + @Operation(summary = "获得素材分页;含频道名回填")
  68 + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:query')")
  69 + public CommonResult<PageResult<ImChannelMaterialRespVO>> getMaterialPage(@Valid ImChannelMaterialPageReqVO pageReqVO) {
  70 + PageResult<ImChannelMaterialDO> pageResult = channelMaterialService.getMaterialPage(pageReqVO);
  71 + if (CollUtil.isEmpty(pageResult.getList())) {
  72 + return success(PageResult.empty(pageResult.getTotal()));
  73 + }
  74 + // 回填频道名
  75 + List<ImChannelDO> channels = channelService.getChannelList(
  76 + convertSet(pageResult.getList(), ImChannelMaterialDO::getChannelId));
  77 + Map<Long, ImChannelDO> channelMap = convertMap(channels, ImChannelDO::getId);
  78 + return success(BeanUtils.toBean(pageResult, ImChannelMaterialRespVO.class, vo ->
  79 + MapUtils.findAndThen(channelMap, vo.getChannelId(), c -> vo.setChannelName(c.getName()))));
  80 + }
  81 +
  82 + @GetMapping("/get")
  83 + @Operation(summary = "获得素材详情(含富文本正文)")
  84 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  85 + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:query')")
  86 + public CommonResult<ImChannelMaterialRespVO> getMaterial(@RequestParam("id") Long id) {
  87 + ImChannelMaterialDO material = channelMaterialService.getMaterial(id);
  88 + return success(BeanUtils.toBean(material, ImChannelMaterialRespVO.class));
  89 + }
  90 +
  91 + @GetMapping("/simple-list")
  92 + @Operation(summary = "获得指定频道下的素材精简列表;用于推送弹窗的素材下拉")
  93 + @Parameter(name = "channelId", description = "频道编号", required = true, example = "1")
  94 + public CommonResult<List<ImChannelMaterialRespVO>> getSimpleMaterialList(@RequestParam("channelId") Long channelId) {
  95 + List<ImChannelMaterialDO> list = channelMaterialService.getMaterialListByChannelId(channelId);
  96 + return success(BeanUtils.toBean(list, ImChannelMaterialRespVO.class));
  97 + }
  98 +
  99 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel;
  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 +@Schema(description = "管理后台 - IM 频道分页 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +public class ImChannelPageReqVO extends PageParam {
  12 +
  13 + @Schema(description = "频道业务码", example = "system_notice")
  14 + private String code;
  15 +
  16 + @Schema(description = "频道名称", example = "系统")
  17 + private String name;
  18 +
  19 + @Schema(description = "状态", example = "0")
  20 + private Integer status; // 参见 CommonStatusEnum 枚举类
  21 +
  22 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel;
  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 ImChannelRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "频道业务码", requiredMode = Schema.RequiredMode.REQUIRED, example = "system_notice")
  16 + private String code;
  17 +
  18 + @Schema(description = "频道名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "系统公告")
  19 + private String name;
  20 +
  21 + @Schema(description = "频道头像")
  22 + private String avatar;
  23 +
  24 + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  25 + private Integer sort;
  26 +
  27 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  28 + private Integer status; // 参见 CommonStatusEnum 枚举类
  29 +
  30 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  31 + private LocalDateTime createTime;
  32 +
  33 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel;
  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.Pattern;
  7 +import jakarta.validation.constraints.Size;
  8 +import lombok.Data;
  9 +
  10 +@Schema(description = "管理后台 - IM 频道新增 / 修改 Request VO")
  11 +@Data
  12 +public class ImChannelSaveReqVO {
  13 +
  14 + @Schema(description = "编号(修改时必填)", example = "1024")
  15 + private Long id;
  16 +
  17 + @Schema(description = "频道业务码;唯一", requiredMode = Schema.RequiredMode.REQUIRED, example = "system_notice")
  18 + @NotBlank(message = "频道编码不能为空")
  19 + @Size(max = 64, message = "频道编码长度不能超过 64")
  20 + @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "频道编码只能由小写字母 / 数字 / 下划线组成,且必须以字母开头")
  21 + private String code;
  22 +
  23 + @Schema(description = "频道名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "系统公告")
  24 + @NotBlank(message = "频道名称不能为空")
  25 + @Size(max = 64, message = "频道名称长度不能超过 64")
  26 + private String name;
  27 +
  28 + @Schema(description = "频道头像", example = "https://cdn.example.com/channel/system_notice.png")
  29 + @Size(max = 512, message = "头像长度不能超过 512")
  30 + private String avatar;
  31 +
  32 + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  33 + @NotNull(message = "排序不能为空")
  34 + private Integer sort;
  35 +
  36 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  37 + @NotNull(message = "状态不能为空")
  38 + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)
  39 +
  40 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material;
  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 ImChannelMaterialPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "频道编号", example = "1")
  19 + private Long channelId;
  20 +
  21 + @Schema(description = "内容类型", example = "1")
  22 + private Integer type; // 参见 ImChannelMaterialTypeEnum 枚举类
  23 +
  24 + @Schema(description = "标题", example = "活动")
  25 + private String title;
  26 +
  27 + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  28 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  29 + private LocalDateTime[] createTime;
  30 +
  31 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material;
  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 ImChannelMaterialRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "频道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  16 + private Long channelId;
  17 +
  18 + @Schema(description = "频道名称(关联查询填充)")
  19 + private String channelName;
  20 +
  21 + @Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  22 + private Integer type; // 参见 ImChannelMaterialTypeEnum 枚举类
  23 +
  24 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
  25 + private String title;
  26 +
  27 + @Schema(description = "封面图")
  28 + private String coverUrl;
  29 +
  30 + @Schema(description = "摘要")
  31 + private String summary;
  32 +
  33 + @Schema(description = "正文;富文本 HTML")
  34 + private String content;
  35 +
  36 + @Schema(description = "跳转链接")
  37 + private String url;
  38 +
  39 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  40 + private LocalDateTime createTime;
  41 +
  42 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material;
  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 ImChannelMaterialSaveReqVO {
  12 +
  13 + @Schema(description = "编号(修改时必填)", example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "频道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + @NotNull(message = "频道编号不能为空")
  18 + private Long channelId;
  19 +
  20 + @Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  21 + @NotNull(message = "内容类型不能为空")
  22 + private Integer type; // 参见 ImChannelMaterialTypeEnum 枚举类
  23 +
  24 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "双十一活动来啦")
  25 + @NotBlank(message = "标题不能为空")
  26 + @Size(max = 128, message = "标题长度不能超过 128")
  27 + private String title;
  28 +
  29 + @Schema(description = "封面图", example = "https://cdn.example.com/cover.png")
  30 + @Size(max = 512, message = "封面图长度不能超过 512")
  31 + private String coverUrl;
  32 +
  33 + @Schema(description = "摘要", example = "全场五折,戳详情看玩法")
  34 + @Size(max = 255, message = "摘要长度不能超过 255")
  35 + private String summary;
  36 +
  37 + @Schema(description = "正文;富文本 HTML")
  38 + private String content;
  39 +
  40 + @Schema(description = "跳转链接;为空表示走客户端内置详情页", example = "https://example.com/activity/123")
  41 + @Size(max = 512, message = "跳转链接长度不能超过 512")
  42 + private String url;
  43 +
  44 +}
... ...
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.item.ImFacePackItemPageReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item.ImFacePackItemRespVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item.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 jakarta.validation.constraints.Size;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
  20 +
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +
  25 +@Tag(name = "管理后台 - IM 表情包项")
  26 +@RestController
  27 +@RequestMapping("/im/manager/face-pack-item")
  28 +@Validated
  29 +public class ImFacePackItemManagerController {
  30 +
  31 + @Resource
  32 + private ImFacePackItemService facePackItemService;
  33 +
  34 + @PostMapping("/create")
  35 + @Operation(summary = "新增表情")
  36 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:create')")
  37 + public CommonResult<Long> createFacePackItem(@Valid @RequestBody ImFacePackItemSaveReqVO reqVO) {
  38 + return success(facePackItemService.createFacePackItem(reqVO));
  39 + }
  40 +
  41 + @PutMapping("/update")
  42 + @Operation(summary = "修改表情")
  43 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:update')")
  44 + public CommonResult<Boolean> updateFacePackItem(@Valid @RequestBody ImFacePackItemSaveReqVO reqVO) {
  45 + facePackItemService.updateFacePackItem(reqVO);
  46 + return success(true);
  47 + }
  48 +
  49 + @DeleteMapping("/delete")
  50 + @Operation(summary = "删除表情")
  51 + @Parameter(name = "id", description = "编号", required = true, example = "2048")
  52 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:delete')")
  53 + public CommonResult<Boolean> deleteFacePackItem(@RequestParam("id") Long id) {
  54 + facePackItemService.deleteFacePackItem(id);
  55 + return success(true);
  56 + }
  57 +
  58 + @DeleteMapping("/delete-list")
  59 + @Operation(summary = "批量删除表情")
  60 + @Parameter(name = "ids", description = "编号列表", required = true)
  61 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:delete')")
  62 + public CommonResult<Boolean> deleteFacePackItemList(
  63 + @RequestParam("ids") @Size(max = 100, message = "批量删除最多 100 条") List<Long> ids) {
  64 + facePackItemService.deleteFacePackItemList(ids);
  65 + return success(true);
  66 + }
  67 +
  68 + @GetMapping("/page")
  69 + @Operation(summary = "获得表情分页")
  70 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:query')")
  71 + public CommonResult<PageResult<ImFacePackItemRespVO>> getFacePackItemPage(@Valid ImFacePackItemPageReqVO pageReqVO) {
  72 + PageResult<ImFacePackItemDO> pageResult = facePackItemService.getFacePackItemPage(pageReqVO);
  73 + return success(BeanUtils.toBean(pageResult, ImFacePackItemRespVO.class));
  74 + }
  75 +
  76 + @GetMapping("/get")
  77 + @Operation(summary = "获得表情详情")
  78 + @Parameter(name = "id", description = "编号", required = true, example = "2048")
  79 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:query')")
  80 + public CommonResult<ImFacePackItemRespVO> getFacePackItem(@RequestParam("id") Long id) {
  81 + ImFacePackItemDO item = facePackItemService.getFacePackItem(id);
  82 + return success(BeanUtils.toBean(item, ImFacePackItemRespVO.class));
  83 + }
  84 +
  85 +}
... ...
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.pack.ImFacePackPageReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack.ImFacePackRespVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack.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 jakarta.validation.constraints.Size;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
  20 +
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +
  25 +@Tag(name = "管理后台 - IM 表情包")
  26 +@RestController
  27 +@RequestMapping("/im/manager/face-pack")
  28 +@Validated
  29 +public class ImFacePackManagerController {
  30 +
  31 + @Resource
  32 + private ImFacePackService facePackService;
  33 +
  34 + @PostMapping("/create")
  35 + @Operation(summary = "新增表情包")
  36 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:create')")
  37 + public CommonResult<Long> createFacePack(@Valid @RequestBody ImFacePackSaveReqVO reqVO) {
  38 + return success(facePackService.createFacePack(reqVO));
  39 + }
  40 +
  41 + @PutMapping("/update")
  42 + @Operation(summary = "修改表情包")
  43 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:update')")
  44 + public CommonResult<Boolean> updateFacePack(@Valid @RequestBody ImFacePackSaveReqVO reqVO) {
  45 + facePackService.updateFacePack(reqVO);
  46 + return success(true);
  47 + }
  48 +
  49 + @DeleteMapping("/delete")
  50 + @Operation(summary = "删除表情包")
  51 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  52 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:delete')")
  53 + public CommonResult<Boolean> deleteFacePack(@RequestParam("id") Long id) {
  54 + facePackService.deleteFacePack(id);
  55 + return success(true);
  56 + }
  57 +
  58 + @DeleteMapping("/delete-list")
  59 + @Operation(summary = "批量删除表情包")
  60 + @Parameter(name = "ids", description = "编号列表", required = true)
  61 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:delete')")
  62 + public CommonResult<Boolean> deleteFacePackList(@RequestParam("ids")
  63 + @Size(max = 100, message = "批量删除最多 100 条") List<Long> ids) {
  64 + facePackService.deleteFacePackList(ids);
  65 + return success(true);
  66 + }
  67 +
  68 + @GetMapping("/page")
  69 + @Operation(summary = "获得表情包分页")
  70 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:query')")
  71 + public CommonResult<PageResult<ImFacePackRespVO>> getFacePackPage(@Valid ImFacePackPageReqVO pageReqVO) {
  72 + PageResult<ImFacePackDO> pageResult = facePackService.getFacePackPage(pageReqVO);
  73 + return success(BeanUtils.toBean(pageResult, ImFacePackRespVO.class));
  74 + }
  75 +
  76 + @GetMapping("/get")
  77 + @Operation(summary = "获得表情包详情")
  78 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  79 + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:query')")
  80 + public CommonResult<ImFacePackRespVO> getFacePack(@RequestParam("id") Long id) {
  81 + ImFacePackDO pack = facePackService.getFacePack(id);
  82 + return success(BeanUtils.toBean(pack, ImFacePackRespVO.class));
  83 + }
  84 +
  85 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFaceUserItemManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerPageReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerRespVO;
  9 +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO;
  10 +import cn.iocoder.yudao.module.im.service.face.ImFaceUserItemService;
  11 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  12 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  13 +import io.swagger.v3.oas.annotations.Operation;
  14 +import io.swagger.v3.oas.annotations.Parameter;
  15 +import io.swagger.v3.oas.annotations.tags.Tag;
  16 +import jakarta.annotation.Resource;
  17 +import jakarta.validation.Valid;
  18 +import org.springframework.security.access.prepost.PreAuthorize;
  19 +import org.springframework.validation.annotation.Validated;
  20 +import org.springframework.web.bind.annotation.*;
  21 +
  22 +import java.util.List;
  23 +import java.util.Map;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  26 +
  27 +@Tag(name = "管理后台 - IM 用户表情")
  28 +@RestController
  29 +@RequestMapping("/im/manager/face-user-item")
  30 +@Validated
  31 +public class ImFaceUserItemManagerController {
  32 +
  33 + @Resource
  34 + private ImFaceUserItemService faceUserItemService;
  35 + @Resource
  36 + private AdminUserApi adminUserApi;
  37 +
  38 + @GetMapping("/page")
  39 + @Operation(summary = "获得用户表情分页")
  40 + @PreAuthorize("@ss.hasPermission('im:manager:face-user-item:query')")
  41 + public CommonResult<PageResult<ImFaceUserItemManagerRespVO>> getFaceUserItemPage(
  42 + @Valid ImFaceUserItemManagerPageReqVO pageReqVO) {
  43 + PageResult<ImFaceUserItemDO> pageResult = faceUserItemService.getFaceUserItemPage(pageReqVO);
  44 + // 关联回填用户昵称
  45 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  46 + CollectionUtils.convertSet(pageResult.getList(), ImFaceUserItemDO::getUserId));
  47 + List<ImFaceUserItemManagerRespVO> voList = CollectionUtils.convertList(pageResult.getList(), item -> {
  48 + ImFaceUserItemManagerRespVO vo = BeanUtils.toBean(item, ImFaceUserItemManagerRespVO.class);
  49 + AdminUserRespDTO user = userMap.get(item.getUserId());
  50 + if (user != null) {
  51 + vo.setUserNickname(user.getNickname());
  52 + }
  53 + return vo;
  54 + });
  55 + return success(new PageResult<>(voList, pageResult.getTotal()));
  56 + }
  57 +
  58 + @DeleteMapping("/delete")
  59 + @Operation(summary = "删除用户表情")
  60 + @Parameter(name = "id", description = "编号", required = true, example = "4096")
  61 + @PreAuthorize("@ss.hasPermission('im:manager:face-user-item:delete')")
  62 + public CommonResult<Boolean> deleteFaceUserItem(@RequestParam("id") Long id) {
  63 + faceUserItemService.deleteFaceUserItem(id);
  64 + return success(true);
  65 + }
  66 +
  67 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +
  10 +@Schema(description = "管理后台 - IM 表情包项分页 Request VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +public class ImFacePackItemPageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "所属表情包编号", example = "1024")
  16 + private Long packId;
  17 +
  18 + @Schema(description = "表情名,模糊匹配", example = "狗")
  19 + private String name;
  20 +
  21 + @Schema(description = "状态", example = "0")
  22 + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
  23 + private Integer status; // 参见 CommonStatusEnum 枚举类
  24 +
  25 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item;
  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/item/ImFacePackItemSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.Max;
  7 +import jakarta.validation.constraints.Min;
  8 +import jakarta.validation.constraints.NotBlank;
  9 +import jakarta.validation.constraints.NotNull;
  10 +import jakarta.validation.constraints.Size;
  11 +import lombok.Data;
  12 +
  13 +@Schema(description = "管理后台 - IM 表情包项新增 / 修改 Request VO")
  14 +@Data
  15 +public class ImFacePackItemSaveReqVO {
  16 +
  17 + @Schema(description = "编号(修改时必填)", example = "2048")
  18 + private Long id;
  19 +
  20 + @Schema(description = "所属表情包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  21 + @NotNull(message = "表情包编号不能为空")
  22 + private Long packId;
  23 +
  24 + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  25 + example = "https://cdn.example.com/face/pack/cat-001.png")
  26 + @NotBlank(message = "表情图 URL 不能为空")
  27 + @Size(max = 512, message = "表情图 URL 长度不能超过 512")
  28 + private String url;
  29 +
  30 + @Schema(description = "表情名", example = "狗头")
  31 + @Size(max = 64, message = "表情名长度不能超过 64")
  32 + private String name;
  33 +
  34 + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  35 + @NotNull(message = "渲染宽度不能为空")
  36 + @Min(value = 1, message = "渲染宽度不能小于 1 像素")
  37 + @Max(value = 2048, message = "渲染宽度不能大于 2048 像素")
  38 + private Integer width;
  39 +
  40 + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
  41 + @NotNull(message = "渲染高度不能为空")
  42 + @Min(value = 1, message = "渲染高度不能小于 1 像素")
  43 + @Max(value = 2048, message = "渲染高度不能大于 2048 像素")
  44 + private Integer height;
  45 +
  46 + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  47 + @NotNull(message = "排序不能为空")
  48 + private Integer sort;
  49 +
  50 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  51 + @NotNull(message = "状态不能为空")
  52 + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
  53 + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)
  54 +
  55 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import org.springframework.format.annotation.DateTimeFormat;
  10 +
  11 +import java.time.LocalDateTime;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  14 +
  15 +@Schema(description = "管理后台 - IM 表情包分页 Request VO")
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +public class ImFacePackPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "表情包名称,模糊匹配", example = "猫")
  21 + private String name;
  22 +
  23 + @Schema(description = "状态", example = "0")
  24 + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
  25 + private Integer status; // 参见 CommonStatusEnum 枚举类
  26 +
  27 + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  28 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  29 + private LocalDateTime[] createTime;
  30 +
  31 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack;
  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 = "表情包图标", example = "https://cdn.example.com/face/pack/cat.png")
  19 + private String icon;
  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/pack/ImFacePackSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotBlank;
  7 +import jakarta.validation.constraints.NotNull;
  8 +import jakarta.validation.constraints.Size;
  9 +import lombok.Data;
  10 +
  11 +@Schema(description = "管理后台 - IM 表情包新增 / 修改 Request VO")
  12 +@Data
  13 +public class ImFacePackSaveReqVO {
  14 +
  15 + @Schema(description = "编号(修改时必填)", example = "1024")
  16 + private Long id;
  17 +
  18 + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子")
  19 + @NotBlank(message = "表情包名称不能为空")
  20 + @Size(max = 64, message = "表情包名称长度不能超过 64")
  21 + private String name;
  22 +
  23 + @Schema(description = "表情包图标", example = "https://cdn.example.com/face/pack/cat.png")
  24 + @Size(max = 512, message = "图标长度不能超过 512")
  25 + private String icon;
  26 +
  27 + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  28 + @NotNull(message = "排序不能为空")
  29 + private Integer sort;
  30 +
  31 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  32 + @NotNull(message = "状态不能为空")
  33 + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
  34 + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)
  35 +
  36 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/useritem/ImFaceUserItemManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 用户表情分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImFaceUserItemManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "所属用户编号", example = "1024")
  19 + private Long userId;
  20 +
  21 + @Schema(description = "表情名,模糊匹配", example = "狗")
  22 + private String name;
  23 +
  24 + @Schema(description = "添加时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  25 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  26 + private LocalDateTime[] createTime;
  27 +
  28 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/useritem/ImFaceUserItemManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 用户表情 Response VO")
  9 +@Data
  10 +public class ImFaceUserItemManagerRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
  13 + private Long id;
  14 +
  15 + @Schema(description = "所属用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long userId;
  17 +
  18 + @Schema(description = "所属用户昵称", example = "张三")
  19 + private String userNickname;
  20 +
  21 + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  22 + example = "https://cdn.example.com/face/user/abc.gif")
  23 + private String url;
  24 +
  25 + @Schema(description = "表情名", example = "狗头")
  26 + private String name;
  27 +
  28 + @Schema(description = "渲染宽度(像素)", example = "200")
  29 + private Integer width;
  30 +
  31 + @Schema(description = "渲染高度(像素)", example = "200")
  32 + private Integer height;
  33 +
  34 + @Schema(description = "添加时间", requiredMode = Schema.RequiredMode.REQUIRED)
  35 + private LocalDateTime createTime;
  36 +
  37 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.friend;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
  11 +import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
  12 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  13 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  14 +import io.swagger.v3.oas.annotations.Operation;
  15 +import io.swagger.v3.oas.annotations.tags.Tag;
  16 +import jakarta.annotation.Resource;
  17 +import jakarta.validation.Valid;
  18 +import org.springframework.security.access.prepost.PreAuthorize;
  19 +import org.springframework.validation.annotation.Validated;
  20 +import org.springframework.web.bind.annotation.GetMapping;
  21 +import org.springframework.web.bind.annotation.RequestMapping;
  22 +import org.springframework.web.bind.annotation.RestController;
  23 +
  24 +import java.util.Map;
  25 +import java.util.Set;
  26 +import java.util.stream.Stream;
  27 +
  28 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  30 +
  31 +@Tag(name = "管理后台 - IM 好友管理")
  32 +@RestController
  33 +@RequestMapping("/im/manager/friend")
  34 +@Validated
  35 +public class ImFriendManagerController {
  36 +
  37 + @Resource
  38 + private ImFriendService friendService;
  39 + @Resource
  40 + private AdminUserApi adminUserApi;
  41 +
  42 + @GetMapping("/page")
  43 + @Operation(summary = "获得好友关系分页")
  44 + @PreAuthorize("@ss.hasPermission('im:manager:friend:query')")
  45 + public CommonResult<PageResult<ImFriendManagerRespVO>> getFriendPage(
  46 + @Valid ImFriendManagerPageReqVO pageReqVO) {
  47 + // 1. 分页查询
  48 + PageResult<ImFriendDO> pageResult = friendService.getFriendPage(pageReqVO);
  49 + if (CollUtil.isEmpty(pageResult.getList())) {
  50 + return success(PageResult.empty(pageResult.getTotal()));
  51 + }
  52 + // 2.1 一次性批量查询用户 + 好友的昵称
  53 + Set<Long> userIds = convertSetByFlatMap(pageResult.getList(),
  54 + f -> Stream.of(f.getUserId(), f.getFriendUserId()));
  55 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  56 + // 2.2 转换为 VO,填充昵称
  57 + return success(BeanUtils.toBean(pageResult, ImFriendManagerRespVO.class, vo -> {
  58 + MapUtils.findAndThen(userMap, vo.getUserId(),
  59 + user -> vo.setUserNickname(user.getNickname()));
  60 + MapUtils.findAndThen(userMap, vo.getFriendUserId(),
  61 + user -> vo.setFriendNickname(user.getNickname()));
  62 + }));
  63 + }
  64 +
  65 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendRequestManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.friend;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendRequestManagerPageReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendRequestManagerRespVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO;
  11 +import cn.iocoder.yudao.module.im.service.friend.ImFriendRequestService;
  12 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  13 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  14 +import io.swagger.v3.oas.annotations.Operation;
  15 +import io.swagger.v3.oas.annotations.tags.Tag;
  16 +import jakarta.annotation.Resource;
  17 +import jakarta.validation.Valid;
  18 +import org.springframework.security.access.prepost.PreAuthorize;
  19 +import org.springframework.validation.annotation.Validated;
  20 +import org.springframework.web.bind.annotation.GetMapping;
  21 +import org.springframework.web.bind.annotation.RequestMapping;
  22 +import org.springframework.web.bind.annotation.RestController;
  23 +
  24 +import java.util.Map;
  25 +import java.util.Set;
  26 +import java.util.stream.Stream;
  27 +
  28 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  30 +
  31 +@Tag(name = "管理后台 - IM 好友申请管理")
  32 +@RestController
  33 +@RequestMapping("/im/manager/friend-request")
  34 +@Validated
  35 +public class ImFriendRequestManagerController {
  36 +
  37 + @Resource
  38 + private ImFriendRequestService friendRequestService;
  39 + @Resource
  40 + private AdminUserApi adminUserApi;
  41 +
  42 + @GetMapping("/page")
  43 + @Operation(summary = "获得好友申请分页")
  44 + @PreAuthorize("@ss.hasPermission('im:manager:friend-request:query')")
  45 + public CommonResult<PageResult<ImFriendRequestManagerRespVO>> getFriendRequestPage(
  46 + @Valid ImFriendRequestManagerPageReqVO pageReqVO) {
  47 + // 1. 分页查询
  48 + PageResult<ImFriendRequestDO> pageResult = friendRequestService.getFriendRequestPage(pageReqVO);
  49 + if (CollUtil.isEmpty(pageResult.getList())) {
  50 + return success(PageResult.empty(pageResult.getTotal()));
  51 + }
  52 +
  53 + // 2.1 一次性批量查询发起方 + 接收方的昵称
  54 + Set<Long> userIds = convertSetByFlatMap(pageResult.getList(),
  55 + request -> Stream.of(request.getFromUserId(), request.getToUserId()));
  56 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  57 + // 2.2 转换为 VO,填充昵称
  58 + return success(BeanUtils.toBean(pageResult, ImFriendRequestManagerRespVO.class, vo -> {
  59 + MapUtils.findAndThen(userMap, vo.getFromUserId(),
  60 + user -> vo.setFromNickname(user.getNickname()));
  61 + MapUtils.findAndThen(userMap, vo.getToUserId(),
  62 + user -> vo.setToNickname(user.getNickname()));
  63 + }));
  64 + }
  65 +
  66 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 好友关系分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImFriendManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "用户编号", example = "1024")
  19 + private Long userId;
  20 +
  21 + @Schema(description = "好友用户编号", example = "2048")
  22 + private Long friendUserId;
  23 +
  24 + @Schema(description = "好友状态", example = "0")
  25 + private Integer status; // 参见 CommonStatusEnum 枚举类
  26 +
  27 + @Schema(description = "是否免打扰", example = "false")
  28 + private Boolean silent;
  29 +
  30 + @Schema(description = "添加好友时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + private LocalDateTime[] addTime;
  33 +
  34 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 好友关系 Response VO")
  9 +@Data
  10 +public class ImFriendManagerRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long userId;
  17 +
  18 + @Schema(description = "用户昵称", example = "张三")
  19 + private String userNickname;
  20 +
  21 + @Schema(description = "好友用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  22 + private Long friendUserId;
  23 +
  24 + @Schema(description = "好友昵称", example = "李四")
  25 + private String friendNickname;
  26 +
  27 + @Schema(description = "好友展示备注")
  28 + private String displayName;
  29 +
  30 + @Schema(description = "添加来源", example = "1")
  31 + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举类
  32 +
  33 + @Schema(description = "是否免打扰", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  34 + private Boolean silent;
  35 +
  36 + @Schema(description = "是否置顶联系人", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  37 + private Boolean pinned;
  38 +
  39 + @Schema(description = "是否拉黑", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  40 + private Boolean blocked;
  41 +
  42 + @Schema(description = "好友状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  43 + private Integer status; // 参见 CommonStatusEnum 枚举类
  44 +
  45 + @Schema(description = "添加好友时间")
  46 + private LocalDateTime addTime;
  47 +
  48 + @Schema(description = "删除好友时间")
  49 + private LocalDateTime deleteTime;
  50 +
  51 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  52 + private LocalDateTime createTime;
  53 +
  54 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendRequestManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 好友申请分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImFriendRequestManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "发起方用户编号", example = "1024")
  19 + private Long fromUserId;
  20 +
  21 + @Schema(description = "接收方用户编号", example = "2048")
  22 + private Long toUserId;
  23 +
  24 + @Schema(description = "处理结果", example = "0")
  25 + private Integer handleResult; // 参见 ImFriendRequestHandleResultEnum 枚举类
  26 +
  27 + @Schema(description = "添加来源", example = "1")
  28 + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举类
  29 +
  30 + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + private LocalDateTime[] createTime;
  33 +
  34 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendRequestManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 好友申请 Response VO")
  9 +@Data
  10 +public class ImFriendRequestManagerRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "发起方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long fromUserId;
  17 +
  18 + @Schema(description = "发起方昵称", example = "张三")
  19 + private String fromNickname;
  20 +
  21 + @Schema(description = "接收方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  22 + private Long toUserId;
  23 +
  24 + @Schema(description = "接收方昵称", example = "李四")
  25 + private String toNickname;
  26 +
  27 + @Schema(description = "申请理由", example = "我是芋艿")
  28 + private String applyContent;
  29 +
  30 + @Schema(description = "发起方对接收方的备注")
  31 + private String displayName;
  32 +
  33 + @Schema(description = "添加来源", example = "1")
  34 + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举类
  35 +
  36 + @Schema(description = "处理结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  37 + private Integer handleResult; // 参见 ImFriendRequestHandleResultEnum 枚举类
  38 +
  39 + @Schema(description = "处理理由", example = "暂不通过")
  40 + private String handleContent;
  41 +
  42 + @Schema(description = "处理时间")
  43 + private LocalDateTime handleTime;
  44 +
  45 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  46 + private LocalDateTime createTime;
  47 +
  48 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO;
  10 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO;
  11 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
  12 +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
  13 +import cn.iocoder.yudao.module.im.service.group.ImGroupService;
  14 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  15 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  16 +import io.swagger.v3.oas.annotations.Operation;
  17 +import io.swagger.v3.oas.annotations.Parameter;
  18 +import io.swagger.v3.oas.annotations.tags.Tag;
  19 +import jakarta.annotation.Resource;
  20 +import jakarta.validation.Valid;
  21 +import org.springframework.security.access.prepost.PreAuthorize;
  22 +import org.springframework.validation.annotation.Validated;
  23 +import org.springframework.web.bind.annotation.*;
  24 +
  25 +import java.util.Map;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  29 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  30 +
  31 +@Tag(name = "管理后台 - IM 群聊管理")
  32 +@RestController
  33 +@RequestMapping("/im/manager/group")
  34 +@Validated
  35 +public class ImGroupManagerController {
  36 +
  37 + @Resource
  38 + private ImGroupService groupService;
  39 + @Resource
  40 + private ImGroupMemberService groupMemberService;
  41 +
  42 + @Resource
  43 + private AdminUserApi adminUserApi;
  44 +
  45 + @GetMapping("/page")
  46 + @Operation(summary = "获得群分页")
  47 + @PreAuthorize("@ss.hasPermission('im:manager:group:query')")
  48 + public CommonResult<PageResult<ImGroupManagerRespVO>> getGroupPage(@Valid ImGroupManagerPageReqVO pageReqVO) {
  49 + // 1. 分页查询群
  50 + PageResult<ImGroupDO> pageResult = groupService.getGroupPage(pageReqVO);
  51 + if (CollUtil.isEmpty(pageResult.getList())) {
  52 + return success(PageResult.empty(pageResult.getTotal()));
  53 + }
  54 + // 2.1 批量查询相关数据
  55 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  56 + convertSet(pageResult.getList(), ImGroupDO::getOwnerUserId));
  57 + Map<Long, Long> memberCountMap = groupMemberService.getActiveMemberCountMap(
  58 + convertSet(pageResult.getList(), ImGroupDO::getId));
  59 + // 2.2 转换为 VO,填充群主昵称、群成员数量
  60 + return success(BeanUtils.toBean(pageResult, ImGroupManagerRespVO.class, vo -> {
  61 + MapUtils.findAndThen(userMap, vo.getOwnerUserId(),
  62 + user -> vo.setOwnerNickname(user.getNickname()));
  63 + vo.setMemberCount(memberCountMap.getOrDefault(vo.getId(), 0L).intValue());
  64 + }));
  65 + }
  66 +
  67 + @GetMapping("/get")
  68 + @Operation(summary = "获得群详情")
  69 + @Parameter(name = "id", description = "群编号", required = true, example = "1024")
  70 + @PreAuthorize("@ss.hasPermission('im:manager:group:query')")
  71 + public CommonResult<ImGroupManagerRespVO> getGroup(@RequestParam("id") Long id) {
  72 + ImGroupDO group = groupService.getGroup(id);
  73 + return success(BeanUtils.toBean(group, ImGroupManagerRespVO.class));
  74 + }
  75 +
  76 + @PutMapping("/ban")
  77 + @Operation(summary = "封禁群")
  78 + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')")
  79 + public CommonResult<Boolean> banGroup(@Valid @RequestBody ImGroupManagerBanReqVO reqVO) {
  80 + groupService.banGroup(getLoginUserId(), reqVO);
  81 + return success(true);
  82 + }
  83 +
  84 + @PutMapping("/unban")
  85 + @Operation(summary = "解封群")
  86 + @Parameter(name = "id", description = "群编号", required = true, example = "1024")
  87 + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')")
  88 + public CommonResult<Boolean> unbanGroup(@RequestParam("id") Long id) {
  89 + groupService.unbanGroup(getLoginUserId(), id);
  90 + return success(true);
  91 + }
  92 +
  93 + @DeleteMapping("/dissolve")
  94 + @Operation(summary = "解散群")
  95 + @Parameter(name = "id", description = "群编号", required = true, example = "1024")
  96 + @PreAuthorize("@ss.hasPermission('im:manager:group:dissolve')")
  97 + public CommonResult<Boolean> dissolveGroup(@RequestParam("id") Long id) {
  98 + groupService.dissolveGroupByManager(getLoginUserId(), id);
  99 + return success(true);
  100 + }
  101 +
  102 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO;
  8 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
  9 +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
  10 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  11 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
  15 +import jakarta.annotation.Resource;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.GetMapping;
  19 +import org.springframework.web.bind.annotation.RequestMapping;
  20 +import org.springframework.web.bind.annotation.RequestParam;
  21 +import org.springframework.web.bind.annotation.RestController;
  22 +
  23 +import java.util.Collections;
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  29 +
  30 +@Tag(name = "管理后台 - IM 群成员管理")
  31 +@RestController
  32 +@RequestMapping("/im/manager/group/member")
  33 +@Validated
  34 +public class ImGroupMemberManagerController {
  35 +
  36 + @Resource
  37 + private ImGroupMemberService groupMemberService;
  38 + @Resource
  39 + private AdminUserApi adminUserApi;
  40 +
  41 + @GetMapping("/list")
  42 + @Operation(summary = "获得群成员列表(含已退群成员,由前端按需过滤)")
  43 + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
  44 + @PreAuthorize("@ss.hasPermission('im:manager:group:query')")
  45 + public CommonResult<List<ImGroupMemberManagerRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) {
  46 + // 1. 查询群全部成员(含已退群)
  47 + List<ImGroupMemberDO> members = groupMemberService.getGroupMemberListByGroupId(groupId);
  48 + if (CollUtil.isEmpty(members)) {
  49 + return success(Collections.emptyList());
  50 + }
  51 + // 2.1 批量查询用户信息
  52 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  53 + convertSet(members, ImGroupMemberDO::getUserId));
  54 + // 2.2 转换为 VO,填充昵称、头像
  55 + return success(BeanUtils.toBean(members, ImGroupMemberManagerRespVO.class, vo ->
  56 + MapUtils.findAndThen(userMap, vo.getUserId(), user ->
  57 + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()))));
  58 + }
  59 +
  60 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupRequestManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupRequestManagerPageReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupRequestManagerRespVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
  11 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO;
  12 +import cn.iocoder.yudao.module.im.service.group.ImGroupRequestService;
  13 +import cn.iocoder.yudao.module.im.service.group.ImGroupService;
  14 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  15 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  16 +import io.swagger.v3.oas.annotations.Operation;
  17 +import io.swagger.v3.oas.annotations.tags.Tag;
  18 +import jakarta.annotation.Resource;
  19 +import jakarta.validation.Valid;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.validation.annotation.Validated;
  22 +import org.springframework.web.bind.annotation.GetMapping;
  23 +import org.springframework.web.bind.annotation.RequestMapping;
  24 +import org.springframework.web.bind.annotation.RestController;
  25 +
  26 +import java.util.Map;
  27 +import java.util.Objects;
  28 +import java.util.Set;
  29 +import java.util.stream.Stream;
  30 +
  31 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  32 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  34 +
  35 +@Tag(name = "管理后台 - IM 加群申请管理")
  36 +@RestController
  37 +@RequestMapping("/im/manager/group-request")
  38 +@Validated
  39 +public class ImGroupRequestManagerController {
  40 +
  41 + @Resource
  42 + private ImGroupRequestService groupRequestService;
  43 + @Resource
  44 + private ImGroupService groupService;
  45 + @Resource
  46 + private AdminUserApi adminUserApi;
  47 +
  48 + @GetMapping("/page")
  49 + @Operation(summary = "获得加群申请分页")
  50 + @PreAuthorize("@ss.hasPermission('im:manager:group-request:query')")
  51 + public CommonResult<PageResult<ImGroupRequestManagerRespVO>> getGroupRequestPage(
  52 + @Valid ImGroupRequestManagerPageReqVO pageReqVO) {
  53 + // 1. 分页查询
  54 + PageResult<ImGroupRequestDO> pageResult = groupRequestService.getGroupRequestPage(pageReqVO);
  55 + if (CollUtil.isEmpty(pageResult.getList())) {
  56 + return success(PageResult.empty(pageResult.getTotal()));
  57 + }
  58 +
  59 + // 2.1 批量聚合 user / inviter / handler 用户昵称
  60 + Set<Long> userIds = convertSetByFlatMap(pageResult.getList(),
  61 + request -> Stream.of(request.getUserId(), request.getInviterUserId(), request.getHandleUserId())
  62 + .filter(Objects::nonNull));
  63 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  64 + // 2.2 批量聚合群信息(取群名)
  65 + Set<Long> groupIds = convertSet(pageResult.getList(), ImGroupRequestDO::getGroupId);
  66 + Map<Long, ImGroupDO> groupMap = groupService.getGroupMap(groupIds);
  67 + return success(BeanUtils.toBean(pageResult, ImGroupRequestManagerRespVO.class, vo -> {
  68 + MapUtils.findAndThen(userMap, vo.getUserId(), user -> vo.setUserNickname(user.getNickname()));
  69 + MapUtils.findAndThen(userMap, vo.getInviterUserId(), user -> vo.setInviterNickname(user.getNickname()));
  70 + MapUtils.findAndThen(userMap, vo.getHandleUserId(), user -> vo.setHandleNickname(user.getNickname()));
  71 + MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName()));
  72 + }));
  73 + }
  74 +
  75 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerBanReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group.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 ImGroupManagerBanReqVO {
  12 +
  13 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotNull(message = "群编号不能为空")
  15 + private Long id;
  16 +
  17 + @Schema(description = "封禁原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "违规内容")
  18 + @NotBlank(message = "封禁原因不能为空")
  19 + @Size(max = 200, message = "封禁原因长度不能超过 200")
  20 + private String reason;
  21 +
  22 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group.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 ImGroupManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "群名称,模糊匹配", example = "技术交流群")
  19 + private String name;
  20 +
  21 + @Schema(description = "群主用户编号", example = "1024")
  22 + private Long ownerUserId;
  23 +
  24 + @Schema(description = "群状态", example = "0")
  25 + private Integer status; // 参见 CommonStatusEnum 枚举类(0 正常 / 1 已解散)
  26 +
  27 + @Schema(description = "是否封禁", example = "false")
  28 + private Boolean banned;
  29 +
  30 + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + private LocalDateTime[] createTime;
  33 +
  34 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group.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 ImGroupManagerRespVO {
  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 = "群头像")
  19 + private String avatar;
  20 +
  21 + @Schema(description = "群公告")
  22 + private String notice;
  23 +
  24 + @Schema(description = "群主用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  25 + private Long ownerUserId;
  26 +
  27 + @Schema(description = "群主昵称", example = "张三")
  28 + private String ownerNickname;
  29 +
  30 + @Schema(description = "群成员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
  31 + private Integer memberCount;
  32 +
  33 + @Schema(description = "群状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  34 + private Integer status; // 参见 CommonStatusEnum 枚举类
  35 +
  36 + @Schema(description = "解散时间")
  37 + private LocalDateTime dissolvedTime;
  38 +
  39 + @Schema(description = "是否封禁", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  40 + private Boolean banned;
  41 +
  42 + @Schema(description = "是否全群禁言")
  43 + private Boolean mutedAll;
  44 +
  45 + @Schema(description = "封禁原因")
  46 + private String bannedReason;
  47 +
  48 + @Schema(description = "封禁时间")
  49 + private LocalDateTime bannedTime;
  50 +
  51 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  52 + private LocalDateTime createTime;
  53 +
  54 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupRequestManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group.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 ImGroupRequestManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "群编号", example = "1024")
  19 + private Long groupId;
  20 +
  21 + @Schema(description = "申请人 / 被邀请人用户编号", example = "2048")
  22 + private Long userId;
  23 +
  24 + @Schema(description = "邀请人用户编号", example = "31460")
  25 + private Long inviterUserId;
  26 +
  27 + @Schema(description = "处理结果", example = "0")
  28 + private Integer handleResult; // 参见 ImGroupRequestHandleResultEnum 枚举类
  29 +
  30 + @Schema(description = "加入来源", example = "1")
  31 + private Integer addSource; // 参见 ImGroupAddSourceEnum 枚举类
  32 +
  33 + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  34 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  35 + private LocalDateTime[] createTime;
  36 +
  37 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupRequestManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group.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 ImGroupRequestManagerRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long groupId;
  17 +
  18 + @Schema(description = "群名称", example = "芋道技术交流群")
  19 + private String groupName;
  20 +
  21 + @Schema(description = "申请人 / 被邀请人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  22 + private Long userId;
  23 +
  24 + @Schema(description = "申请人 / 被邀请人昵称", example = "张三")
  25 + private String userNickname;
  26 +
  27 + @Schema(description = "邀请人用户编号;NULL 表示用户主动申请", example = "200")
  28 + private Long inviterUserId;
  29 +
  30 + @Schema(description = "邀请人昵称", example = "老张")
  31 + private String inviterNickname;
  32 +
  33 + @Schema(description = "申请理由", example = "我想加入这个群")
  34 + private String applyContent;
  35 +
  36 + @Schema(description = "加入来源", example = "1")
  37 + private Integer addSource; // 参见 ImGroupAddSourceEnum 枚举类
  38 +
  39 + @Schema(description = "处理结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  40 + private Integer handleResult; // 参见 ImGroupRequestHandleResultEnum 枚举类
  41 +
  42 + @Schema(description = "处理人用户编号", example = "31460")
  43 + private Long handleUserId;
  44 +
  45 + @Schema(description = "处理人昵称", example = "管理员")
  46 + private String handleNickname;
  47 +
  48 + @Schema(description = "处理理由", example = "暂不通过")
  49 + private String handleContent;
  50 +
  51 + @Schema(description = "处理时间")
  52 + private LocalDateTime handleTime;
  53 +
  54 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  55 + private LocalDateTime createTime;
  56 +
  57 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/member/ImGroupMemberManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member;
  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 ImGroupMemberManagerRespVO {
  11 +
  12 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long userId;
  14 +
  15 + @Schema(description = "用户昵称", example = "张三")
  16 + private String nickname;
  17 +
  18 + @Schema(description = "用户头像")
  19 + private String avatar;
  20 +
  21 + @Schema(description = "组内显示名", example = "三哥")
  22 + private String displayUserName;
  23 +
  24 + @Schema(description = "群备注", example = "技术交流群")
  25 + private String groupRemark;
  26 +
  27 + @Schema(description = "是否免打扰", example = "false")
  28 + private Boolean silent;
  29 +
  30 + @Schema(description = "成员状态", example = "0")
  31 + private Integer status; // 参见 CommonStatusEnum 枚举类
  32 +
  33 + @Schema(description = "成员角色;1=群主 2=管理员 3=普通成员", example = "3")
  34 + private Integer role; // 参见 ImGroupMemberRoleEnum 枚举类
  35 +
  36 + @Schema(description = "入群时间")
  37 + private LocalDateTime joinTime;
  38 +
  39 + @Schema(description = "退群时间")
  40 + private LocalDateTime quitTime;
  41 +
  42 + @Schema(description = "禁言到期时间")
  43 + private LocalDateTime muteEndTime;
  44 +
  45 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImChannelMessageManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessagePageReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessageRespVO;
  10 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessageSendReqVO;
  11 +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO;
  12 +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO;
  13 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
  14 +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService;
  15 +import cn.iocoder.yudao.module.im.service.message.ImChannelMessageService;
  16 +import cn.iocoder.yudao.module.im.service.channel.ImChannelService;
  17 +import io.swagger.v3.oas.annotations.Operation;
  18 +import io.swagger.v3.oas.annotations.Parameter;
  19 +import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import jakarta.annotation.Resource;
  21 +import jakarta.validation.Valid;
  22 +import org.springframework.security.access.prepost.PreAuthorize;
  23 +import org.springframework.validation.annotation.Validated;
  24 +import org.springframework.web.bind.annotation.*;
  25 +
  26 +import java.util.Map;
  27 +
  28 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  30 +
  31 +@Tag(name = "管理后台 - IM 频道消息")
  32 +@RestController
  33 +@RequestMapping("/im/manager/channel-message")
  34 +@Validated
  35 +public class ImChannelMessageManagerController {
  36 +
  37 + @Resource
  38 + private ImChannelMessageService channelMessageService;
  39 + @Resource
  40 + private ImChannelService channelService;
  41 + @Resource
  42 + private ImChannelMaterialService channelMaterialService;
  43 +
  44 + @PostMapping("/send")
  45 + @Operation(summary = "立即推送频道消息")
  46 + @PreAuthorize("@ss.hasPermission('im:manager:channel-message:send')")
  47 + public CommonResult<Long> sendMessage(@Valid @RequestBody ImChannelMessageSendReqVO reqVO) {
  48 + return success(channelMessageService.sendMessage(reqVO));
  49 + }
  50 +
  51 + @DeleteMapping("/delete")
  52 + @Operation(summary = "删除频道消息")
  53 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  54 + @PreAuthorize("@ss.hasPermission('im:manager:channel-message:delete')")
  55 + public CommonResult<Boolean> deleteMessage(@RequestParam("id") Long id) {
  56 + channelMessageService.deleteMessage(id);
  57 + return success(true);
  58 + }
  59 +
  60 + @GetMapping("/page")
  61 + @Operation(summary = "获得频道消息分页;回填频道名 / 素材标题")
  62 + @PreAuthorize("@ss.hasPermission('im:manager:channel-message:query')")
  63 + public CommonResult<PageResult<ImChannelMessageRespVO>> getMessagePage(@Valid ImChannelMessagePageReqVO pageReqVO) {
  64 + PageResult<ImChannelMessageDO> pageResult = channelMessageService.getMessagePage(pageReqVO);
  65 + if (CollUtil.isEmpty(pageResult.getList())) {
  66 + return success(PageResult.empty(pageResult.getTotal()));
  67 + }
  68 + // 批量查询频道和素材,并回填频道名 / 素材标题
  69 + Map<Long, ImChannelDO> channelMap = channelService.getChannelMap(
  70 + convertSet(pageResult.getList(), ImChannelMessageDO::getChannelId));
  71 + Map<Long, ImChannelMaterialDO> materialMap = channelMaterialService.getMaterialMap(
  72 + convertSet(pageResult.getList(), ImChannelMessageDO::getMaterialId));
  73 + return success(BeanUtils.toBean(pageResult, ImChannelMessageRespVO.class, vo -> {
  74 + MapUtils.findAndThen(channelMap, vo.getChannelId(), c -> vo.setChannelName(c.getName()));
  75 + MapUtils.findAndThen(materialMap, vo.getMaterialId(),
  76 + material -> vo.setMaterialTitle(material.getTitle()).setMaterialCoverUrl(material.getCoverUrl()));
  77 + }));
  78 + }
  79 +
  80 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
  11 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
  12 +import cn.iocoder.yudao.module.im.service.group.ImGroupService;
  13 +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService;
  14 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  15 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  16 +import io.swagger.v3.oas.annotations.Operation;
  17 +import io.swagger.v3.oas.annotations.Parameter;
  18 +import io.swagger.v3.oas.annotations.tags.Tag;
  19 +import jakarta.annotation.Resource;
  20 +import jakarta.validation.Valid;
  21 +import org.springframework.security.access.prepost.PreAuthorize;
  22 +import org.springframework.validation.annotation.Validated;
  23 +import org.springframework.web.bind.annotation.GetMapping;
  24 +import org.springframework.web.bind.annotation.RequestMapping;
  25 +import org.springframework.web.bind.annotation.RequestParam;
  26 +import org.springframework.web.bind.annotation.RestController;
  27 +
  28 +import java.util.Map;
  29 +import java.util.Objects;
  30 +import java.util.Set;
  31 +import java.util.stream.Stream;
  32 +
  33 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  36 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  37 +import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.AT_USER_ID_ALL;
  38 +
  39 +@Tag(name = "管理后台 - IM 群聊消息")
  40 +@RestController
  41 +@RequestMapping("/im/manager/message/group")
  42 +@Validated
  43 +public class ImGroupMessageManagerController {
  44 +
  45 + @Resource
  46 + private ImGroupMessageService groupMessageService;
  47 + @Resource
  48 + private ImGroupService groupService;
  49 + @Resource
  50 + private AdminUserApi adminUserApi;
  51 +
  52 + @GetMapping("/page")
  53 + @Operation(summary = "获得群聊消息分页")
  54 + @PreAuthorize("@ss.hasPermission('im:manager:message:query')")
  55 + public CommonResult<PageResult<ImGroupMessageManagerRespVO>> getGroupMessagePage(
  56 + @Valid ImGroupMessageManagerPageReqVO pageReqVO) {
  57 + // 1. 分页查询
  58 + PageResult<ImGroupMessageDO> pageResult = groupMessageService.getGroupMessagePage(pageReqVO);
  59 + if (CollUtil.isEmpty(pageResult.getList())) {
  60 + return success(PageResult.empty(pageResult.getTotal()));
  61 + }
  62 + // 2.1 批量查询群名称、发送人昵称、@ 用户昵称(-1 表示 @所有人,跳过查询,由前端判断渲染)
  63 + Map<Long, ImGroupDO> groupMap = groupService.getGroupMap(
  64 + convertSet(pageResult.getList(), ImGroupMessageDO::getGroupId));
  65 + Set<Long> userIds = convertSetByFlatMap(pageResult.getList(), m -> Stream.concat(
  66 + Stream.of(m.getSenderId()),
  67 + CollUtil.emptyIfNull(m.getAtUserIds()).stream()
  68 + .filter(id -> !Objects.equals(id, AT_USER_ID_ALL))));
  69 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  70 + // 2.2 转换为 VO,填充群名 / 发送人昵称 / @ 用户昵称(-1 位置留 null,由前端展示「@所有人」)
  71 + return success(BeanUtils.toBean(pageResult, ImGroupMessageManagerRespVO.class, vo -> {
  72 + MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName()));
  73 + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname()));
  74 + if (CollUtil.isNotEmpty(vo.getAtUserIds())) {
  75 + vo.setAtUserNicknames(convertList(vo.getAtUserIds(), id -> {
  76 + AdminUserRespDTO user = userMap.get(id);
  77 + return user != null ? user.getNickname() : null;
  78 + }));
  79 + }
  80 + }));
  81 + }
  82 +
  83 + @GetMapping("/get")
  84 + @Operation(summary = "获得群聊消息详情")
  85 + @Parameter(name = "id", description = "消息编号", required = true, example = "1024")
  86 + @PreAuthorize("@ss.hasPermission('im:manager:message:query')")
  87 + public CommonResult<ImGroupMessageManagerRespVO> getGroupMessage(@RequestParam("id") Long id) {
  88 + ImGroupMessageDO message = groupMessageService.getGroupMessage(id);
  89 + return success(BeanUtils.toBean(message, ImGroupMessageManagerRespVO.class));
  90 + }
  91 +
  92 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
  11 +import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
  12 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  13 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  14 +import io.swagger.v3.oas.annotations.Operation;
  15 +import io.swagger.v3.oas.annotations.Parameter;
  16 +import io.swagger.v3.oas.annotations.tags.Tag;
  17 +import jakarta.annotation.Resource;
  18 +import jakarta.validation.Valid;
  19 +import org.springframework.security.access.prepost.PreAuthorize;
  20 +import org.springframework.validation.annotation.Validated;
  21 +import org.springframework.web.bind.annotation.GetMapping;
  22 +import org.springframework.web.bind.annotation.RequestMapping;
  23 +import org.springframework.web.bind.annotation.RequestParam;
  24 +import org.springframework.web.bind.annotation.RestController;
  25 +
  26 +import java.util.Map;
  27 +import java.util.stream.Stream;
  28 +
  29 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  31 +
  32 +@Tag(name = "管理后台 - IM 私聊消息")
  33 +@RestController
  34 +@RequestMapping("/im/manager/message/private")
  35 +@Validated
  36 +public class ImPrivateMessageManagerController {
  37 +
  38 + @Resource
  39 + private ImPrivateMessageService privateMessageService;
  40 + @Resource
  41 + private AdminUserApi adminUserApi;
  42 +
  43 + @GetMapping("/page")
  44 + @Operation(summary = "获得私聊消息分页")
  45 + @PreAuthorize("@ss.hasPermission('im:manager:message:query')")
  46 + public CommonResult<PageResult<ImPrivateMessageManagerRespVO>> getPrivateMessagePage(
  47 + @Valid ImPrivateMessageManagerPageReqVO pageReqVO) {
  48 + // 1. 分页查询
  49 + PageResult<ImPrivateMessageDO> pageResult = privateMessageService.getPrivateMessagePage(pageReqVO);
  50 + if (CollUtil.isEmpty(pageResult.getList())) {
  51 + return success(PageResult.empty(pageResult.getTotal()));
  52 + }
  53 + // 2.1 一次性批量查询发送人 + 接收人昵称
  54 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(pageResult.getList(),
  55 + m -> Stream.of(m.getSenderId(), m.getReceiverId())));
  56 + // 2.2 转换为 VO,填充昵称
  57 + return success(BeanUtils.toBean(pageResult, ImPrivateMessageManagerRespVO.class, vo -> {
  58 + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname()));
  59 + MapUtils.findAndThen(userMap, vo.getReceiverId(), user -> vo.setReceiverNickname(user.getNickname()));
  60 + }));
  61 + }
  62 +
  63 + @GetMapping("/get")
  64 + @Operation(summary = "获得私聊消息详情")
  65 + @Parameter(name = "id", description = "消息编号", required = true, example = "1024")
  66 + @PreAuthorize("@ss.hasPermission('im:manager:message:query')")
  67 + public CommonResult<ImPrivateMessageManagerRespVO> getPrivateMessage(@RequestParam("id") Long id) {
  68 + ImPrivateMessageDO message = privateMessageService.getPrivateMessage(id);
  69 + return success(BeanUtils.toBean(message, ImPrivateMessageManagerRespVO.class));
  70 + }
  71 +
  72 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessagePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel;
  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 ImChannelMessagePageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "频道编号", example = "1")
  19 + private Long channelId;
  20 +
  21 + @Schema(description = "素材编号", example = "1024")
  22 + private Long materialId;
  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[] sendTime;
  27 +
  28 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - IM 频道消息 Response VO")
  10 +@Data
  11 +public class ImChannelMessageRespVO {
  12 +
  13 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "频道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Long channelId;
  18 +
  19 + @Schema(description = "频道名称(关联查询填充)")
  20 + private String channelName;
  21 +
  22 + @Schema(description = "素材编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  23 + private Long materialId;
  24 +
  25 + @Schema(description = "素材标题(关联查询填充)")
  26 + private String materialTitle;
  27 +
  28 + @Schema(description = "素材封面 URL(关联查询填充)")
  29 + private String materialCoverUrl;
  30 +
  31 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "125")
  32 + private Integer type; // 参见 ImMessageTypeEnum 枚举类
  33 +
  34 + @Schema(description = "消息内容;payload JSON 快照")
  35 + private String content;
  36 +
  37 + @Schema(description = "接收人编号列表;为空表示全员")
  38 + private List<Long> receiverUserIds;
  39 +
  40 + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
  41 + private LocalDateTime sendTime;
  42 +
  43 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageSendReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - IM 频道消息推送 Request VO")
  10 +@Data
  11 +public class ImChannelMessageSendReqVO {
  12 +
  13 + @Schema(description = "素材编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotNull(message = "素材编号不能为空")
  15 + private Long materialId;
  16 +
  17 + @Schema(description = "接收人编号列表;为空表示全员", example = "[1024, 2048]")
  18 + private List<Long> receiverUserIds;
  19 +
  20 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 群聊消息分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImGroupMessageManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "群编号", example = "1024")
  19 + private Long groupId;
  20 +
  21 + @Schema(description = "发送人编号", example = "1024")
  22 + private Long senderId;
  23 +
  24 + @Schema(description = "消息类型", example = "1")
  25 + private Integer type; // 参见 ImMessageTypeEnum 枚举类
  26 +
  27 + @Schema(description = "消息内容", example = "你好")
  28 + private String content;
  29 +
  30 + @Schema(description = "消息状态", example = "0")
  31 + private Integer status; // 参见 ImMessageStatusEnum 枚举类
  32 +
  33 + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  34 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  35 + private LocalDateTime[] sendTime;
  36 +
  37 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - IM 群聊消息 Response VO")
  10 +@Data
  11 +public class ImGroupMessageManagerRespVO {
  12 +
  13 + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "客户端消息编号", example = "c-uuid-xxx")
  17 + private String clientMessageId;
  18 +
  19 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  20 + private Long groupId;
  21 +
  22 + @Schema(description = "群名称", example = "技术交流群")
  23 + private String groupName;
  24 +
  25 + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  26 + private Long senderId;
  27 +
  28 + @Schema(description = "发送人昵称", example = "张三")
  29 + private String senderNickname;
  30 +
  31 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  32 + private Integer type; // 参见 ImMessageTypeEnum 枚举类
  33 +
  34 + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED)
  35 + private String content;
  36 +
  37 + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  38 + private Integer status; // 参见 ImMessageStatusEnum 枚举类
  39 +
  40 + @Schema(description = "@ 目标用户编号列表(-1 表示 @所有人)")
  41 + private List<Long> atUserIds;
  42 + @Schema(description = "@ 目标用户昵称列表(-1 位置为 null,前端根据 atUserIds 自行展示「@所有人」)")
  43 + private List<String> atUserNicknames;
  44 +
  45 + @Schema(description = "回执状态", example = "0")
  46 + private Integer receiptStatus; // 参见 ImGroupMessageReceiptStatusEnum 枚举类
  47 +
  48 + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
  49 + private LocalDateTime sendTime;
  50 +
  51 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  52 + private LocalDateTime createTime;
  53 +
  54 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 私聊消息分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImPrivateMessageManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "发送人编号", example = "1024")
  19 + private Long senderId;
  20 +
  21 + @Schema(description = "接收人编号", example = "2048")
  22 + private Long receiverId;
  23 +
  24 + @Schema(description = "消息类型", example = "1")
  25 + private Integer type; // 参见 ImMessageTypeEnum 枚举类
  26 +
  27 + @Schema(description = "消息内容", example = "你好")
  28 + private String content;
  29 +
  30 + @Schema(description = "消息状态", example = "0")
  31 + private Integer status; // 参见 ImMessageStatusEnum 枚举类
  32 +
  33 + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  34 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  35 + private LocalDateTime[] sendTime;
  36 +
  37 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 私聊消息 Response VO")
  9 +@Data
  10 +public class ImPrivateMessageManagerRespVO {
  11 +
  12 + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "客户端消息编号", example = "c-uuid-xxx")
  16 + private String clientMessageId;
  17 +
  18 + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  19 + private Long senderId;
  20 +
  21 + @Schema(description = "发送人昵称", example = "张三")
  22 + private String senderNickname;
  23 +
  24 + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  25 + private Long receiverId;
  26 +
  27 + @Schema(description = "接收人昵称", example = "李四")
  28 + private String receiverNickname;
  29 +
  30 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  31 + private Integer type; // 参见 ImMessageTypeEnum 枚举类
  32 +
  33 + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED)
  34 + private String content;
  35 +
  36 + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  37 + private Integer status; // 参见 ImMessageStatusEnum 枚举类
  38 +
  39 + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
  40 + private LocalDateTime sendTime;
  41 +
  42 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  43 + private LocalDateTime createTime;
  44 +
  45 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/ImRtcCallManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.rtc;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo.ImRtcCallManagerPageReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo.ImRtcCallManagerRespVO;
  10 +import cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo.ImRtcParticipantManagerRespVO;
  11 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
  12 +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO;
  13 +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcParticipantDO;
  14 +import cn.iocoder.yudao.module.im.service.group.ImGroupService;
  15 +import cn.iocoder.yudao.module.im.service.rtc.ImRtcCallService;
  16 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  17 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  18 +import io.swagger.v3.oas.annotations.Operation;
  19 +import io.swagger.v3.oas.annotations.Parameter;
  20 +import io.swagger.v3.oas.annotations.tags.Tag;
  21 +import jakarta.annotation.Resource;
  22 +import jakarta.validation.Valid;
  23 +import org.springframework.security.access.prepost.PreAuthorize;
  24 +import org.springframework.validation.annotation.Validated;
  25 +import org.springframework.web.bind.annotation.GetMapping;
  26 +import org.springframework.web.bind.annotation.RequestMapping;
  27 +import org.springframework.web.bind.annotation.RequestParam;
  28 +import org.springframework.web.bind.annotation.RestController;
  29 +
  30 +import java.util.Collections;
  31 +import java.util.List;
  32 +import java.util.Map;
  33 +
  34 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  36 +
  37 +@Tag(name = "管理后台 - IM 通话记录")
  38 +@RestController
  39 +@RequestMapping("/im/manager/rtc")
  40 +@Validated
  41 +public class ImRtcCallManagerController {
  42 +
  43 + @Resource
  44 + private ImRtcCallService rtcCallService;
  45 + @Resource
  46 + private ImGroupService groupService;
  47 +
  48 + @Resource
  49 + private AdminUserApi adminUserApi;
  50 +
  51 + @GetMapping("/page")
  52 + @Operation(summary = "获得通话记录分页")
  53 + @PreAuthorize("@ss.hasPermission('im:manager:rtc:query')")
  54 + public CommonResult<PageResult<ImRtcCallManagerRespVO>> getCallPage(@Valid ImRtcCallManagerPageReqVO pageReqVO) {
  55 + PageResult<ImRtcCallDO> pageResult = rtcCallService.getCallPage(pageReqVO);
  56 + return success(buildCallRespVOPage(pageResult));
  57 + }
  58 +
  59 + @GetMapping("/get")
  60 + @Operation(summary = "获得通话记录详情")
  61 + @Parameter(name = "id", description = "通话编号", required = true, example = "1024")
  62 + @PreAuthorize("@ss.hasPermission('im:manager:rtc:query')")
  63 + public CommonResult<ImRtcCallManagerRespVO> getCall(@RequestParam("id") Long id) {
  64 + ImRtcCallDO call = rtcCallService.getCall(id);
  65 + if (call == null) {
  66 + return success(null);
  67 + }
  68 + return success(CollUtil.getFirst(buildCallRespVOList(Collections.singletonList(call))));
  69 + }
  70 +
  71 + @GetMapping("/participant-list")
  72 + @Operation(summary = "获得通话参与者列表")
  73 + @Parameter(name = "id", description = "通话编号", required = true, example = "1024")
  74 + @PreAuthorize("@ss.hasPermission('im:manager:rtc:query')")
  75 + public CommonResult<List<ImRtcParticipantManagerRespVO>> getCallParticipantList(@RequestParam("id") Long id) {
  76 + List<ImRtcParticipantDO> participants = rtcCallService.getCallParticipantListByCallId(id);
  77 + if (CollUtil.isEmpty(participants)) {
  78 + return success(Collections.emptyList());
  79 + }
  80 + // 查询用户信息
  81 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  82 + convertSet(participants, ImRtcParticipantDO::getUserId));
  83 + // 组装返回
  84 + return success(BeanUtils.toBean(participants, ImRtcParticipantManagerRespVO.class, vo ->
  85 + MapUtils.findAndThen(userMap, vo.getUserId(),
  86 + user -> vo.setUserNickname(user.getNickname()))));
  87 + }
  88 +
  89 + // ========== 私有方法:VO 组装 ==========
  90 +
  91 + private PageResult<ImRtcCallManagerRespVO> buildCallRespVOPage(PageResult<ImRtcCallDO> pageResult) {
  92 + if (CollUtil.isEmpty(pageResult.getList())) {
  93 + return PageResult.empty(pageResult.getTotal());
  94 + }
  95 + return new PageResult<>(buildCallRespVOList(pageResult.getList()), pageResult.getTotal());
  96 + }
  97 +
  98 + private List<ImRtcCallManagerRespVO> buildCallRespVOList(List<ImRtcCallDO> calls) {
  99 + // 查询用户信息
  100 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  101 + convertSet(calls, ImRtcCallDO::getInviterUserId));
  102 + Map<Long, ImGroupDO> groupMap = groupService.getGroupMap(
  103 + convertSet(calls, ImRtcCallDO::getGroupId));
  104 + // 组装返回
  105 + return BeanUtils.toBean(calls, ImRtcCallManagerRespVO.class, vo -> {
  106 + MapUtils.findAndThen(userMap, vo.getInviterUserId(),
  107 + user -> vo.setInviterNickname(user.getNickname()));
  108 + MapUtils.findAndThen(groupMap, vo.getGroupId(),
  109 + group -> vo.setGroupName(group.getName()));
  110 + });
  111 + }
  112 +
  113 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcCallManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.rtc.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 ImRtcCallManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "发起人用户编号", example = "1024")
  19 + private Long inviterUserId;
  20 +
  21 + @Schema(description = "会话类型", example = "1")
  22 + private Integer conversationType; // 参见 ImConversationTypeEnum 枚举类
  23 +
  24 + @Schema(description = "媒体类型", example = "1")
  25 + private Integer mediaType; // 参见 ImRtcCallMediaTypeEnum 枚举类
  26 +
  27 + @Schema(description = "通话状态", example = "10")
  28 + private Integer status; // 参见 ImRtcCallStatusEnum 枚举类
  29 +
  30 + @Schema(description = "结束原因", example = "1")
  31 + private Integer endReason; // 参见 ImRtcCallEndReasonEnum 枚举类
  32 +
  33 + @Schema(description = "发起时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  34 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  35 + private LocalDateTime[] startTime;
  36 +
  37 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcCallManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.rtc.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 ImRtcCallManagerRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "业务通话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "uuid-xxx")
  16 + private String room;
  17 +
  18 + @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + private Integer conversationType; // 参见 ImConversationTypeEnum 枚举类
  20 +
  21 + @Schema(description = "媒体类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  22 + private Integer mediaType; // 参见 ImRtcCallMediaTypeEnum 枚举类
  23 +
  24 + @Schema(description = "发起人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  25 + private Long inviterUserId;
  26 +
  27 + @Schema(description = "发起人昵称", example = "张三")
  28 + private String inviterNickname;
  29 +
  30 + @Schema(description = "群编号;私聊为空", example = "999")
  31 + private Long groupId;
  32 +
  33 + @Schema(description = "群名称;私聊为空", example = "测试群")
  34 + private String groupName;
  35 +
  36 + @Schema(description = "通话状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  37 + private Integer status; // 参见 ImRtcCallStatusEnum 枚举类
  38 +
  39 + @Schema(description = "结束原因", example = "1")
  40 + private Integer endReason; // 参见 ImRtcCallEndReasonEnum 枚举类
  41 +
  42 + @Schema(description = "发起时间", requiredMode = Schema.RequiredMode.REQUIRED)
  43 + private LocalDateTime startTime;
  44 +
  45 + @Schema(description = "接通时间;未接通为空")
  46 + private LocalDateTime acceptTime;
  47 +
  48 + @Schema(description = "结束时间;未结束为空")
  49 + private LocalDateTime endTime;
  50 +
  51 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  52 + private LocalDateTime createTime;
  53 +
  54 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcParticipantManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.rtc.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 ImRtcParticipantManagerRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "通话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  16 + private Long callId;
  17 +
  18 + @Schema(description = "参与者用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  19 + private Long userId;
  20 +
  21 + @Schema(description = "参与者昵称", example = "张三")
  22 + private String userNickname;
  23 +
  24 + @Schema(description = "参与角色", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + private Integer role; // 参见 ImRtcParticipantRoleEnum 枚举类
  26 +
  27 + @Schema(description = "参与状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
  28 + private Integer status; // 参见 ImRtcParticipantStatusEnum 枚举类
  29 +
  30 + @Schema(description = "被邀请时间", requiredMode = Schema.RequiredMode.REQUIRED)
  31 + private LocalDateTime inviteTime;
  32 +
  33 + @Schema(description = "接听时间;未接听为空")
  34 + private LocalDateTime acceptTime;
  35 +
  36 + @Schema(description = "离开时间;未加入为空")
  37 + private LocalDateTime leaveTime;
  38 +
  39 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO;
  10 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordRespVO;
  11 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO;
  12 +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
  13 +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
  14 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  15 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  16 +import io.swagger.v3.oas.annotations.Operation;
  17 +import io.swagger.v3.oas.annotations.Parameter;
  18 +import io.swagger.v3.oas.annotations.tags.Tag;
  19 +import jakarta.annotation.Resource;
  20 +import jakarta.validation.Valid;
  21 +import jakarta.validation.constraints.Size;
  22 +import org.springframework.security.access.prepost.PreAuthorize;
  23 +import org.springframework.validation.annotation.Validated;
  24 +import org.springframework.web.bind.annotation.*;
  25 +
  26 +import java.util.List;
  27 +import java.util.Map;
  28 +
  29 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  31 +
  32 +@Tag(name = "管理后台 - IM 敏感词")
  33 +@RestController
  34 +@RequestMapping("/im/manager/sensitive-word")
  35 +@Validated
  36 +public class ImSensitiveWordManagerController {
  37 +
  38 + @Resource
  39 + private ImSensitiveWordService sensitiveWordService;
  40 + @Resource
  41 + private AdminUserApi adminUserApi;
  42 +
  43 + @PostMapping("/create")
  44 + @Operation(summary = "新增敏感词")
  45 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:create')")
  46 + public CommonResult<Long> createSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) {
  47 + return success(sensitiveWordService.createSensitiveWord(reqVO));
  48 + }
  49 +
  50 + @PutMapping("/update")
  51 + @Operation(summary = "修改敏感词")
  52 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:update')")
  53 + public CommonResult<Boolean> updateSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) {
  54 + sensitiveWordService.updateSensitiveWord(reqVO);
  55 + return success(true);
  56 + }
  57 +
  58 + @DeleteMapping("/delete")
  59 + @Operation(summary = "删除敏感词")
  60 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  61 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')")
  62 + public CommonResult<Boolean> deleteSensitiveWord(@RequestParam("id") Long id) {
  63 + sensitiveWordService.deleteSensitiveWord(id);
  64 + return success(true);
  65 + }
  66 +
  67 + @DeleteMapping("/delete-list")
  68 + @Operation(summary = "批量删除敏感词")
  69 + @Parameter(name = "ids", description = "编号列表", required = true)
  70 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')")
  71 + public CommonResult<Boolean> deleteSensitiveWordList(
  72 + @RequestParam("ids")
  73 + @Size(max = 100, message = "批量删除最多 100 条") List<Long> ids) {
  74 + sensitiveWordService.deleteSensitiveWordList(ids);
  75 + return success(true);
  76 + }
  77 +
  78 + @GetMapping("/page")
  79 + @Operation(summary = "获得敏感词分页")
  80 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')")
  81 + public CommonResult<PageResult<ImSensitiveWordRespVO>> getSensitiveWordPage(
  82 + @Valid ImSensitiveWordPageReqVO pageReqVO) {
  83 + // 1. 分页查询
  84 + PageResult<ImSensitiveWordDO> pageResult = sensitiveWordService.getSensitiveWordPage(pageReqVO);
  85 + if (CollUtil.isEmpty(pageResult.getList())) {
  86 + return success(PageResult.empty(pageResult.getTotal()));
  87 + }
  88 + // 2.1 批量查询创建人昵称
  89 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(pageResult.getList(),
  90 + word -> NumberUtils.parseLong(word.getCreator())));
  91 + // 2.2 转换为 VO,填充创建人昵称
  92 + return success(BeanUtils.toBean(pageResult, ImSensitiveWordRespVO.class, vo ->
  93 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(vo.getCreator()),
  94 + user -> vo.setCreatorName(user.getNickname()))));
  95 + }
  96 +
  97 + @GetMapping("/get")
  98 + @Operation(summary = "获得敏感词详情")
  99 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  100 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')")
  101 + public CommonResult<ImSensitiveWordRespVO> getSensitiveWord(@RequestParam("id") Long id) {
  102 + ImSensitiveWordDO word = sensitiveWordService.getSensitiveWord(id);
  103 + return success(BeanUtils.toBean(word, ImSensitiveWordRespVO.class));
  104 + }
  105 +
  106 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import org.springframework.format.annotation.DateTimeFormat;
  10 +
  11 +import java.time.LocalDateTime;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  14 +
  15 +@Schema(description = "管理后台 - IM 敏感词分页 Request VO")
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +public class ImSensitiveWordPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "敏感词,模糊匹配", example = "敏感")
  21 + private String word;
  22 +
  23 + @Schema(description = "状态", example = "0")
  24 + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
  25 + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)
  26 +
  27 + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  28 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  29 + private LocalDateTime[] createTime;
  30 +
  31 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 敏感词 Response VO")
  9 +@Data
  10 +public class ImSensitiveWordRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "敏感词", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏感词内容")
  16 + private String word;
  17 +
  18 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  19 + private Integer status; // 参见 CommonStatusEnum 枚举类
  20 +
  21 + @Schema(description = "创建人")
  22 + private String creator;
  23 + @Schema(description = "创建人昵称", example = "张三")
  24 + private String creatorName;
  25 +
  26 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  27 + private LocalDateTime createTime;
  28 +
  29 +}
... ...