Commit ec9862cd29f140aeb764a931ab2fad35a08ee065
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.
pom.xml
| @@ -26,6 +26,7 @@ | @@ -26,6 +26,7 @@ | ||
| 26 | <!-- <module>yudao-module-iot</module>--> | 26 | <!-- <module>yudao-module-iot</module>--> |
| 27 | <!-- <module>yudao-module-mes</module>--> | 27 | <!-- <module>yudao-module-mes</module>--> |
| 28 | <!-- <module>yudao-module-wms</module>--> | 28 | <!-- <module>yudao-module-wms</module>--> |
| 29 | +<!-- <module>yudao-module-im</module>--> | ||
| 29 | <!-- 请参考 https://doc.iocoder.cn/ai/build/ 文档,完成 AI 模块的启动!!! --> | 30 | <!-- 请参考 https://doc.iocoder.cn/ai/build/ 文档,完成 AI 模块的启动!!! --> |
| 30 | <!-- <module>yudao-module-ai</module>--> | 31 | <!-- <module>yudao-module-ai</module>--> |
| 31 | </modules> | 32 | </modules> |
sql/mysql/im/alter_message_content_8192.sql
0 → 100644
yudao-dependencies/pom.xml
| @@ -50,6 +50,8 @@ | @@ -50,6 +50,8 @@ | ||
| 50 | <!-- 工具类相关 --> | 50 | <!-- 工具类相关 --> |
| 51 | <anji-plus-captcha.version>1.4.0</anji-plus-captcha.version> | 51 | <anji-plus-captcha.version>1.4.0</anji-plus-captcha.version> |
| 52 | <jsoup.version>1.22.2</jsoup.version> | 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 | <lombok.version>1.18.46</lombok.version> | 55 | <lombok.version>1.18.46</lombok.version> |
| 54 | <mapstruct.version>1.6.3</mapstruct.version> | 56 | <mapstruct.version>1.6.3</mapstruct.version> |
| 55 | <hutool-5.version>5.8.44</hutool-5.version> | 57 | <hutool-5.version>5.8.44</hutool-5.version> |
| @@ -559,6 +561,18 @@ | @@ -559,6 +561,18 @@ | ||
| 559 | <version>${jsoup.version}</version> | 561 | <version>${jsoup.version}</version> |
| 560 | </dependency> | 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 | <!-- Vert.x --> | 576 | <!-- Vert.x --> |
| 563 | <dependency> | 577 | <dependency> |
| 564 | <groupId>io.vertx</groupId> | 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,6 +124,22 @@ public class CollectionUtils { | ||
| 124 | return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet()); | 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 | public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) { | 143 | public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) { |
| 128 | if (CollUtil.isEmpty(from)) { | 144 | if (CollUtil.isEmpty(from)) { |
| 129 | return new HashMap<>(); | 145 | return new HashMap<>(); |
| @@ -372,4 +388,14 @@ public class CollectionUtils { | @@ -372,4 +388,14 @@ public class CollectionUtils { | ||
| 372 | return false; | 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 | \ No newline at end of file | 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,6 +236,23 @@ public class LocalDateTimeUtils { | ||
| 236 | return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN); | 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 | public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime, | 256 | public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime, |
| 240 | LocalDateTime endTime, | 257 | LocalDateTime endTime, |
| 241 | Integer interval) { | 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,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,6 +22,7 @@ import java.lang.reflect.Type; | ||
| 22 | import java.time.LocalDateTime; | 22 | import java.time.LocalDateTime; |
| 23 | import java.util.ArrayList; | 23 | import java.util.ArrayList; |
| 24 | import java.util.List; | 24 | import java.util.List; |
| 25 | +import java.util.Map; | ||
| 25 | 26 | ||
| 26 | /** | 27 | /** |
| 27 | * JSON 工具类 | 28 | * JSON 工具类 |
| @@ -174,6 +175,23 @@ public class JsonUtils { | @@ -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 | * 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null | 195 | * 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null |
| 178 | * | 196 | * |
| 179 | * @param text 字符串 | 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,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.string; | ||
| 3 | import cn.hutool.core.text.StrPool; | 3 | import cn.hutool.core.text.StrPool; |
| 4 | import cn.hutool.core.util.ArrayUtil; | 4 | import cn.hutool.core.util.ArrayUtil; |
| 5 | import cn.hutool.core.util.StrUtil; | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | +import cn.hutool.extra.pinyin.PinyinUtil; | ||
| 6 | import org.aspectj.lang.JoinPoint; | 7 | import org.aspectj.lang.JoinPoint; |
| 7 | 8 | ||
| 8 | import java.util.Arrays; | 9 | import java.util.Arrays; |
| @@ -79,6 +80,25 @@ public class StrUtils { | @@ -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 | * 特殊:排除一些无法序列化的参数,如 ServletRequest、ServletResponse、MultipartFile | 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,7 +192,12 @@ public class YudaoTenantAutoConfiguration { | ||
| 192 | RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, | 192 | RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, |
| 193 | BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize())); | 193 | BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize())); |
| 194 | // 创建 TenantRedisCacheManager 对象 | 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<T> extends MPJBaseMapper<T> { | @@ -170,6 +170,17 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | ||
| 170 | return CollUtil.getFirst(list); | 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 | default Long selectCount() { | 185 | default Long selectCount() { |
| 175 | return selectCount(new QueryWrapper<>()); | 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,8 +75,12 @@ public class YudaoCacheAutoConfiguration { | ||
| 75 | RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory()); | 75 | RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory()); |
| 76 | RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, | 76 | RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, |
| 77 | BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize())); | 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,7 +76,7 @@ public class JsonWebSocketMessageHandler extends TextWebSocketHandler { | ||
| 76 | Long tenantId = WebSocketFrameworkUtils.getTenantId(session); | 76 | Long tenantId = WebSocketFrameworkUtils.getTenantId(session); |
| 77 | TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj)); | 77 | TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj)); |
| 78 | } catch (Throwable ex) { | 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,9 +27,10 @@ public class LoginUserHandshakeInterceptor implements HandshakeInterceptor { | ||
| 27 | public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, | 27 | public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, |
| 28 | WebSocketHandler wsHandler, Map<String, Object> attributes) { | 28 | WebSocketHandler wsHandler, Map<String, Object> attributes) { |
| 29 | LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); | 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 | return true; | 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
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 | \ No newline at end of file | 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 | \ No newline at end of file | 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 | \ No newline at end of file | 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 | \ No newline at end of file | 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 | +} |