新建:im 模块
This commit is contained in:
parent
dda0909843
commit
bb0b7056cf
5
pom.xml
5
pom.xml
|
|
@ -15,7 +15,9 @@
|
|||
<!-- 各种 module 拓展 -->
|
||||
<module>yudao-module-system</module>
|
||||
<module>yudao-module-infra</module>
|
||||
<!-- <module>yudao-module-member</module>-->
|
||||
<module>yudao-module-im</module>
|
||||
<module>yudao-module-im/yudao-module-im-api</module>
|
||||
<!-- <module>yudao-module-member</module>-->
|
||||
<!-- <module>yudao-module-bpm</module>-->
|
||||
<!-- <module>yudao-module-report</module>-->
|
||||
<!-- <module>yudao-module-mp</module>-->
|
||||
|
|
@ -23,6 +25,7 @@
|
|||
<!-- <module>yudao-module-mall</module>-->
|
||||
<!-- <module>yudao-module-crm</module>-->
|
||||
<!-- <module>yudao-module-erp</module>-->
|
||||
<module>yudao-module-im</module>
|
||||
<!-- 示例项目 -->
|
||||
<!-- <module>yudao-example</module>-->
|
||||
</modules>
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public class JsonWebSocketMessageHandler extends TextWebSocketHandler {
|
|||
Long tenantId = WebSocketFrameworkUtils.getTenantId(session);
|
||||
TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));
|
||||
} catch (Throwable ex) {
|
||||
log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload());
|
||||
log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modules>
|
||||
<module>yudao-module-im-api</module>
|
||||
<module>yudao-module-im-biz</module>
|
||||
</modules>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-module-im</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
im 模块,主要提供能力:
|
||||
1. 通讯能力,例如:消息发送、消息接收、消息撤回、消息已读等。
|
||||
2. 通讯会话,例如:单聊、群聊、聊天室等。
|
||||
3. 通讯消息,例如:文本、图片、语音、视频、文件等。
|
||||
4. 通讯消息的存储,例如:消息存储、消息索引、消息搜索等。
|
||||
5. 通讯消息的推送,例如:消息推送、消息通知等。
|
||||
6. 通讯消息的安全,例如:消息加密、消息签名等。
|
||||
</description>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-im</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-module-im-api</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
im 模块 API,暴露给其它模块调用
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* @author anhaohao
|
||||
* @date 2024/3/9 下午8:59
|
||||
*/
|
||||
package cn.iocoder.yudao.module.im.api;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package cn.iocoder.yudao.module.im.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* IM 错误码枚举类
|
||||
* <p>
|
||||
* im 系统,使用 1-040-000-000 段
|
||||
*/
|
||||
public interface ErrorCodeConstants {
|
||||
|
||||
// ========== 会话 (1-040-100-000) ==========
|
||||
ErrorCode CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_100_000, "会话不存在");
|
||||
|
||||
// ========== 收件箱 (1-040-200-000) ==========
|
||||
ErrorCode INBOX_NOT_EXISTS = new ErrorCode(1_040_200_000, "收件箱不存在");
|
||||
|
||||
// ========== 消息 (1-040-300-000) ==========
|
||||
ErrorCode MESSAGE_NOT_EXISTS = new ErrorCode(1_040_300_000, "消息不存在");
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package cn.iocoder.yudao.module.im.enums.conversation;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* IM 会话的类型枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ImConversationTypeEnum {
|
||||
|
||||
PRIVATE(1, "单聊"),
|
||||
GROUP(2, "群聊");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package cn.iocoder.yudao.module.im.enums.message;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* IM 消息的类型枚举
|
||||
*
|
||||
* 参考 <a href="https://doc.yunxin.163.com/messaging/docs/zg3NzA3NTA?platform=web#消息类型">“消息类型”</a> 文档
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ImMessageTypeEnum {
|
||||
|
||||
TEXT(1, "文本"), // 消息内容为普通文本
|
||||
IMAGE(2, "图片"), // 消息内容为图片 URL 地址、尺寸、图片大小等信息
|
||||
AUDIO(3, "语音"), // 消息内容为语音文件的 URL 地址、时长、大小、格式等信息
|
||||
VIDEO(4, "视频"), // 消息内容为视频文件的 URL 地址、时长、大小、格式等信息
|
||||
FILE(5, "文件"), // 消息内容为文件的 URL 地址、大小、格式等信息
|
||||
LOCATION(6, "地理位置"), // 消息内容为地理位置标题、经度、纬度信息
|
||||
// TODO @芋艿:下面两种,貌似企业微信设计的更好:https://developer.work.weixin.qq.com/document/path/90240
|
||||
TIP(7, "提示"), // 又叫做 Tip 消息,没有推送和通知栏提醒,主要用于会话内的通知提醒,例如进入会话时出现的欢迎消息,或是会话过程中命中敏感词后的提示消息等场景
|
||||
NOTIFICATION(8, "通知"), // 主要用于群组、聊天室和超大群的事件通知,由服务端下发,客户端无法发送事件通知消息。通知类消息有在线、离线、漫游机制;没有通知栏提醒
|
||||
;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* @author anhaohao
|
||||
* @date 2024/3/9 下午8:59
|
||||
*/
|
||||
package cn.iocoder.yudao.module.im.enums;
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>yudao-module-im</artifactId>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<version>${revision}</version> <!-- 1. 修改 version 为 ${revision} -->
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging> <!-- 2. 新增 packaging 为 jar -->
|
||||
|
||||
<artifactId>yudao-module-im-biz</artifactId>
|
||||
|
||||
<name>${project.artifactId}</name> <!-- 3. 新增 name 为 ${project.artifactId} -->
|
||||
<description> <!-- 4. 新增 description 为该模块的描述 -->
|
||||
im 模块,主要实现 im 模块的业务逻辑。
|
||||
</description>
|
||||
|
||||
<dependencies> <!-- 5. 新增依赖,这里引入的都是比较常用的业务组件、技术组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-im-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.conversation;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ConversationPageReqVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ConversationRespVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ConversationSaveReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ConversationDO;
|
||||
import cn.iocoder.yudao.module.im.service.conversation.ConversationService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
|
||||
|
||||
@Tag(name = "管理后台 - 会话")
|
||||
@RestController
|
||||
@RequestMapping("/im/conversation")
|
||||
@Validated
|
||||
public class ConversationController {
|
||||
|
||||
@Resource
|
||||
private ConversationService conversationService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建会话")
|
||||
@PreAuthorize("@ss.hasPermission('im:conversation:create')")
|
||||
public CommonResult<Long> createConversation(@Valid @RequestBody ConversationSaveReqVO createReqVO) {
|
||||
return success(conversationService.createConversation(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新会话")
|
||||
@PreAuthorize("@ss.hasPermission('im:conversation:update')")
|
||||
public CommonResult<Boolean> updateConversation(@Valid @RequestBody ConversationSaveReqVO updateReqVO) {
|
||||
conversationService.updateConversation(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除会话")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('im:conversation:delete')")
|
||||
public CommonResult<Boolean> deleteConversation(@RequestParam("id") Long id) {
|
||||
conversationService.deleteConversation(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得会话")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('im:conversation:query')")
|
||||
public CommonResult<ConversationRespVO> getConversation(@RequestParam("id") Long id) {
|
||||
ConversationDO conversation = conversationService.getConversation(id);
|
||||
return success(BeanUtils.toBean(conversation, ConversationRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得会话分页")
|
||||
@PreAuthorize("@ss.hasPermission('im:conversation:query')")
|
||||
public CommonResult<PageResult<ConversationRespVO>> getConversationPage(@Valid ConversationPageReqVO pageReqVO) {
|
||||
PageResult<ConversationDO> pageResult = conversationService.getConversationPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, ConversationRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出会话 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('im:conversation:export')")
|
||||
@OperateLog(type = EXPORT)
|
||||
public void exportConversationExcel(@Valid ConversationPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<ConversationDO> list = conversationService.getConversationPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "会话.xls", "数据", ConversationRespVO.class,
|
||||
BeanUtils.toBean(list, ConversationRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.conversation.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 会话分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ConversationPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "所属用户", example = "11545")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "类型:1 单聊;2 群聊;4 通知会话(预留)", example = "1")
|
||||
private Boolean conversationType;
|
||||
|
||||
@Schema(description = "单聊时,用户编号;群聊时,群编号", example = "21454")
|
||||
private String targetId;
|
||||
|
||||
@Schema(description = "会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId")
|
||||
private String no;
|
||||
|
||||
@Schema(description = "是否置顶 0否 1是")
|
||||
private Boolean pinned;
|
||||
|
||||
@Schema(description = "最后已读时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] lastReadTime;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.conversation.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 会话 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class ConversationRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13905")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "11545")
|
||||
@ExcelProperty("所属用户")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "类型:1 单聊;2 群聊;4 通知会话(预留)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty("类型:1 单聊;2 群聊;4 通知会话(预留)")
|
||||
private Boolean conversationType;
|
||||
|
||||
@Schema(description = "单聊时,用户编号;群聊时,群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21454")
|
||||
@ExcelProperty("单聊时,用户编号;群聊时,群编号")
|
||||
private String targetId;
|
||||
|
||||
@Schema(description = "会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId")
|
||||
private String no;
|
||||
|
||||
@Schema(description = "是否置顶 0否 1是", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("是否置顶 0否 1是")
|
||||
private Boolean pinned;
|
||||
|
||||
@Schema(description = "最后已读时间")
|
||||
@ExcelProperty("最后已读时间")
|
||||
private LocalDateTime lastReadTime;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.conversation.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 会话新增/修改 Request VO")
|
||||
@Data
|
||||
public class ConversationSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13905")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "11545")
|
||||
@NotNull(message = "所属用户不能为空")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "类型:1 单聊;2 群聊;4 通知会话(预留)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "类型:1 单聊;2 群聊;4 通知会话(预留)不能为空")
|
||||
private Boolean conversationType;
|
||||
|
||||
@Schema(description = "单聊时,用户编号;群聊时,群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21454")
|
||||
@NotEmpty(message = "单聊时,用户编号;群聊时,群编号不能为空")
|
||||
private String targetId;
|
||||
|
||||
@Schema(description = "会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId不能为空")
|
||||
private String no;
|
||||
|
||||
@Schema(description = "是否置顶 0否 1是", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "是否置顶 0否 1是不能为空")
|
||||
private Boolean pinned;
|
||||
|
||||
@Schema(description = "最后已读时间")
|
||||
private LocalDateTime lastReadTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.inbox;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxPageReqVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxRespVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSaveReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
|
||||
import cn.iocoder.yudao.module.im.service.inbox.InboxService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
|
||||
|
||||
@Tag(name = "管理后台 - 收件箱")
|
||||
@RestController
|
||||
@RequestMapping("/im/inbox")
|
||||
@Validated
|
||||
public class InboxController {
|
||||
|
||||
@Resource
|
||||
private InboxService inboxService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建收件箱")
|
||||
@PreAuthorize("@ss.hasPermission('im:inbox:create')")
|
||||
public CommonResult<Long> createInbox(@Valid @RequestBody InboxSaveReqVO createReqVO) {
|
||||
return success(inboxService.createInbox(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新收件箱")
|
||||
@PreAuthorize("@ss.hasPermission('im:inbox:update')")
|
||||
public CommonResult<Boolean> updateInbox(@Valid @RequestBody InboxSaveReqVO updateReqVO) {
|
||||
inboxService.updateInbox(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除收件箱")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('im:inbox:delete')")
|
||||
public CommonResult<Boolean> deleteInbox(@RequestParam("id") Long id) {
|
||||
inboxService.deleteInbox(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得收件箱")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('im:inbox:query')")
|
||||
public CommonResult<InboxRespVO> getInbox(@RequestParam("id") Long id) {
|
||||
InboxDO inbox = inboxService.getInbox(id);
|
||||
return success(BeanUtils.toBean(inbox, InboxRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得收件箱分页")
|
||||
@PreAuthorize("@ss.hasPermission('im:inbox:query')")
|
||||
public CommonResult<PageResult<InboxRespVO>> getInboxPage(@Valid InboxPageReqVO pageReqVO) {
|
||||
PageResult<InboxDO> pageResult = inboxService.getInboxPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, InboxRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出收件箱 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('im:inbox:export')")
|
||||
@OperateLog(type = EXPORT)
|
||||
public void exportInboxExcel(@Valid InboxPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<InboxDO> list = inboxService.getInboxPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "收件箱.xls", "数据", InboxRespVO.class,
|
||||
BeanUtils.toBean(list, InboxRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.inbox.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 收件箱分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class InboxPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "用户编号", example = "3979")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "消息编号", example = "12454")
|
||||
private Long messageId;
|
||||
|
||||
@Schema(description = "序号,按照 user 递增")
|
||||
private Long sequence;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.inbox.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 收件箱 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class InboxRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18389")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3979")
|
||||
@ExcelProperty("用户编号")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12454")
|
||||
@ExcelProperty("消息编号")
|
||||
private Long messageId;
|
||||
|
||||
@Schema(description = "序号,按照 user 递增", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("序号,按照 user 递增")
|
||||
private Long sequence;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.inbox.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 收件箱新增/修改 Request VO")
|
||||
@Data
|
||||
public class InboxSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18389")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3979")
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12454")
|
||||
@NotNull(message = "消息编号不能为空")
|
||||
private Long messageId;
|
||||
|
||||
@Schema(description = "序号,按照 user 递增", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "序号,按照 user 递增不能为空")
|
||||
private Long sequence;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.message;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessagePageReqVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessageRespVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessageSaveReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
|
||||
import cn.iocoder.yudao.module.im.service.message.MessageService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
|
||||
|
||||
@Tag(name = "管理后台 - 消息")
|
||||
@RestController
|
||||
@RequestMapping("/im/message")
|
||||
@Validated
|
||||
public class MessageController {
|
||||
|
||||
@Resource
|
||||
private MessageService messageService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建消息")
|
||||
@PreAuthorize("@ss.hasPermission('im:message:create')")
|
||||
public CommonResult<Long> createMessage(@Valid @RequestBody MessageSaveReqVO createReqVO) {
|
||||
return success(messageService.createMessage(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新消息")
|
||||
@PreAuthorize("@ss.hasPermission('im:message:update')")
|
||||
public CommonResult<Boolean> updateMessage(@Valid @RequestBody MessageSaveReqVO updateReqVO) {
|
||||
messageService.updateMessage(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除消息")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('im:message:delete')")
|
||||
public CommonResult<Boolean> deleteMessage(@RequestParam("id") Long id) {
|
||||
messageService.deleteMessage(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得消息")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('im:message:query')")
|
||||
public CommonResult<MessageRespVO> getMessage(@RequestParam("id") Long id) {
|
||||
MessageDO message = messageService.getMessage(id);
|
||||
return success(BeanUtils.toBean(message, MessageRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得消息分页")
|
||||
@PreAuthorize("@ss.hasPermission('im:message:query')")
|
||||
public CommonResult<PageResult<MessageRespVO>> getMessagePage(@Valid MessagePageReqVO pageReqVO) {
|
||||
PageResult<MessageDO> pageResult = messageService.getMessagePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, MessageRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出消息 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('im:message:export')")
|
||||
@OperateLog(type = EXPORT)
|
||||
public void exportMessageExcel(@Valid MessagePageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<MessageDO> list = messageService.getMessagePage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "消息.xls", "数据", MessageRespVO.class,
|
||||
BeanUtils.toBean(list, MessageRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/send")
|
||||
@Operation(summary = "发送私聊消息")
|
||||
public CommonResult<Long> sendMessage(@Valid @RequestBody MessageSaveReqVO messageSaveReqVO) {
|
||||
return success(messageService.sendPrivateMessage(messageSaveReqVO));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.message.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 消息分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class MessagePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "客户端消息编号 uuid,用于排重", example = "3331")
|
||||
private String clientMessageId;
|
||||
|
||||
@Schema(description = "发送人编号", example = "23239")
|
||||
private Long senderId;
|
||||
|
||||
@Schema(description = "接收人编号", example = "32494")
|
||||
private Long receiverId;
|
||||
|
||||
@Schema(description = "发送人昵称", example = "李四")
|
||||
private String senderNickname;
|
||||
|
||||
@Schema(description = "发送人头像")
|
||||
private String senderAvatar;
|
||||
|
||||
@Schema(description = "会话类型", example = "2")
|
||||
private Boolean conversationType;
|
||||
|
||||
@Schema(description = "会话标志")
|
||||
private String conversationNo;
|
||||
|
||||
@Schema(description = "消息类型", example = "1")
|
||||
private Boolean contentType;
|
||||
|
||||
@Schema(description = "消息内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "发送时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] sendTime;
|
||||
|
||||
@Schema(description = "消息来源 100-用户发送;200-系统发送(一般是通知);")
|
||||
private Boolean sendFrom;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.message.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 消息 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class MessageRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30713")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "客户端消息编号 uuid,用于排重", requiredMode = Schema.RequiredMode.REQUIRED, example = "3331")
|
||||
@ExcelProperty("客户端消息编号 uuid,用于排重")
|
||||
private String clientMessageId;
|
||||
|
||||
@Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23239")
|
||||
@ExcelProperty("发送人编号")
|
||||
private Long senderId;
|
||||
|
||||
@Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
|
||||
@ExcelProperty("接收人编号")
|
||||
private Long receiverId;
|
||||
|
||||
@Schema(description = "发送人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
@ExcelProperty("发送人昵称")
|
||||
private String senderNickname;
|
||||
|
||||
@Schema(description = "发送人头像", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("发送人头像")
|
||||
private String senderAvatar;
|
||||
|
||||
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
@ExcelProperty("会话类型")
|
||||
private Boolean conversationType;
|
||||
|
||||
@Schema(description = "会话标志", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("会话标志")
|
||||
private String conversationNo;
|
||||
|
||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty("消息类型")
|
||||
private Boolean contentType;
|
||||
|
||||
@Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("消息内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("发送时间")
|
||||
private LocalDateTime sendTime;
|
||||
|
||||
@Schema(description = "消息来源 100-用户发送;200-系统发送(一般是通知);", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("消息来源 100-用户发送;200-系统发送(一般是通知);")
|
||||
private Boolean sendFrom;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package cn.iocoder.yudao.module.im.controller.admin.message.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 消息新增/修改 Request VO")
|
||||
@Data
|
||||
public class MessageSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30713")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "客户端消息编号 uuid,用于排重", requiredMode = Schema.RequiredMode.REQUIRED, example = "3331")
|
||||
@NotEmpty(message = "客户端消息编号 uuid,用于排重不能为空")
|
||||
private String clientMessageId;
|
||||
|
||||
@Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23239")
|
||||
@NotNull(message = "发送人编号不能为空")
|
||||
private Long senderId;
|
||||
|
||||
@Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
|
||||
@NotNull(message = "接收人编号不能为空")
|
||||
private Long receiverId;
|
||||
|
||||
@Schema(description = "发送人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
@NotEmpty(message = "发送人昵称不能为空")
|
||||
private String senderNickname;
|
||||
|
||||
@Schema(description = "发送人头像", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "发送人头像不能为空")
|
||||
private String senderAvatar;
|
||||
|
||||
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
@NotNull(message = "会话类型不能为空")
|
||||
private Boolean conversationType;
|
||||
|
||||
@Schema(description = "会话标志", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "会话标志不能为空")
|
||||
private String conversationNo;
|
||||
|
||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "消息类型不能为空")
|
||||
private Boolean contentType;
|
||||
|
||||
@Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "消息内容不能为空")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "发送时间不能为空")
|
||||
private LocalDateTime sendTime;
|
||||
|
||||
@Schema(description = "消息来源 100-用户发送;200-系统发送(一般是通知);", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "消息来源 100-用户发送;200-系统发送(一般是通知);不能为空")
|
||||
private Boolean sendFrom;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.conversation;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 会话 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("im_conversation")
|
||||
@KeySequence("im_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ConversationDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 所属用户
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 类型:1 单聊;2 群聊;4 通知会话(预留)
|
||||
*/
|
||||
private Boolean conversationType;
|
||||
/**
|
||||
* 单聊时,用户编号;群聊时,群编号
|
||||
*/
|
||||
private String targetId;
|
||||
/**
|
||||
* 会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId
|
||||
*/
|
||||
private String no;
|
||||
/**
|
||||
* 是否置顶 0否 1是
|
||||
*/
|
||||
private Boolean pinned;
|
||||
/**
|
||||
* 最后已读时间
|
||||
*/
|
||||
private LocalDateTime lastReadTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.inbox;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 收件箱 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("im_inbox")
|
||||
@KeySequence("im_inbox_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InboxDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 消息编号
|
||||
*/
|
||||
private Long messageId;
|
||||
/**
|
||||
* 序号,按照 user 递增
|
||||
*/
|
||||
private Long sequence;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.message;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 消息 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("im_message")
|
||||
@KeySequence("im_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MessageDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 客户端消息编号 uuid,用于排重
|
||||
*/
|
||||
private String clientMessageId;
|
||||
/**
|
||||
* 发送人编号
|
||||
*/
|
||||
private Long senderId;
|
||||
/**
|
||||
* 接收人编号
|
||||
*/
|
||||
private Long receiverId;
|
||||
/**
|
||||
* 发送人昵称
|
||||
*/
|
||||
private String senderNickname;
|
||||
/**
|
||||
* 发送人头像
|
||||
*/
|
||||
private String senderAvatar;
|
||||
/**
|
||||
* 会话类型
|
||||
*/
|
||||
private Boolean conversationType;
|
||||
/**
|
||||
* 会话标志
|
||||
*/
|
||||
private String conversationNo;
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private Boolean contentType;
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
/**
|
||||
* 发送时间
|
||||
*/
|
||||
private LocalDateTime sendTime;
|
||||
/**
|
||||
* 消息来源 100-用户发送;200-系统发送(一般是通知);
|
||||
*/
|
||||
private Boolean sendFrom;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 语音消息的 {@link ImMessageBody}
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ImAudioMessageBody implements ImMessageBody {
|
||||
|
||||
/**
|
||||
* 语音 URL
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 语音格式,例如说 arm、mp3、speex 等
|
||||
*/
|
||||
private String format;
|
||||
|
||||
// TODO 芋艿:要不要以下字段?待定;云信有、企业微信没有
|
||||
//"dur":4551, //语音持续时长ms
|
||||
// "md5":"87b94a090dec5c58f242b7132a530a01", //语音文件的md5值,按照字节流加密
|
||||
// "size":16420 //语音文件大小,单位为字节(Byte)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 文件消息的 {@link ImMessageBody}
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ImFileMessageBody implements ImMessageBody {
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 文件 URL
|
||||
*/
|
||||
private String url;
|
||||
|
||||
// TODO 芋艿:要不要以下字段?待定;云信有、企业微信没有
|
||||
// "md5":"79d62a35fa3d34c367b20c66afc2a500", //文件MD5,按照字节流加密
|
||||
// "ext":"ttf", //文件后缀类型
|
||||
// "size":91680 //大小,单位为字节(Byte)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 图片消息的 {@link ImMessageBody}
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ImImageMessageBody implements ImMessageBody {
|
||||
|
||||
/**
|
||||
* 图片地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
// TODO 芋艿:要不要以下字段?待定;云信有、企业微信没有
|
||||
// "name":"图片发送于2015-05-07 13:59", //图片name
|
||||
// "md5":"9894907e4ad9de4678091277509361f7", //图片文件md5,按照字节流加密
|
||||
// "ext":"jpg", //图片后缀
|
||||
// "w":6814, //宽,单位为像素
|
||||
// "h":2332, //高,单位为像素
|
||||
// "size":388245 //图片文件大小,单位为字节(Byte)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 地址位置消息的 {@link ImMessageBody}
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ImLocationMessageBody implements ImMessageBody {
|
||||
|
||||
/**
|
||||
* 地理位置
|
||||
*
|
||||
* 例如说:中国 浙江省 杭州市 网商路 599号
|
||||
*/
|
||||
private String address;
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
private Double longitude;
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
private Double latitude;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.module.im.jackson.ImMessageBodyDeserializer;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
/**
|
||||
* IM 消息的 body 内容
|
||||
*/
|
||||
@JsonDeserialize(using = ImMessageBodyDeserializer.class)
|
||||
public interface ImMessageBody {
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 文本消息的 {@link ImMessageBody}
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ImTextMessageBody implements ImMessageBody {
|
||||
|
||||
/**
|
||||
* 文本消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package cn.iocoder.yudao.module.im.dal.dataobject.message.body;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 视频消息的 {@link ImMessageBody}
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class ImVideoMessageBody implements ImMessageBody {
|
||||
|
||||
/**
|
||||
* 视频地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
// TODO 芋艿:要不要以下字段?待定;云信有、企业微信没有
|
||||
// "dur":8003, //视频持续时长ms
|
||||
// "md5":"da2cef3e5663ee9c3547ef5d127f7e3e", //视频文件的md5值,按照字节流加密
|
||||
// "w":360, //宽,单位为像素
|
||||
// "h":480, //高,单位为像素
|
||||
// "size":16420 //视频文件大小,单位为字节(Byte)】
|
||||
// "ext":"mp4", //视频格式
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package cn.iocoder.yudao.module.im.dal.mysql.conversation;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ConversationPageReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ConversationDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 会话 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConversationMapper extends BaseMapperX<ConversationDO> {
|
||||
|
||||
default PageResult<ConversationDO> selectPage(ConversationPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<ConversationDO>()
|
||||
.eqIfPresent(ConversationDO::getUserId, reqVO.getUserId())
|
||||
.eqIfPresent(ConversationDO::getConversationType, reqVO.getConversationType())
|
||||
.eqIfPresent(ConversationDO::getTargetId, reqVO.getTargetId())
|
||||
.eqIfPresent(ConversationDO::getNo, reqVO.getNo())
|
||||
.eqIfPresent(ConversationDO::getPinned, reqVO.getPinned())
|
||||
.betweenIfPresent(ConversationDO::getLastReadTime, reqVO.getLastReadTime())
|
||||
.betweenIfPresent(ConversationDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(ConversationDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package cn.iocoder.yudao.module.im.dal.mysql.inbox;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxPageReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 收件箱 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface InboxMapper extends BaseMapperX<InboxDO> {
|
||||
|
||||
default PageResult<InboxDO> selectPage(InboxPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<InboxDO>()
|
||||
.eqIfPresent(InboxDO::getUserId, reqVO.getUserId())
|
||||
.eqIfPresent(InboxDO::getMessageId, reqVO.getMessageId())
|
||||
.eqIfPresent(InboxDO::getSequence, reqVO.getSequence())
|
||||
.betweenIfPresent(InboxDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(InboxDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package cn.iocoder.yudao.module.im.dal.mysql.message;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessagePageReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 消息 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface MessageMapper extends BaseMapperX<MessageDO> {
|
||||
|
||||
default PageResult<MessageDO> selectPage(MessagePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<MessageDO>()
|
||||
.eqIfPresent(MessageDO::getClientMessageId, reqVO.getClientMessageId())
|
||||
.eqIfPresent(MessageDO::getSenderId, reqVO.getSenderId())
|
||||
.eqIfPresent(MessageDO::getReceiverId, reqVO.getReceiverId())
|
||||
.likeIfPresent(MessageDO::getSenderNickname, reqVO.getSenderNickname())
|
||||
.eqIfPresent(MessageDO::getSenderAvatar, reqVO.getSenderAvatar())
|
||||
.eqIfPresent(MessageDO::getConversationType, reqVO.getConversationType())
|
||||
.eqIfPresent(MessageDO::getConversationNo, reqVO.getConversationNo())
|
||||
.eqIfPresent(MessageDO::getContentType, reqVO.getContentType())
|
||||
.eqIfPresent(MessageDO::getContent, reqVO.getContent())
|
||||
.betweenIfPresent(MessageDO::getSendTime, reqVO.getSendTime())
|
||||
.eqIfPresent(MessageDO::getSendFrom, reqVO.getSendFrom())
|
||||
.betweenIfPresent(MessageDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(MessageDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* 属于 erp 模块的 framework 封装
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
package cn.iocoder.yudao.module.im.framework;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package cn.iocoder.yudao.module.im.framework.web.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* im 模块的 web 组件的 Configuration
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class ImWebConfiguration {
|
||||
|
||||
/**
|
||||
* im 模块的 API 分组
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi imGroupedOpenApi() {
|
||||
return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("im");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* trade 模块的 web 配置
|
||||
*/
|
||||
package cn.iocoder.yudao.module.im.framework.web;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package cn.iocoder.yudao.module.im.jackson;
|
||||
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.message.body.*;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ImMessageBodyDeserializer extends JsonDeserializer<ImMessageBody> {
|
||||
|
||||
@Override
|
||||
public ImMessageBody deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
JsonNode node = p.getCodec().readTree(p);
|
||||
// 根据 node 中的内容来判断应该实例化哪个子类
|
||||
if (node.has("content")) {
|
||||
return new ImTextMessageBody(node.get("content").asText());
|
||||
}
|
||||
if (node.has("url")) {
|
||||
String url = node.get("url").asText();
|
||||
if (node.has("format")) {
|
||||
return new ImAudioMessageBody(url, node.get("format").asText());
|
||||
}
|
||||
return new ImImageMessageBody(url);
|
||||
}
|
||||
if (node.has("name")) {
|
||||
return new ImFileMessageBody(node.get("name").asText(), node.get("url").asText());
|
||||
}
|
||||
if (node.has("address")) {
|
||||
return new ImLocationMessageBody(node.get("address").asText(), node.get("longitude").asDouble(), node.get("latitude").asDouble());
|
||||
}
|
||||
// 如果没有匹配的属性,抛出异常
|
||||
throw ctxt.mappingException("Cannot deserialize to an instance of ImMessageBody");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package cn.iocoder.yudao.module.im.service.conversation;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ConversationPageReqVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ConversationSaveReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ConversationDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 会话 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface ConversationService {
|
||||
|
||||
/**
|
||||
* 创建会话
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createConversation(@Valid ConversationSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新会话
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateConversation(@Valid ConversationSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteConversation(Long id);
|
||||
|
||||
/**
|
||||
* 获得会话
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 会话
|
||||
*/
|
||||
ConversationDO getConversation(Long id);
|
||||
|
||||
/**
|
||||
* 获得会话分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 会话分页
|
||||
*/
|
||||
PageResult<ConversationDO> getConversationPage(ConversationPageReqVO pageReqVO);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package cn.iocoder.yudao.module.im.service.conversation;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ConversationPageReqVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ConversationSaveReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ConversationDO;
|
||||
import cn.iocoder.yudao.module.im.dal.mysql.conversation.ConversationMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.CONVERSATION_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 会话 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class ConversationServiceImpl implements ConversationService {
|
||||
|
||||
@Resource
|
||||
private ConversationMapper conversationMapper;
|
||||
|
||||
@Override
|
||||
public Long createConversation(ConversationSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
ConversationDO conversation = BeanUtils.toBean(createReqVO, ConversationDO.class);
|
||||
conversationMapper.insert(conversation);
|
||||
// 返回
|
||||
return conversation.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateConversation(ConversationSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateConversationExists(updateReqVO.getId());
|
||||
// 更新
|
||||
ConversationDO updateObj = BeanUtils.toBean(updateReqVO, ConversationDO.class);
|
||||
conversationMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteConversation(Long id) {
|
||||
// 校验存在
|
||||
validateConversationExists(id);
|
||||
// 删除
|
||||
conversationMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateConversationExists(Long id) {
|
||||
if (conversationMapper.selectById(id) == null) {
|
||||
throw exception(CONVERSATION_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConversationDO getConversation(Long id) {
|
||||
return conversationMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ConversationDO> getConversationPage(ConversationPageReqVO pageReqVO) {
|
||||
return conversationMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package cn.iocoder.yudao.module.im.service.inbox;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxPageReqVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSaveReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 收件箱 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface InboxService {
|
||||
|
||||
/**
|
||||
* 创建收件箱
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createInbox(@Valid InboxSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新收件箱
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateInbox(@Valid InboxSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除收件箱
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteInbox(Long id);
|
||||
|
||||
/**
|
||||
* 获得收件箱
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 收件箱
|
||||
*/
|
||||
InboxDO getInbox(Long id);
|
||||
|
||||
/**
|
||||
* 获得收件箱分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 收件箱分页
|
||||
*/
|
||||
PageResult<InboxDO> getInboxPage(InboxPageReqVO pageReqVO);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package cn.iocoder.yudao.module.im.service.inbox;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxPageReqVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.InboxSaveReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
|
||||
import cn.iocoder.yudao.module.im.dal.mysql.inbox.InboxMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.INBOX_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 收件箱 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class InboxServiceImpl implements InboxService {
|
||||
|
||||
@Resource
|
||||
private InboxMapper inboxMapper;
|
||||
|
||||
@Override
|
||||
public Long createInbox(InboxSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
InboxDO inbox = BeanUtils.toBean(createReqVO, InboxDO.class);
|
||||
inboxMapper.insert(inbox);
|
||||
// 返回
|
||||
return inbox.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateInbox(InboxSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateInboxExists(updateReqVO.getId());
|
||||
// 更新
|
||||
InboxDO updateObj = BeanUtils.toBean(updateReqVO, InboxDO.class);
|
||||
inboxMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteInbox(Long id) {
|
||||
// 校验存在
|
||||
validateInboxExists(id);
|
||||
// 删除
|
||||
inboxMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateInboxExists(Long id) {
|
||||
if (inboxMapper.selectById(id) == null) {
|
||||
throw exception(INBOX_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InboxDO getInbox(Long id) {
|
||||
return inboxMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<InboxDO> getInboxPage(InboxPageReqVO pageReqVO) {
|
||||
return inboxMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package cn.iocoder.yudao.module.im.service.message;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessagePageReqVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessageSaveReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 消息 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface MessageService {
|
||||
|
||||
/**
|
||||
* 创建消息
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createMessage(@Valid MessageSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新消息
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateMessage(@Valid MessageSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteMessage(Long id);
|
||||
|
||||
/**
|
||||
* 获得消息
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 消息
|
||||
*/
|
||||
MessageDO getMessage(Long id);
|
||||
|
||||
/**
|
||||
* 获得消息分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 消息分页
|
||||
*/
|
||||
PageResult<MessageDO> getMessagePage(MessagePageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 发送私聊消息
|
||||
* @param messageSaveReqVO 消息信息
|
||||
* @return 消息编号
|
||||
*/
|
||||
Long sendPrivateMessage(MessageSaveReqVO messageSaveReqVO);
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package cn.iocoder.yudao.module.im.service.message;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessagePageReqVO;
|
||||
import cn.iocoder.yudao.module.im.controller.admin.message.vo.MessageSaveReqVO;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
|
||||
import cn.iocoder.yudao.module.im.dal.mysql.message.MessageMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 消息 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class MessageServiceImpl implements MessageService {
|
||||
|
||||
@Resource
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
@Override
|
||||
public Long createMessage(MessageSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
MessageDO message = BeanUtils.toBean(createReqVO, MessageDO.class);
|
||||
messageMapper.insert(message);
|
||||
// 返回
|
||||
return message.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessage(MessageSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateMessageExists(updateReqVO.getId());
|
||||
// 更新
|
||||
MessageDO updateObj = BeanUtils.toBean(updateReqVO, MessageDO.class);
|
||||
messageMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMessage(Long id) {
|
||||
// 校验存在
|
||||
validateMessageExists(id);
|
||||
// 删除
|
||||
messageMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateMessageExists(Long id) {
|
||||
if (messageMapper.selectById(id) == null) {
|
||||
throw exception(MESSAGE_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageDO getMessage(Long id) {
|
||||
return messageMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<MessageDO> getMessagePage(MessagePageReqVO pageReqVO) {
|
||||
return messageMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long sendPrivateMessage(MessageSaveReqVO messageSaveReqVO) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package cn.iocoder.yudao.module.im.websocket;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener;
|
||||
import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
|
||||
import cn.iocoder.yudao.framework.websocket.core.util.WebSocketFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||
import cn.iocoder.yudao.module.im.websocket.message.ImReceiveMessage;
|
||||
import cn.iocoder.yudao.module.im.websocket.message.ImSendMessage;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
/**
|
||||
* WebSocket 示例:单发消息
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
public class ImWebSocketMessageListener implements WebSocketMessageListener<ImSendMessage> {
|
||||
|
||||
@Resource
|
||||
private WebSocketMessageSender webSocketMessageSender;
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocketSession session, ImSendMessage message) {
|
||||
Long fromUserId = WebSocketFrameworkUtils.getLoginUserId(session);
|
||||
// 私聊
|
||||
if (message.getConversationType().equals(ImConversationTypeEnum.PRIVATE.getType())) {
|
||||
ImReceiveMessage toMessage = new ImReceiveMessage();
|
||||
toMessage.setToId(fromUserId);
|
||||
toMessage.setConversationType(ImConversationTypeEnum.PRIVATE.getType());
|
||||
//消息类型
|
||||
toMessage.setType(message.getType());
|
||||
toMessage.setBody(message.getBody());
|
||||
webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getToId(), // 给指定用户
|
||||
"im-message-receive", toMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "im-message-send";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package cn.iocoder.yudao.module.im.websocket.message;
|
||||
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.message.body.ImMessageBody;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 消息发送 send")
|
||||
@Data
|
||||
public class ImReceiveMessage {
|
||||
|
||||
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer conversationType; // 对应 ImConversationTypeEnum 枚举
|
||||
|
||||
@Schema(description = "聊天对象,用户编号或群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long toId; // 根据 conversationType 区分
|
||||
|
||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer type; // 参见 ImMessageTypeEnum 枚举
|
||||
|
||||
@Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private ImMessageBody body;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package cn.iocoder.yudao.module.im.websocket.message;
|
||||
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.message.body.ImMessageBody;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 消息发送 send")
|
||||
@Data
|
||||
public class ImSendMessage {
|
||||
|
||||
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer conversationType; // 对应 ImConversationTypeEnum 枚举
|
||||
|
||||
@Schema(description = "聊天对象,用户编号或群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long toId; // 根据 conversationType 区分
|
||||
|
||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer type; // 参见 ImMessageTypeEnum 枚举
|
||||
|
||||
@Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private ImMessageBody body;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.im.dal.mysql.conversation.ConversationMapper">
|
||||
|
||||
<!--
|
||||
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
|
||||
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
|
||||
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
|
||||
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
|
||||
-->
|
||||
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.im.dal.mysql.inbox.InboxMapper">
|
||||
|
||||
<!--
|
||||
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
|
||||
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
|
||||
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
|
||||
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
|
||||
-->
|
||||
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.im.dal.mysql.message.MessageMapper">
|
||||
|
||||
<!--
|
||||
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
|
||||
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
|
||||
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
|
||||
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
|
||||
-->
|
||||
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package cn.iocoder.yudao.module.im.service.conversation;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
|
||||
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.*;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ConversationDO;
|
||||
import cn.iocoder.yudao.module.im.dal.mysql.conversation.ConversationMapper;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.*;
|
||||
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link ConversationServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(ConversationServiceImpl.class)
|
||||
public class ConversationServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private ConversationServiceImpl conversationService;
|
||||
|
||||
@Resource
|
||||
private ConversationMapper conversationMapper;
|
||||
|
||||
@Test
|
||||
public void testCreateConversation_success() {
|
||||
// 准备参数
|
||||
ConversationSaveReqVO createReqVO = randomPojo(ConversationSaveReqVO.class).setId(null);
|
||||
|
||||
// 调用
|
||||
Long conversationId = conversationService.createConversation(createReqVO);
|
||||
// 断言
|
||||
assertNotNull(conversationId);
|
||||
// 校验记录的属性是否正确
|
||||
ConversationDO conversation = conversationMapper.selectById(conversationId);
|
||||
assertPojoEquals(createReqVO, conversation, "id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConversation_success() {
|
||||
// mock 数据
|
||||
ConversationDO dbConversation = randomPojo(ConversationDO.class);
|
||||
conversationMapper.insert(dbConversation);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
ConversationSaveReqVO updateReqVO = randomPojo(ConversationSaveReqVO.class, o -> {
|
||||
o.setId(dbConversation.getId()); // 设置更新的 ID
|
||||
});
|
||||
|
||||
// 调用
|
||||
conversationService.updateConversation(updateReqVO);
|
||||
// 校验是否更新正确
|
||||
ConversationDO conversation = conversationMapper.selectById(updateReqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(updateReqVO, conversation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConversation_notExists() {
|
||||
// 准备参数
|
||||
ConversationSaveReqVO updateReqVO = randomPojo(ConversationSaveReqVO.class);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> conversationService.updateConversation(updateReqVO), CONVERSATION_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteConversation_success() {
|
||||
// mock 数据
|
||||
ConversationDO dbConversation = randomPojo(ConversationDO.class);
|
||||
conversationMapper.insert(dbConversation);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbConversation.getId();
|
||||
|
||||
// 调用
|
||||
conversationService.deleteConversation(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(conversationMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteConversation_notExists() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> conversationService.deleteConversation(id), CONVERSATION_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
|
||||
public void testGetConversationPage() {
|
||||
// mock 数据
|
||||
ConversationDO dbConversation = randomPojo(ConversationDO.class, o -> { // 等会查询到
|
||||
o.setUserId(null);
|
||||
o.setConversationType(null);
|
||||
o.setTargetId(null);
|
||||
o.setNo(null);
|
||||
o.setPinned(null);
|
||||
o.setLastReadTime(null);
|
||||
o.setCreateTime(null);
|
||||
});
|
||||
conversationMapper.insert(dbConversation);
|
||||
// 测试 userId 不匹配
|
||||
conversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setUserId(null)));
|
||||
// 测试 conversationType 不匹配
|
||||
conversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setConversationType(null)));
|
||||
// 测试 targetId 不匹配
|
||||
conversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setTargetId(null)));
|
||||
// 测试 no 不匹配
|
||||
conversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setNo(null)));
|
||||
// 测试 pinned 不匹配
|
||||
conversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setPinned(null)));
|
||||
// 测试 lastReadTime 不匹配
|
||||
conversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setLastReadTime(null)));
|
||||
// 测试 createTime 不匹配
|
||||
conversationMapper.insert(cloneIgnoreId(dbConversation, o -> o.setCreateTime(null)));
|
||||
// 准备参数
|
||||
ConversationPageReqVO reqVO = new ConversationPageReqVO();
|
||||
reqVO.setUserId(null);
|
||||
reqVO.setConversationType(null);
|
||||
reqVO.setTargetId(null);
|
||||
reqVO.setNo(null);
|
||||
reqVO.setPinned(null);
|
||||
reqVO.setLastReadTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
|
||||
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
|
||||
|
||||
// 调用
|
||||
PageResult<ConversationDO> pageResult = conversationService.getConversationPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbConversation, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package cn.iocoder.yudao.module.im.service.inbox;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
|
||||
import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.*;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.inbox.InboxDO;
|
||||
import cn.iocoder.yudao.module.im.dal.mysql.inbox.InboxMapper;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.*;
|
||||
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link InboxServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(InboxServiceImpl.class)
|
||||
public class InboxServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private InboxServiceImpl inboxService;
|
||||
|
||||
@Resource
|
||||
private InboxMapper inboxMapper;
|
||||
|
||||
@Test
|
||||
public void testCreateInbox_success() {
|
||||
// 准备参数
|
||||
InboxSaveReqVO createReqVO = randomPojo(InboxSaveReqVO.class).setId(null);
|
||||
|
||||
// 调用
|
||||
Long inboxId = inboxService.createInbox(createReqVO);
|
||||
// 断言
|
||||
assertNotNull(inboxId);
|
||||
// 校验记录的属性是否正确
|
||||
InboxDO inbox = inboxMapper.selectById(inboxId);
|
||||
assertPojoEquals(createReqVO, inbox, "id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInbox_success() {
|
||||
// mock 数据
|
||||
InboxDO dbInbox = randomPojo(InboxDO.class);
|
||||
inboxMapper.insert(dbInbox);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
InboxSaveReqVO updateReqVO = randomPojo(InboxSaveReqVO.class, o -> {
|
||||
o.setId(dbInbox.getId()); // 设置更新的 ID
|
||||
});
|
||||
|
||||
// 调用
|
||||
inboxService.updateInbox(updateReqVO);
|
||||
// 校验是否更新正确
|
||||
InboxDO inbox = inboxMapper.selectById(updateReqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(updateReqVO, inbox);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInbox_notExists() {
|
||||
// 准备参数
|
||||
InboxSaveReqVO updateReqVO = randomPojo(InboxSaveReqVO.class);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> inboxService.updateInbox(updateReqVO), INBOX_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteInbox_success() {
|
||||
// mock 数据
|
||||
InboxDO dbInbox = randomPojo(InboxDO.class);
|
||||
inboxMapper.insert(dbInbox);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbInbox.getId();
|
||||
|
||||
// 调用
|
||||
inboxService.deleteInbox(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(inboxMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteInbox_notExists() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> inboxService.deleteInbox(id), INBOX_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
|
||||
public void testGetInboxPage() {
|
||||
// mock 数据
|
||||
InboxDO dbInbox = randomPojo(InboxDO.class, o -> { // 等会查询到
|
||||
o.setUserId(null);
|
||||
o.setMessageId(null);
|
||||
o.setSequence(null);
|
||||
o.setCreateTime(null);
|
||||
});
|
||||
inboxMapper.insert(dbInbox);
|
||||
// 测试 userId 不匹配
|
||||
inboxMapper.insert(cloneIgnoreId(dbInbox, o -> o.setUserId(null)));
|
||||
// 测试 messageId 不匹配
|
||||
inboxMapper.insert(cloneIgnoreId(dbInbox, o -> o.setMessageId(null)));
|
||||
// 测试 sequence 不匹配
|
||||
inboxMapper.insert(cloneIgnoreId(dbInbox, o -> o.setSequence(null)));
|
||||
// 测试 createTime 不匹配
|
||||
inboxMapper.insert(cloneIgnoreId(dbInbox, o -> o.setCreateTime(null)));
|
||||
// 准备参数
|
||||
InboxPageReqVO reqVO = new InboxPageReqVO();
|
||||
reqVO.setUserId(null);
|
||||
reqVO.setMessageId(null);
|
||||
reqVO.setSequence(null);
|
||||
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
|
||||
|
||||
// 调用
|
||||
PageResult<InboxDO> pageResult = inboxService.getInboxPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbInbox, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package cn.iocoder.yudao.module.im.service.message;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
|
||||
import cn.iocoder.yudao.module.im.controller.admin.message.vo.*;
|
||||
import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
|
||||
import cn.iocoder.yudao.module.im.dal.mysql.message.MessageMapper;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.*;
|
||||
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link MessageServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MessageServiceImpl.class)
|
||||
public class MessageServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MessageServiceImpl messageService;
|
||||
|
||||
@Resource
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
@Test
|
||||
public void testCreateMessage_success() {
|
||||
// 准备参数
|
||||
MessageSaveReqVO createReqVO = randomPojo(MessageSaveReqVO.class).setId(null);
|
||||
|
||||
// 调用
|
||||
Long messageId = messageService.createMessage(createReqVO);
|
||||
// 断言
|
||||
assertNotNull(messageId);
|
||||
// 校验记录的属性是否正确
|
||||
MessageDO message = messageMapper.selectById(messageId);
|
||||
assertPojoEquals(createReqVO, message, "id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateMessage_success() {
|
||||
// mock 数据
|
||||
MessageDO dbMessage = randomPojo(MessageDO.class);
|
||||
messageMapper.insert(dbMessage);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
MessageSaveReqVO updateReqVO = randomPojo(MessageSaveReqVO.class, o -> {
|
||||
o.setId(dbMessage.getId()); // 设置更新的 ID
|
||||
});
|
||||
|
||||
// 调用
|
||||
messageService.updateMessage(updateReqVO);
|
||||
// 校验是否更新正确
|
||||
MessageDO message = messageMapper.selectById(updateReqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(updateReqVO, message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateMessage_notExists() {
|
||||
// 准备参数
|
||||
MessageSaveReqVO updateReqVO = randomPojo(MessageSaveReqVO.class);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> messageService.updateMessage(updateReqVO), MESSAGE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMessage_success() {
|
||||
// mock 数据
|
||||
MessageDO dbMessage = randomPojo(MessageDO.class);
|
||||
messageMapper.insert(dbMessage);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbMessage.getId();
|
||||
|
||||
// 调用
|
||||
messageService.deleteMessage(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(messageMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMessage_notExists() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> messageService.deleteMessage(id), MESSAGE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
|
||||
public void testGetMessagePage() {
|
||||
// mock 数据
|
||||
MessageDO dbMessage = randomPojo(MessageDO.class, o -> { // 等会查询到
|
||||
o.setClientMessageId(null);
|
||||
o.setSenderId(null);
|
||||
o.setReceiverId(null);
|
||||
o.setSenderNickname(null);
|
||||
o.setSenderAvatar(null);
|
||||
o.setConversationType(null);
|
||||
o.setConversationNo(null);
|
||||
o.setContentType(null);
|
||||
o.setContent(null);
|
||||
o.setSendTime(null);
|
||||
o.setSendFrom(null);
|
||||
o.setCreateTime(null);
|
||||
});
|
||||
messageMapper.insert(dbMessage);
|
||||
// 测试 clientMessageId 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setClientMessageId(null)));
|
||||
// 测试 senderId 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSenderId(null)));
|
||||
// 测试 receiverId 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setReceiverId(null)));
|
||||
// 测试 senderNickname 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSenderNickname(null)));
|
||||
// 测试 senderAvatar 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSenderAvatar(null)));
|
||||
// 测试 conversationType 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setConversationType(null)));
|
||||
// 测试 conversationNo 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setConversationNo(null)));
|
||||
// 测试 contentType 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setContentType(null)));
|
||||
// 测试 content 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setContent(null)));
|
||||
// 测试 sendTime 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSendTime(null)));
|
||||
// 测试 sendFrom 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setSendFrom(null)));
|
||||
// 测试 createTime 不匹配
|
||||
messageMapper.insert(cloneIgnoreId(dbMessage, o -> o.setCreateTime(null)));
|
||||
// 准备参数
|
||||
MessagePageReqVO reqVO = new MessagePageReqVO();
|
||||
reqVO.setClientMessageId(null);
|
||||
reqVO.setSenderId(null);
|
||||
reqVO.setReceiverId(null);
|
||||
reqVO.setSenderNickname(null);
|
||||
reqVO.setSenderAvatar(null);
|
||||
reqVO.setConversationType(null);
|
||||
reqVO.setConversationNo(null);
|
||||
reqVO.setContentType(null);
|
||||
reqVO.setContent(null);
|
||||
reqVO.setSendTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
|
||||
reqVO.setSendFrom(null);
|
||||
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
|
||||
|
||||
// 调用
|
||||
PageResult<MessageDO> pageResult = messageService.getMessagePage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbMessage, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -101,6 +101,13 @@
|
|||
<!-- <version>${revision}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- IM 相关模块。默认注释,保证编译速度 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-im-biz</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- spring boot 配置所需依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ logging:
|
|||
cn.iocoder.yudao.module.statistics.dal.mysql: debug
|
||||
cn.iocoder.yudao.module.crm.dal.mysql: debug
|
||||
cn.iocoder.yudao.module.erp.dal.mysql: debug
|
||||
cn.iocoder.yudao.module.im.dal.mysql: debug
|
||||
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
|
||||
|
||||
debug: false
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ yudao:
|
|||
- cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants
|
||||
- cn.iocoder.yudao.module.system.enums.ErrorCodeConstants
|
||||
- cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants
|
||||
- cn.iocoder.yudao.module.im.enums.ErrorCodeConstants
|
||||
tenant: # 多租户相关配置项
|
||||
enable: true
|
||||
ignore-urls:
|
||||
|
|
|
|||
Loading…
Reference in New Issue