├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── doc ├── 20220722202239.jpg └── 2022年4月18日.jpg ├── im-core ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── example │ ├── config │ ├── Chat.java │ ├── CourierConfig.java │ ├── Im.java │ ├── ImConfig.java │ ├── ImSessionContext.java │ └── MongoConfig.java │ ├── dao │ ├── AuthRepository.java │ ├── EmoticonRepository.java │ ├── FileRepository.java │ ├── FriendInfoRepository.java │ ├── GroupRepository.java │ ├── MessageRepository.java │ ├── MongoRepository.java │ ├── UnReadMessageRepository.java │ ├── UserEmoticonRepository.java │ ├── UserGroupRepository.java │ └── UserRepository.java │ ├── enums │ ├── CommandEnum.java │ ├── DefaultEnum.java │ ├── EmoticonOperationTypeEnum.java │ ├── GroupAdminTypeEnum.java │ ├── GroupOutTypeEnum.java │ ├── JoinGroupEnum.java │ ├── KeyEnum.java │ ├── MessageFetchTypeEnum.java │ ├── RemoveGroupEnum.java │ ├── RoomRoleEnum.java │ ├── SystemMessageTypeEnum.java │ ├── UserGroupConfigTypeEnum.java │ └── VideoCommandEnum.java │ ├── listener │ ├── AbstractImGroupListener.java │ ├── AbstractImUserListener.java │ ├── ImGroupListener.java │ ├── ImGroupListenerAdapter.java │ ├── ImUserListener.java │ └── ImUserListenerAdapter.java │ ├── packets │ ├── FileSource.java │ ├── ImClientNode.java │ ├── ImPacket.java │ ├── LastMessage.java │ ├── ReplyMessage.java │ ├── Status.java │ ├── bean │ │ ├── Auth.java │ │ ├── Emoticon.java │ │ ├── FileInfo.java │ │ ├── FriendInfo.java │ │ ├── Group.java │ │ ├── Message.java │ │ ├── UnReadMessage.java │ │ ├── User.java │ │ ├── UserEmoticon.java │ │ └── UserGroup.java │ ├── file │ │ ├── FileInit.java │ │ └── FileMerge.java │ └── handler │ │ ├── emoticon │ │ ├── EmoticonOperationReqBody.java │ │ ├── EmoticonOperationRespBody.java │ │ └── EmoticonSearchReqBody.java │ │ ├── message │ │ ├── ChatReqBody.java │ │ ├── ChatRespBody.java │ │ ├── FileMessageBody.java │ │ ├── MessageDeleteReqBody.java │ │ ├── MessageDeleteRespBody.java │ │ ├── MessageForwardReqBody.java │ │ ├── MessageReactionReqBody.java │ │ ├── MessageReactionRespBody.java │ │ ├── MessageReadReqBody.java │ │ ├── MessageReqBody.java │ │ ├── MessageRespBody.java │ │ └── MessageSearchReqBody.java │ │ ├── room │ │ ├── CreateGroupReqBody.java │ │ ├── GroupAdminReqBody.java │ │ ├── GroupProfileReqBody.java │ │ ├── GroupUserReqBody.java │ │ ├── HandoverGroupRespBody.java │ │ ├── JoinGroupNotifyBody.java │ │ ├── SearchRoomReqBody.java │ │ ├── SearchRoomRespBody.java │ │ ├── SetPublicRoomReqBody.java │ │ └── UserGroupConfigReqBody.java │ │ ├── system │ │ ├── HeartbeatBody.java │ │ ├── LoginReqBody.java │ │ ├── LoginRespBody.java │ │ ├── RegisterReqBody.java │ │ ├── RespBody.java │ │ ├── SetNewPasswordReqBody.java │ │ ├── SystemMessageReqBody.java │ │ └── SystemTextMessage.java │ │ ├── user │ │ ├── EditProfileReqBody.java │ │ ├── SearchUserReqBody.java │ │ ├── SearchUserRespBody.java │ │ ├── UserReqBody.java │ │ └── UserStatusBody.java │ │ └── video │ │ └── VideoReqBody.java │ ├── service │ ├── AuthService.java │ ├── EmoticonService.java │ ├── FileService.java │ ├── FriendInfoService.java │ ├── GroupService.java │ ├── MessageService.java │ ├── UnReadMessageService.java │ ├── UserEmoticonService.java │ ├── UserGroupService.java │ └── UserService.java │ └── util │ └── TestUtil.java ├── im-server ├── pom.xml ├── script │ ├── _cmd.bat │ ├── pkg.xml │ ├── startup.bat │ └── startup.sh └── src │ └── main │ ├── java │ └── org │ │ └── example │ │ ├── ImServer.java │ │ ├── commond │ │ ├── AbstractCmdHandler.java │ │ ├── CmdHandler.java │ │ ├── CommandManager.java │ │ └── handler │ │ │ ├── CloseReqHandler.java │ │ │ ├── HeartbeatReqHandler.java │ │ │ ├── LoginReqHandler.java │ │ │ ├── UserListHandler.java │ │ │ ├── UserReqHandler.java │ │ │ ├── emoticon │ │ │ ├── EmoticonOperationReqHandler.java │ │ │ ├── EmoticonReqHandler.java │ │ │ └── EmoticonSearchReqHandler.java │ │ │ ├── message │ │ │ ├── ChatReqHandler.java │ │ │ ├── MessageDeleteHandler.java │ │ │ ├── MessageForwardReqHandler.java │ │ │ ├── MessageReactionReqHandler.java │ │ │ ├── MessageReadReqHandler.java │ │ │ ├── MessageReqHandler.java │ │ │ └── MessageSearchReqHandler.java │ │ │ ├── room │ │ │ ├── CreatGroupReqHandler.java │ │ │ ├── DisbandGroupHandler.java │ │ │ ├── EditGroupProfileReqHandler.java │ │ │ ├── HandoverGroupHandler.java │ │ │ ├── JoinGroupReqHandler.java │ │ │ ├── RemoveGroupUserReqHandler.java │ │ │ ├── SearchRoomReqHandler.java │ │ │ ├── SetPublicRoomReqHandler.java │ │ │ ├── SetRoomAdminReqHandler.java │ │ │ └── UserGroupConfigReqHandler.java │ │ │ ├── system │ │ │ ├── EditProfileHandler.java │ │ │ ├── SetNewPasswordReqHandler.java │ │ │ └── SystemTextMessageHandler.java │ │ │ └── video │ │ │ └── VideoReqHandler.java │ │ ├── config │ │ ├── ImServerHttpStart.java │ │ └── ImServerWebSocketStart.java │ │ ├── listener │ │ ├── ImServerGroupListener.java │ │ ├── ImServerUserListener.java │ │ └── ImTioServerListener.java │ │ ├── protocol │ │ ├── http │ │ │ ├── LoginController.java │ │ │ ├── RegisterController.java │ │ │ ├── UploadController.java │ │ │ └── service │ │ │ │ └── UploadService.java │ │ └── ws │ │ │ └── WsMsgHandler.java │ │ └── util │ │ ├── CustomMinioClient.java │ │ ├── MinIoUtils.java │ │ ├── TestUtil2.java │ │ └── ThreadPoolUtil.java │ └── resources │ ├── application.setting │ ├── img │ ├── account.png │ ├── accountGroup.png │ └── logo.png │ ├── logback.properties │ └── logback.xml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | .idea 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | /target/ 24 | 25 | im-core/target/ 26 | im-server/target/ 27 | 28 | /im-server/target/classes/index.html -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17 2 | MAINTAINER 473302042@qq.com 3 | ADD im-server-jar-with-dependencies.jar im-server-jar-with-dependencies.jar 4 | ADD public.crt public.crt 5 | RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone 6 | RUN keytool -import -alias sslServer_03 -file public.crt -keystore /usr/java/openjdk-17/lib/security/cacerts -storepass changeit -v -noprompt 7 | EXPOSE 8088 9326 8 | ENTRYPOINT ["java","-XX:+HeapDumpOnOutOfMemoryError","-Dtio.default.read.buffer.size=512","-XX:HeapDumpPath=./java-im-server-pid.hprof","-DENV=prod","-jar","im-server-jar-with-dependencies.jar"] 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 信使 简洁轻量的即时通讯工具 2 | 3 | [国内访问](https://gitee.com/LiLongLong719/im) [GitHub](https://github.com/Alisonispig/im) 4 | 5 | ### 😯 演示地址(万水千山都是情,点个Star行不行) 6 | 7 | 文档地址: [https://www.o0o0oo.com/](https://www.o0o0oo.com/) 8 | 9 | 演示地址: [https://chat.o0o0oo.com/](https://chat.o0o0oo.com/) 10 | 11 | 前端地址:[https://gitee.com/LiLongLong719/im-web](https://gitee.com/LiLongLong719/im-web) 12 | 13 | 演示账号 a/a 仅演示目的.服务器的内容不定期清理(作者叫礼貌,可以找我聊天哦) 14 | 15 | ### 🏞️ 展示截图 16 | 17 | ![功能展示](doc/20220722202239.jpg) 18 | 19 | ### 🤩 核心目标: 完成单机10万+的可用项目 20 | 21 | 注意: 早期的思路和内容来自J-IM 22 | 1. 单机十万+的目标来自Tio的性能测试,据称可以实现单机百万连接,我寻思着我十万应该问题不大 23 | 2. 从头开始,没有使用J-IM的代码更有掌控力,无论代码好与坏,并且在这个过程中遇到的问题和累积的经验更有价值.(主要是J-IM的代码有些看不懂) 24 | 3. 前端的代码需要有一个与之匹配的后端,否则的话可能是一种灾难 25 | 26 | ### 🤦‍ 技术栈 27 | 28 | 核心Tio,包括http和socket都是tio.没有引入spring系列,所以大部分的内容都需要自己封装,好处是启动快,体积小。 弊端就是方方面面都需要自己考虑。 29 | 30 | 从登录开始所有的交互全部使用socket io,除minio分片上传使用了http外,目前没有其他使用http的地方。 31 | 32 | 文件存储使用了minio. 一个标准的数据存储,很香 33 | 34 | 缓存和数据存储使用mangodb, 同时解决缓存和数据存储的问题(据说挺快的,没有做大量尝试) 35 | 36 | ### 🚗 使用 37 | 38 | 运行ImServer下Main方法 39 | 40 | ### 🎁 打包 41 | 42 | 运行 mvn clean package 43 | 44 | ### 🥶 安装教程 45 | 46 | > 教程来自windows docker, linux同理 47 | 1. 安装minio 48 | ``` 49 | docker run -d -p 9000:9000 -p 9001:9001 --name minio -v /home/minio/data:/data -v /home/minio/cert:/root/.minio -e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" -e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" --restart=always quay.io/minio/minio server /data --console-address ":9001" 50 | ``` 51 | 2. 安装MongoDB 52 | ``` 53 | docker run --name mongo --restart=always -p 27017:27017 -v /home/mongodb:/data/db -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=QuniMade! -d mongo:latest mongod --auth 54 | ``` 55 | 3. 安装nginx, 作者使用nginxWebui 56 | ``` 57 | # Linux 58 | docker run -itd -v /home/nginxWebUI:/home/nginxWebUI -e BOOT_OPTIONS="--server.port=8080" --privileged=true --restart=always --net=host cym1102/nginxwebui:latest 59 | ``` 60 | 4. 配置minio https访问(此处以自签名为例)下载签名工具 [签名工具](https://github.com/minio/certgen) 61 | > 使用公网证书可忽略生成的步骤,直接讲证书放进去即可 62 | ``` 63 | # 172.17.0.3 是docker minio内部IP, 需要特别注意这个IP在映射出端口的情况下必须写 64 | certgen-windows-amd64.exe -ecdsa-curve P256 -host 127.0.0.1,localhost,172.17.0.3,192.168.0.103 65 | ``` 66 | 放置于抛出的 E:/docker/minio/cert 目录下,完整目录为 E:/docker/minio/cert/certs 重启容器 67 | 5. 配置JDK证书信任,否则会抛出下列错误 68 | ``` 69 | javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 70 | at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131) 71 | at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:371) 72 | at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:314) 73 | ``` 74 | 在JDK bin目录下运行 75 | ``` 76 | .\keytool -import -alias sslServer_03 -file D:\key\新建文件夹\public.crt -keystore ..\lib\security\cacerts -storepass changeit -v 77 | ``` 78 | 79 | ### 🐧 特别鸣谢 80 | [J-IM](https://gitee.com/xchao/j-im) 提供的思路 81 | 82 | ### ⚒️ 交流 83 | 在使用过程中有任何问题和想法,请给我提 [Issue](https://gitee.com/LiLongLong719/im/issues)。 84 | 你也可以在Issue查看别人提的问题和给出解决方案。 85 | 86 | 可以通过演示系统中添加会话 礼貌 联系到我 87 | 88 | 想了解最新进展,可以通过微信与我沟通 89 | ![添加微信,备注信使](doc/2022年4月18日.jpg) 90 | -------------------------------------------------------------------------------- /doc/20220722202239.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alisonispig/im/2117923905091aae516058e216505375265802ce/doc/20220722202239.jpg -------------------------------------------------------------------------------- /doc/2022年4月18日.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alisonispig/im/2117923905091aae516058e216505375265802ce/doc/2022年4月18日.jpg -------------------------------------------------------------------------------- /im-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | im 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | UTF-8 13 | 13 14 | 13 15 | 3.8.1.v20220401-RELEASE 16 | 17 | 18 | im-core 19 | 20 | 21 | 22 | org.t-io 23 | tio-core 24 | ${tio-version} 25 | 26 | 27 | org.t-io 28 | tio-websocket-server 29 | ${tio-version} 30 | 31 | 32 | org.t-io 33 | tio-http-server 34 | ${tio-version} 35 | 36 | 37 | 38 | org.projectlombok 39 | lombok 40 | 1.18.24 41 | 42 | 43 | 44 | cn.hutool 45 | hutool-core 46 | 5.8.3 47 | 48 | 49 | 50 | cn.hutool 51 | hutool-setting 52 | 5.8.3 53 | 54 | 55 | cn.hutool 56 | hutool-crypto 57 | 5.8.3 58 | 59 | 60 | cn.hutool 61 | hutool-jwt 62 | 5.8.3 63 | 64 | 65 | 66 | ch.qos.logback 67 | logback-core 68 | 1.2.11 69 | 70 | 71 | 72 | ch.qos.logback 73 | logback-classic 74 | 1.2.11 75 | 76 | 77 | 78 | org.mongodb 79 | mongodb-driver-sync 80 | 4.6.0 81 | 82 | 83 | 84 | io.minio 85 | minio 86 | 8.3.1 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/config/CourierConfig.java: -------------------------------------------------------------------------------- 1 | package org.example.config; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import cn.hutool.setting.Setting; 5 | import lombok.Data; 6 | 7 | import java.nio.charset.Charset; 8 | 9 | public class CourierConfig { 10 | 11 | /** 12 | * 应用名称 13 | */ 14 | public static String applicationName; 15 | 16 | /** 17 | * Socket端口 18 | */ 19 | public static Integer socketPort; 20 | 21 | /** 22 | * 心跳间隔 23 | */ 24 | public static Integer socketHeartbeat; 25 | 26 | /** 27 | * Http端口 28 | */ 29 | public static Integer httpPort; 30 | 31 | /** 32 | * 最大保持 33 | */ 34 | public static Integer httpMaxLiveTime; 35 | 36 | /** 37 | * 是否使用session 38 | */ 39 | public static Boolean httpUseSession; 40 | 41 | /** 42 | * 检查Host 43 | */ 44 | public static Boolean httpCheckHost; 45 | 46 | /** 47 | * 检查Md5 48 | */ 49 | public static Boolean checkFileMd5; 50 | 51 | /** 52 | * 数据库端口 53 | */ 54 | public static String mongoHost; 55 | 56 | public static String mongoUserName; 57 | 58 | public static String mongoPassword; 59 | 60 | /** 61 | * 文件地址 62 | */ 63 | public static String fileUrl; 64 | 65 | public static String minioHost; 66 | public static Integer minioPort; 67 | public static String minioAccessKey; 68 | public static String minioSecretKey; 69 | public static Boolean minioUseSSL; 70 | 71 | public static String env; 72 | 73 | static { 74 | Setting setting = new Setting("application.setting", Charset.defaultCharset(), true); 75 | String property = System.getProperty("ENV"); 76 | env = StrUtil.isNotBlank(property) ? property : "dev"; 77 | 78 | applicationName = setting.get("applicationName"); 79 | 80 | socketPort = setting.getInt("socketPort"); 81 | socketHeartbeat = setting.getInt("socketHeartbeat"); 82 | 83 | httpPort = setting.getInt("httpPort"); 84 | httpMaxLiveTime = setting.getInt("httpMaxLiveTime"); 85 | httpUseSession = setting.getBool("httpUseSession"); 86 | httpCheckHost = setting.getBool("httpCheckHost"); 87 | 88 | checkFileMd5 = setting.getBool("checkFileMd5"); 89 | 90 | mongoHost = setting.get(env, "mongoHost"); 91 | mongoUserName = setting.get(env, "mongoUserName"); 92 | mongoPassword = setting.get(env, "mongoPassword"); 93 | fileUrl = setting.get(env, "fileUrl"); 94 | minioHost = setting.get(env, "minioHost"); 95 | minioAccessKey = setting.get(env, "minioAccessKey"); 96 | minioSecretKey = setting.get(env, "minioSecretKey"); 97 | minioPort = setting.getInt("minioPort", env); 98 | minioUseSSL = setting.getBool("minioUseSSL", env); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/config/ImConfig.java: -------------------------------------------------------------------------------- 1 | package org.example.config; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import org.example.listener.ImUserListener; 6 | import org.tio.core.TioConfig; 7 | import org.tio.utils.prop.MapWithLockPropSupport; 8 | import org.tio.utils.time.Time; 9 | 10 | @EqualsAndHashCode(callSuper = true) 11 | @Data 12 | public class ImConfig extends MapWithLockPropSupport { 13 | 14 | public static final String CHARSET = "utf-8"; 15 | 16 | public TioConfig tioConfig; 17 | 18 | /* 19 | 消息处理器,Redis持久化, 处理在线离线消息 20 | */ 21 | // public MessageHelper messageHelper; 22 | 23 | /** 24 | * 用户绑定监听器 25 | */ 26 | public ImUserListener imUserListener; 27 | 28 | 29 | ImConfig() { 30 | ImConfig.Global.set(this); 31 | } 32 | 33 | public static ImConfig get() { 34 | return ImConfig.Global.get(); 35 | } 36 | 37 | /** 38 | * 协议名字(可以随便取,主要用于开发人员辨识) 39 | */ 40 | public static final String PROTOCOL_NAME = "IM"; 41 | 42 | 43 | 44 | /** 45 | * 监听的ip 46 | */ 47 | public static final String SERVER_IP = null;//null表示监听所有,并不指定ip 48 | 49 | /** 50 | * ip数据监控统计,时间段 51 | * 52 | * @author tanyaowu 53 | */ 54 | public static interface IpStatDuration { 55 | public static final Long DURATION_1 = Time.MINUTE_1 * 5; 56 | public static final Long[] IPSTAT_DURATIONS = new Long[]{DURATION_1}; 57 | } 58 | 59 | public static class Global { 60 | private static ImConfig imConfig; 61 | 62 | public static ImConfig get() { 63 | return imConfig; 64 | } 65 | 66 | public static void set(ImConfig imConfig) { 67 | Global.imConfig = imConfig; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/config/ImSessionContext.java: -------------------------------------------------------------------------------- 1 | package org.example.config; 2 | 3 | import lombok.Data; 4 | import org.example.packets.ImClientNode; 5 | import org.tio.websocket.common.WsSessionContext; 6 | 7 | @Data 8 | public class ImSessionContext extends WsSessionContext { 9 | 10 | /** 11 | * 客户端Node信息 12 | */ 13 | protected ImClientNode imClientNode = null; 14 | 15 | /** 16 | * 客户端票据token,一般业务设置用 17 | */ 18 | protected String token = null; 19 | 20 | /** 21 | * sessionContext唯一ID 22 | */ 23 | protected String id; 24 | } 25 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/config/MongoConfig.java: -------------------------------------------------------------------------------- 1 | package org.example.config; 2 | 3 | import com.mongodb.ConnectionString; 4 | import com.mongodb.MongoClientSettings; 5 | import com.mongodb.client.MongoClient; 6 | import com.mongodb.client.MongoClients; 7 | import org.bson.codecs.configuration.CodecRegistry; 8 | import org.bson.codecs.pojo.PojoCodecProvider; 9 | 10 | import static org.bson.codecs.configuration.CodecRegistries.fromProviders; 11 | import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; 12 | 13 | /** 14 | * docker run --name mongo --restart=always -p 27017:27017 -v /home/mongodb:/data/db -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=QuniMade! -d mongo:latest mongod --auth 15 | */ 16 | 17 | public class MongoConfig { 18 | 19 | private static MongoClient mongoClient = null; 20 | 21 | public static MongoClient getClient() { 22 | if (mongoClient == null) { 23 | CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(), 24 | fromProviders(PojoCodecProvider.builder().automatic(true).build())); 25 | 26 | MongoClientSettings settings = MongoClientSettings.builder() 27 | .codecRegistry(pojoCodecRegistry) 28 | .applyConnectionString(new ConnectionString("mongodb://" + CourierConfig.mongoUserName + ":" + CourierConfig.mongoPassword + "@" + CourierConfig.mongoHost+"/")) 29 | .build(); 30 | 31 | mongoClient = MongoClients.create(settings); 32 | } 33 | return mongoClient; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/AuthRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.Auth; 4 | 5 | public class AuthRepository extends MongoRepository{ 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/EmoticonRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.Emoticon; 4 | 5 | public class EmoticonRepository extends MongoRepository{ 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/FileRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.FileInfo; 4 | 5 | public class FileRepository extends MongoRepository{ 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/FriendInfoRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.FriendInfo; 4 | 5 | public class FriendInfoRepository extends MongoRepository{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/GroupRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.Group; 4 | 5 | public class GroupRepository extends MongoRepository { 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/MessageRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.Message; 4 | 5 | public class MessageRepository extends MongoRepository{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/UnReadMessageRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.UnReadMessage; 4 | 5 | public class UnReadMessageRepository extends MongoRepository{ 6 | } 7 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/UserEmoticonRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.UserEmoticon; 4 | 5 | public class UserEmoticonRepository extends MongoRepository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/UserGroupRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.UserGroup; 4 | 5 | public class UserGroupRepository extends MongoRepository { 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/dao/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.dao; 2 | 3 | import org.example.packets.bean.User; 4 | 5 | public class UserRepository extends MongoRepository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/DefaultEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public enum DefaultEnum { 9 | 10 | /** 11 | * 默认用户头像 12 | */ 13 | ACCOUNT("account-default", "account.png"), 14 | 15 | /** 16 | * 默认群组头像 17 | */ 18 | ACCOUNT_GROUP("account-group-default", "accountGroup.png"), 19 | 20 | /** 21 | * 系统消息默认头像 22 | */ 23 | LOGO("logo","logo.png") 24 | 25 | ; 26 | 27 | private final String key; 28 | 29 | private final String value; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/EmoticonOperationTypeEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | public enum EmoticonOperationTypeEnum { 4 | 5 | /** 6 | * 添加表情包 7 | */ 8 | INSERT_TO_USER, 9 | /** 10 | * 删除用户的表情包 11 | */ 12 | DELETE, 13 | /** 14 | * 添加表情包到商店 15 | */ 16 | INSERT_TO_STORE, 17 | /** 18 | * 添加到用户和商店 19 | */ 20 | INSERT_TO_USER_AND_STORE, 21 | /** 22 | * 添加已存在的表情包到自己的库 23 | */ 24 | INSERT_EMOTICON_TO_USER, 25 | /** 26 | * 移动表情包到最前面 27 | */ 28 | MOVE_TOP 29 | 30 | } 31 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/GroupAdminTypeEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum GroupAdminTypeEnum { 7 | 8 | SET,UN_SET 9 | } 10 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/GroupOutTypeEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum GroupOutTypeEnum { 7 | 8 | OUT,REMOVE 9 | } 10 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/JoinGroupEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public enum JoinGroupEnum { 9 | 10 | /** 11 | * 群组创建 12 | */ 13 | STATE_CREATE(0), 14 | 15 | /** 16 | * 加入成功 17 | */ 18 | STATE_JOIN_SUCCESS(1); 19 | 20 | private final Integer value; 21 | } 22 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/KeyEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public enum KeyEnum { 9 | 10 | /** 11 | * 上下文Key 12 | */ 13 | IM_CHANNEL_CONTEXT_KEY("im_channel_context_key"), 14 | 15 | /** 16 | * Session上下文 Key 17 | */ 18 | IM_CHANNEL_SESSION_CONTEXT_KEY("im_channel_session_context_key"), 19 | 20 | /** 21 | * 用户信息 下存储人员姓名等基础信息 包括人员状态 22 | */ 23 | IM_USER_INFO_KEY("USER_INFO"), 24 | 25 | /** 26 | * 用户 存储用户拥有的群组ID列表 27 | */ 28 | IM_USER_GROUPS_KEY("USER_GROUPS"), 29 | 30 | /** 31 | * 用户 存储用户拥有的好友ID列表 32 | */ 33 | IM_USER_FRIENDS_KEY("USER_FRIENDS"), 34 | 35 | /** 36 | * 用户 存储用户当前的会话列表 37 | */ 38 | IM_USER_CHATS_KEY("USER_CHATS"), 39 | 40 | /** 41 | * 组 该Key下存储群组下面的用户ID列表 42 | */ 43 | IM_GROUP_USERS_KEY("GROUP_USERS"), 44 | 45 | /** 46 | * 群组基本信息 47 | */ 48 | IM_GROUP_INFO_KEY("GROUP_INFO"), 49 | 50 | /** 51 | * 群组消息 52 | */ 53 | IM_GROUP_MESSAGE_KEY("GROUP_MESSAGE"), 54 | 55 | /** 56 | * 群组未读消息 57 | */ 58 | IM_GROUP_UNREAD_MESSAGE_KEY("GROUP_UNREAD_MESSAGE"), 59 | 60 | /** 61 | * 所有用户Key 62 | */ 63 | IM_USER_LIST_KEY("USER_LIST"), 64 | 65 | /** 66 | * 用户信息列表 67 | */ 68 | IM_ACCOUNT_MAP_KEY("ACCOUNT_MAP_KEY"), 69 | 70 | /** 71 | * 表情回复消息 72 | */ 73 | IM_MESSAGE_REACTION_MAP_KEY("MESSAGE_REACTION_MAP_KEY"), 74 | 75 | /** 76 | * 文件上传信息 77 | */ 78 | IM_FILE_UPLOAD_KEY("FILE_UPLOAD_KEY"), 79 | ; 80 | 81 | private final String key; 82 | } 83 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/MessageFetchTypeEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum MessageFetchTypeEnum { 7 | 8 | DOWN,TOP 9 | } 10 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/RemoveGroupEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum RemoveGroupEnum { 7 | 8 | SELF_LEAVE,REMOVE_LEAVE 9 | 10 | } 11 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/RoomRoleEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum RoomRoleEnum { 7 | 8 | /** 9 | * 管理员 10 | */ 11 | ADMIN, 12 | /** 13 | * 子管理员 14 | */ 15 | SUB_ADMIN, 16 | /** 17 | * 群成员 18 | */ 19 | GENERAL 20 | 21 | } 22 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/SystemMessageTypeEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum SystemMessageTypeEnum { 7 | 8 | TEXT, 9 | } 10 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/UserGroupConfigTypeEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 用户对群组的配置类型枚举 7 | * 8 | * @author smart 9 | * @since 1.0.0 10 | */ 11 | @Getter 12 | public enum UserGroupConfigTypeEnum { 13 | 14 | /** 15 | * 打开或关闭通知 16 | */ 17 | NOTICE, 18 | 19 | /** 20 | * 群组备注 / 个人时为对方备注 / 群组时为对群组的备注 21 | */ 22 | GROUP_REMARK, 23 | 24 | /** 25 | * 置顶 26 | */ 27 | MOVE_TOP, 28 | 29 | } 30 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/enums/VideoCommandEnum.java: -------------------------------------------------------------------------------- 1 | package org.example.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum VideoCommandEnum { 7 | 8 | /** 9 | * 呼叫 10 | */ 11 | CALL, 12 | 13 | /** 14 | * 呼叫者拒绝/挂断 15 | */ 16 | CALLED_REFUSE, 17 | 18 | /** 19 | * 呼叫者拒绝/挂断 20 | */ 21 | BE_CALLED_REFUSE, 22 | 23 | /** 24 | * 同意 25 | */ 26 | AGREE, 27 | 28 | /** 29 | * 流已准备好 30 | */ 31 | STREAM_OK, 32 | 33 | /** 34 | * 对方不在线 35 | */ 36 | NOT_ONLINE, 37 | 38 | /** 39 | * 超时未响应 40 | */ 41 | TIME_OUT, 42 | 43 | /** 44 | * 命令发送成功 45 | */ 46 | REQ_SUCCESS, 47 | } 48 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/listener/AbstractImGroupListener.java: -------------------------------------------------------------------------------- 1 | package org.example.listener; 2 | 3 | import org.example.packets.bean.Group; 4 | import org.tio.core.ChannelContext; 5 | 6 | public abstract class AbstractImGroupListener implements ImGroupListener { 7 | 8 | public abstract void doAfterBind(ChannelContext channelContext, Group group); 9 | 10 | public abstract void doAfterUnbind(ChannelContext channelContext, Group group); 11 | 12 | // TODO 消息持久化 13 | @Override 14 | public void onAfterBind(ChannelContext channelContext, Group group) { 15 | 16 | doAfterBind(channelContext, group); 17 | } 18 | 19 | // TODO 消息持久化 20 | @Override 21 | public void onAfterUnbind(ChannelContext channelContext, Group group) { 22 | doAfterUnbind(channelContext, group); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/listener/AbstractImUserListener.java: -------------------------------------------------------------------------------- 1 | package org.example.listener; 2 | 3 | import org.example.packets.bean.User; 4 | import org.tio.core.ChannelContext; 5 | 6 | public abstract class AbstractImUserListener implements ImUserListener { 7 | 8 | public abstract void doAfterBind(ChannelContext channelContext, User user); 9 | 10 | public abstract void doAfterUnbind(ChannelContext channelContext, User user); 11 | 12 | @Override 13 | public void onAfterBind(ChannelContext channelContext, User user) { 14 | doAfterBind(channelContext, user); 15 | } 16 | 17 | @Override 18 | public void onAfterUnbind(ChannelContext channelContext, User user) { 19 | doAfterUnbind(channelContext, user); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/listener/ImGroupListener.java: -------------------------------------------------------------------------------- 1 | package org.example.listener; 2 | 3 | import org.example.packets.bean.Group; 4 | import org.tio.core.ChannelContext; 5 | 6 | public interface ImGroupListener { 7 | 8 | void onAfterBind(ChannelContext channelContext, Group build); 9 | 10 | void onAfterUnbind(ChannelContext channelContext, Group build); 11 | } 12 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/listener/ImGroupListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package org.example.listener; 2 | 3 | import org.example.packets.bean.Group; 4 | import org.tio.core.ChannelContext; 5 | import org.tio.core.intf.GroupListener; 6 | 7 | /** 8 | * T-io 群组适配器 用来适配转发来自Tio的消息 9 | */ 10 | public class ImGroupListenerAdapter implements GroupListener { 11 | 12 | private final ImGroupListener imGroupListener; 13 | 14 | public ImGroupListenerAdapter(ImGroupListener imGroupListener) { 15 | this.imGroupListener = imGroupListener; 16 | } 17 | 18 | @Override 19 | public void onAfterBind(ChannelContext channelContext, String group) { 20 | imGroupListener.onAfterBind(channelContext, Group.builder().roomId(group).build()); 21 | } 22 | 23 | @Override 24 | public void onAfterUnbind(ChannelContext channelContext, String group) { 25 | imGroupListener.onAfterUnbind(channelContext, Group.builder().roomId(group).build()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/listener/ImUserListener.java: -------------------------------------------------------------------------------- 1 | package org.example.listener; 2 | 3 | import org.example.packets.bean.User; 4 | import org.tio.core.ChannelContext; 5 | 6 | public interface ImUserListener { 7 | 8 | void onAfterBind(ChannelContext channelContext, User user); 9 | 10 | void onAfterUnbind(ChannelContext channelContext, User user); 11 | } 12 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/listener/ImUserListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package org.example.listener; 2 | 3 | import org.example.packets.bean.User; 4 | import org.tio.core.ChannelContext; 5 | 6 | public class ImUserListenerAdapter implements ImUserListener { 7 | 8 | private ImUserListener imUserListener; 9 | 10 | public ImUserListenerAdapter(ImUserListener imUserListener) { 11 | this.imUserListener = imUserListener; 12 | } 13 | 14 | @Override 15 | public void onAfterBind(ChannelContext channelContext, User user) { 16 | imUserListener.onAfterBind(channelContext, user); 17 | } 18 | 19 | @Override 20 | public void onAfterUnbind(ChannelContext channelContext, User user) { 21 | imUserListener.onAfterUnbind(channelContext, user); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/FileSource.java: -------------------------------------------------------------------------------- 1 | package org.example.packets; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class FileSource { 7 | 8 | private String name; 9 | 10 | private Integer size; 11 | 12 | private String type; 13 | 14 | private Boolean audio; 15 | 16 | private Double duration; 17 | 18 | private String url; 19 | 20 | private String preview; 21 | } 22 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/ImClientNode.java: -------------------------------------------------------------------------------- 1 | package org.example.packets; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import org.example.packets.bean.User; 6 | 7 | @Builder 8 | @Data 9 | public class ImClientNode { 10 | 11 | private String id; 12 | 13 | /** 14 | * 客户端ip 15 | */ 16 | private String ip; 17 | /** 18 | * 客户端远程port 19 | */ 20 | private int port; 21 | /** 22 | * 如果没登录过,则为null 23 | */ 24 | private User user; 25 | /** 26 | * 地区 27 | */ 28 | private String region; 29 | /** 30 | * 浏览器信息(这里暂时放在这,后面会扩展出比如httpImClientNode、TcpImClientNode等) 31 | */ 32 | private String useragent; 33 | } 34 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/ImPacket.java: -------------------------------------------------------------------------------- 1 | package org.example.packets; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import org.example.enums.CommandEnum; 6 | import org.tio.core.intf.Packet; 7 | 8 | 9 | public class ImPacket extends Packet { 10 | 11 | /** 12 | * 包状态码; 13 | */ 14 | protected Status status; 15 | /** 16 | * 消息体; 17 | */ 18 | protected String body; 19 | /** 20 | * 消息命令; 21 | */ 22 | private CommandEnum command; 23 | 24 | 25 | public ImPacket(CommandEnum command, Object data) { 26 | if (ObjectUtil.isNotNull(data)) { 27 | this.body = JSON.toJSONString(data); 28 | } 29 | this.command = command; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/LastMessage.java: -------------------------------------------------------------------------------- 1 | package org.example.packets; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * 最后一条消息 10 | */ 11 | @Builder 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class LastMessage { 16 | 17 | private String messageId; 18 | 19 | /** 20 | * 消息主体 21 | */ 22 | private String content; 23 | 24 | /** 25 | * 发送者ID 26 | */ 27 | private String senderId; 28 | 29 | /** 30 | * 用户名 31 | */ 32 | private String username; 33 | 34 | /** 35 | * 发送时间 10:20 36 | */ 37 | private String timestamp; 38 | 39 | /** 40 | * 发送年月日 41 | */ 42 | private String date; 43 | 44 | /** 45 | * 是否已保存 46 | */ 47 | private Boolean saved; 48 | 49 | /** 50 | * 已分发 51 | */ 52 | private Boolean distributed; 53 | 54 | /** 55 | * 已读 56 | */ 57 | private Boolean seen; 58 | 59 | /** 60 | * 发送时间时间戳 61 | */ 62 | private Long indexId; 63 | 64 | } 65 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/ReplyMessage.java: -------------------------------------------------------------------------------- 1 | package org.example.packets; 2 | 3 | import lombok.Data; 4 | import org.example.packets.handler.message.FileMessageBody; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class ReplyMessage { 10 | 11 | /** 12 | * 引用内容 13 | */ 14 | private String content; 15 | 16 | /** 17 | * 发送者 18 | */ 19 | private String senderId; 20 | 21 | /** 22 | * 文件信息 23 | */ 24 | private List files; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/Status.java: -------------------------------------------------------------------------------- 1 | package org.example.packets; 2 | 3 | import cn.hutool.core.date.DatePattern; 4 | import cn.hutool.core.date.DateUtil; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.Date; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Data 14 | public class Status { 15 | 16 | private String state; 17 | 18 | private String lastChanged; 19 | 20 | public static Status online() { 21 | return new Status("online", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN)); 22 | } 23 | 24 | public static Status offline() { 25 | return new Status("offline", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/Auth.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import lombok.Data; 4 | import org.bson.codecs.pojo.annotations.BsonId; 5 | 6 | import java.util.Date; 7 | 8 | @Data 9 | public class Auth { 10 | 11 | @BsonId 12 | private String id; 13 | 14 | /** 15 | * 用户ID 16 | */ 17 | private String userId; 18 | 19 | /** 20 | * 登录名 21 | */ 22 | private String account; 23 | 24 | /** 25 | * 密码 26 | */ 27 | private String password; 28 | 29 | /** 30 | * 注册时间 31 | */ 32 | private Date registerDate; 33 | 34 | /** 35 | * 最后登录IP 36 | */ 37 | private String lastLoginIp; 38 | 39 | /** 40 | * 问题 41 | */ 42 | private String question; 43 | 44 | /** 45 | * 答案 46 | */ 47 | private String answer; 48 | } 49 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/Emoticon.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | import lombok.Data; 5 | import org.bson.codecs.pojo.annotations.BsonId; 6 | 7 | @Data 8 | public class Emoticon { 9 | 10 | @BsonId 11 | @JSONField(name = "_id") 12 | private String id; 13 | 14 | /** 15 | * 文件URL 16 | */ 17 | private String url; 18 | 19 | /** 20 | * 文件大小 21 | */ 22 | private Long size; 23 | 24 | /** 25 | * 文件名 26 | */ 27 | private String name; 28 | 29 | /** 30 | * 文件类型 31 | */ 32 | private String type; 33 | 34 | /** 35 | * 是否私有 36 | */ 37 | private Boolean isPrivate; 38 | 39 | private Long index; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/FileInfo.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import lombok.Data; 4 | import org.bson.codecs.pojo.annotations.BsonId; 5 | 6 | @Data 7 | public class FileInfo { 8 | 9 | /** 10 | * 主键 11 | */ 12 | @BsonId 13 | private String id; 14 | 15 | /** 16 | * 文件MD5 17 | */ 18 | private String md5; 19 | 20 | /** 21 | * 文件路径 22 | */ 23 | private String url; 24 | 25 | /** 26 | * 文件大小 27 | */ 28 | private Long size; 29 | 30 | /** 31 | * 文件名 32 | */ 33 | private String name; 34 | 35 | /** 36 | * 文件类型 37 | */ 38 | private String type; 39 | 40 | public static final String COL_MD5 = "md5"; 41 | public static final String COL_SIZE = "size"; 42 | public static final String COL_NAME = "name"; 43 | public static final String COL_TYPE = "type"; 44 | } 45 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/FriendInfo.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 好友信息 7 | */ 8 | @Data 9 | public class FriendInfo { 10 | 11 | /** 12 | * 房间号 13 | */ 14 | private String roomId; 15 | 16 | /** 17 | * 自己的Id 18 | */ 19 | private String self; 20 | 21 | /** 22 | * 好友ID 23 | */ 24 | private String friendId; 25 | 26 | /** 27 | * 好友备注 28 | */ 29 | private String remark; 30 | } 31 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/Group.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.bson.codecs.pojo.annotations.BsonId; 9 | import org.bson.codecs.pojo.annotations.BsonIgnore; 10 | import org.example.packets.LastMessage; 11 | 12 | import java.io.Serializable; 13 | import java.util.List; 14 | 15 | @Data 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @Builder 19 | public class Group implements Serializable { 20 | 21 | /** 22 | * 群组ID 23 | */ 24 | @BsonId 25 | private String roomId; 26 | 27 | /** 28 | * 排序 29 | */ 30 | private long index; 31 | 32 | /** 33 | * 是否好友分组 34 | */ 35 | private Boolean isFriend; 36 | 37 | /** 38 | * 群组名称 39 | */ 40 | private String roomName; 41 | 42 | /** 43 | * 头像 44 | */ 45 | private String avatar; 46 | 47 | /** 48 | * 是否系统会话 49 | */ 50 | private Boolean isSystem; 51 | 52 | /** 53 | * 好友ID 54 | */ 55 | @BsonIgnore 56 | private String friendId; 57 | 58 | /** 59 | * 最后一条消息 60 | */ 61 | private LastMessage lastMessage; 62 | 63 | /** 64 | * 是否删除 65 | */ 66 | private Boolean isDeleted; 67 | 68 | /** 69 | * 是否打开通知 70 | */ 71 | private Boolean notice; 72 | 73 | /** 74 | * 组用户 75 | */ 76 | private List users; 77 | 78 | /** 79 | * 是否公开群组 80 | */ 81 | private Boolean publicRoom; 82 | 83 | public Group clone(){ 84 | return BeanUtil.copyProperties(this,Group.class,"users"); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/Message.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | import lombok.Data; 5 | import org.bson.codecs.pojo.annotations.BsonId; 6 | import org.example.packets.ReplyMessage; 7 | import org.example.packets.handler.message.FileMessageBody; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | @Data 13 | public class Message { 14 | 15 | /** 16 | * 消息ID 17 | */ 18 | @BsonId 19 | @JSONField(name = "_id") 20 | private String id; 21 | 22 | /** 23 | * 消息内容 24 | */ 25 | private String content; 26 | 27 | /** 28 | * 未读消息数量 29 | */ 30 | private Integer unreadCount; 31 | 32 | /** 33 | * 发送者ID 34 | */ 35 | private String senderId; 36 | 37 | /** 38 | * 当前用户ID 39 | */ 40 | private String currentUserId; 41 | 42 | /** 43 | * 房间ID 44 | */ 45 | private String roomId; 46 | 47 | /** 48 | * 年月日 49 | */ 50 | private String date; 51 | 52 | /** 53 | * 00:00 54 | */ 55 | private String timestamp; 56 | 57 | /** 58 | * 是否系统消息 59 | */ 60 | private Boolean system; 61 | 62 | /** 63 | * 是否已保存 64 | */ 65 | private Boolean saved; 66 | 67 | /** 68 | * 已分发 69 | */ 70 | private Boolean distributed; 71 | 72 | /** 73 | * 已读 74 | */ 75 | private Boolean seen; 76 | 77 | /** 78 | * 是否删除的消息 79 | */ 80 | private Boolean deleted; 81 | 82 | /** 83 | * 发送消息 84 | */ 85 | private Boolean disableActions; 86 | 87 | /** 88 | * 发送消息 89 | */ 90 | private Boolean disableReactions; 91 | 92 | /** 93 | * 回复消息 94 | */ 95 | private ReplyMessage replyMessage; 96 | 97 | /** 98 | * 文件消息 99 | */ 100 | private List files; 101 | 102 | /** 103 | * 表情回复消息 104 | */ 105 | private Map> reactions; 106 | 107 | 108 | private Long sendTime; 109 | 110 | 111 | public static final String COL_SEND_TIME = "sendTime"; 112 | } 113 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/UnReadMessage.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class UnReadMessage { 11 | 12 | private String messageId; 13 | 14 | private String roomId; 15 | 16 | private String userId; 17 | 18 | private Long sendTime; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/User.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import com.alibaba.fastjson.annotation.JSONField; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.bson.codecs.pojo.annotations.BsonId; 10 | import org.example.enums.RoomRoleEnum; 11 | import org.example.packets.Status; 12 | 13 | import java.io.Serializable; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | @Builder 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class User implements Serializable { 22 | 23 | /** 24 | * 主键 25 | */ 26 | @BsonId 27 | @JSONField(name = "_id") 28 | private String id; 29 | 30 | /** 31 | * 用户名/ 昵称 32 | */ 33 | private String username; 34 | 35 | /** 36 | * 账户 37 | */ 38 | private String account; 39 | 40 | /** 41 | * 头像 42 | */ 43 | private String avatar; 44 | 45 | /** 46 | * 用户状态 47 | */ 48 | private Status status; 49 | 50 | /** 51 | * 是否系统账号 52 | */ 53 | private Boolean isSystem; 54 | 55 | /** 56 | * 群组身份 57 | */ 58 | private RoomRoleEnum role; 59 | 60 | /** 61 | * 用户群组列表 62 | */ 63 | private List groups; 64 | 65 | /** 66 | * 用户会话列表 67 | */ 68 | private List chats; 69 | 70 | public void addGroup(Group group) { 71 | if (groups == null) { 72 | groups = new ArrayList<>(); 73 | } 74 | groups.add(group); 75 | } 76 | 77 | public User clone() { 78 | return BeanUtil.copyProperties(this, User.class, "groups", "chats"); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/UserEmoticon.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | import lombok.Data; 5 | import org.bson.codecs.pojo.annotations.BsonId; 6 | 7 | @Data 8 | public class UserEmoticon { 9 | 10 | @BsonId 11 | @JSONField(name = "_id") 12 | private String id; 13 | 14 | /** 15 | * 用户ID 16 | */ 17 | private String userId; 18 | 19 | /** 20 | * 表情包ID 21 | */ 22 | private String emoticonId; 23 | 24 | /** 25 | * 排序号 26 | */ 27 | private Long index; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/bean/UserGroup.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.bean; 2 | 3 | import lombok.Data; 4 | import org.example.enums.RoomRoleEnum; 5 | 6 | @Data 7 | public class UserGroup { 8 | 9 | /** 10 | * 用户iD 11 | */ 12 | private String userId; 13 | 14 | /** 15 | * 群组ID 16 | */ 17 | private String roomId; 18 | 19 | /** 20 | * 当前群组角色 21 | */ 22 | private RoomRoleEnum role; 23 | 24 | /** 25 | * 房间是否删除 26 | */ 27 | private Boolean roomDeleted; 28 | 29 | /** 30 | * 当前群组是否系统会话 31 | */ 32 | private Boolean isSystem; 33 | 34 | /** 35 | * 是否开启通知 36 | */ 37 | private Boolean notice; 38 | 39 | /** 40 | * 置顶 41 | */ 42 | private Boolean top; 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/file/FileInit.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.file; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class FileInit { 7 | 8 | /** 9 | * 路径 10 | */ 11 | private String path; 12 | 13 | /** 14 | * 文件名 15 | */ 16 | private String filename; 17 | 18 | private String contentType; 19 | 20 | private String md5; 21 | 22 | private Integer partCount; 23 | 24 | private Long size; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/file/FileMerge.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.file; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class FileMerge { 7 | 8 | /** 9 | * 上传ID 10 | */ 11 | private String uploadId; 12 | 13 | /** 14 | * 上传地址 15 | */ 16 | private String objectName; 17 | 18 | /** 19 | * md5 20 | */ 21 | private String md5; 22 | 23 | private Long size; 24 | 25 | private String name; 26 | 27 | private String type; 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/emoticon/EmoticonOperationReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.emoticon; 2 | 3 | import lombok.Data; 4 | import org.example.enums.EmoticonOperationTypeEnum; 5 | 6 | @Data 7 | public class EmoticonOperationReqBody { 8 | 9 | /** 10 | * 表情ID 11 | */ 12 | private String emoticonId; 13 | 14 | /** 15 | * 表情路径 16 | */ 17 | private String url; 18 | 19 | private Integer size; 20 | 21 | /** 22 | * 表情名称 23 | */ 24 | private String name; 25 | 26 | /** 27 | * 操作类型 28 | */ 29 | private EmoticonOperationTypeEnum type; 30 | } 31 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/emoticon/EmoticonOperationRespBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.emoticon; 2 | 3 | import lombok.Data; 4 | import org.example.enums.EmoticonOperationTypeEnum; 5 | import org.example.packets.bean.Emoticon; 6 | 7 | @Data 8 | public class EmoticonOperationRespBody { 9 | 10 | private EmoticonOperationTypeEnum type; 11 | 12 | private Emoticon emoticon; 13 | } 14 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/emoticon/EmoticonSearchReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.emoticon; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class EmoticonSearchReqBody { 7 | 8 | private String id; 9 | 10 | /** 11 | * 查询内容 12 | */ 13 | private String content; 14 | } 15 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/ChatReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | import org.example.packets.ReplyMessage; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class ChatReqBody { 10 | 11 | /** 12 | * 消息ID 13 | */ 14 | private String _id; 15 | 16 | /** 17 | * 发送者编号 18 | */ 19 | private String senderId; 20 | 21 | /** 22 | * 房间号 23 | */ 24 | private String roomId; 25 | 26 | /** 27 | * 内容 28 | */ 29 | private String content; 30 | 31 | /** 32 | * 发送日期 33 | */ 34 | private String date; 35 | 36 | /** 37 | * 是否系统消息 38 | */ 39 | private Boolean system; 40 | 41 | /** 42 | * 发送时间 43 | */ 44 | private String timestamp; 45 | 46 | /** 47 | * 文件信息 48 | */ 49 | private List files; 50 | 51 | /** 52 | * 回复消息 53 | */ 54 | private ReplyMessage replyMessage; 55 | 56 | public static ChatReqBody buildSystem(String roomId, String senderId, String content) { 57 | ChatReqBody chatReqBody = new ChatReqBody(); 58 | chatReqBody.setRoomId(roomId); 59 | chatReqBody.setSenderId(senderId); 60 | chatReqBody.setContent(content); 61 | chatReqBody.setSystem(true); 62 | return chatReqBody; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/ChatRespBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.example.packets.ReplyMessage; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | @Builder 14 | @Data 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class ChatRespBody { 18 | 19 | /** 20 | * 消息ID 21 | */ 22 | @JSONField(name = "_id") 23 | private String id; 24 | 25 | 26 | /** 27 | * 消息内容 28 | */ 29 | private String content; 30 | 31 | /** 32 | * 未读消息数量 33 | */ 34 | private Integer unreadCount; 35 | 36 | /** 37 | * 发送者ID 38 | */ 39 | private String senderId; 40 | 41 | /** 42 | * 当前用户ID 43 | */ 44 | private String currentUserId; 45 | 46 | /** 47 | * 房间ID 48 | */ 49 | private String roomId; 50 | 51 | /** 52 | * 用户名 53 | */ 54 | private String username; 55 | 56 | /** 57 | * 用户头像 58 | */ 59 | private String avatar; 60 | 61 | /** 62 | * 年月日 63 | */ 64 | private String date; 65 | 66 | /** 67 | * 00:00 68 | */ 69 | private String timestamp; 70 | 71 | /** 72 | * 是否系统消息 73 | */ 74 | private Boolean system; 75 | 76 | /** 77 | * 是否已保存 78 | */ 79 | private Boolean saved; 80 | 81 | /** 82 | * 已分发 83 | */ 84 | private Boolean distributed; 85 | 86 | /** 87 | * 已读 88 | */ 89 | private Boolean seen; 90 | 91 | /** 92 | * 是否删除的消息 93 | */ 94 | private Boolean deleted; 95 | 96 | /** 97 | * 发送消息 98 | */ 99 | private Boolean disableActions; 100 | 101 | /** 102 | * 发送消息 103 | */ 104 | private Boolean disableReactions; 105 | 106 | /** 107 | * 回复消息 108 | */ 109 | private ReplyMessage replyMessage; 110 | 111 | private List files; 112 | 113 | /** 114 | * 表情回复消息 115 | */ 116 | private Map> reactions; 117 | 118 | private Long sendTime; 119 | 120 | } 121 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/FileMessageBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class FileMessageBody { 8 | 9 | @JSONField(name = "_id") 10 | private String id; 11 | 12 | /** 13 | * 文件名 14 | */ 15 | private String name; 16 | 17 | /** 18 | * 文件大小 19 | */ 20 | private long size; 21 | 22 | /** 23 | * 文件类型 24 | */ 25 | private String type; 26 | 27 | /** 28 | * 文件Url 29 | */ 30 | private String url; 31 | 32 | /** 33 | * 是不是表情包 34 | */ 35 | private Boolean isEmoticon; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/MessageDeleteReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class MessageDeleteReqBody { 7 | 8 | private String messageId; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/MessageDeleteRespBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import org.example.packets.bean.Message; 6 | 7 | @EqualsAndHashCode(callSuper = true) 8 | @Data 9 | public class MessageDeleteRespBody extends Message { 10 | 11 | /** 12 | * 是否最后一条消息, 决定是否要构建消息体 13 | */ 14 | private Boolean isLastMessage; 15 | 16 | private String deleteUserName; 17 | } 18 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/MessageForwardReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | import org.example.packets.bean.Message; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class MessageForwardReqBody { 10 | 11 | /** 12 | * 会话列表 13 | */ 14 | private List chats; 15 | 16 | /** 17 | * 用户列表 18 | */ 19 | private List users; 20 | 21 | /** 22 | * 消息列表 23 | */ 24 | private List messages; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/MessageReactionReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 表情回复消息 7 | */ 8 | @Data 9 | public class MessageReactionReqBody { 10 | 11 | /** 12 | * 表情unicode 13 | */ 14 | private String reaction; 15 | 16 | /** 17 | * 是否删除 18 | */ 19 | private Boolean remove; 20 | 21 | /** 22 | * 消息ID 23 | */ 24 | private String messageId; 25 | 26 | /** 27 | * 房间号 28 | */ 29 | private String roomId; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/MessageReactionRespBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | @Data 9 | public class MessageReactionRespBody { 10 | 11 | /** 12 | * 消息ID 13 | */ 14 | private String messageId; 15 | 16 | /** 17 | * 房间号 18 | */ 19 | private String roomId; 20 | 21 | /** 22 | * 表情回复 23 | */ 24 | private Map> reactions; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/MessageReadReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class MessageReadReqBody { 7 | 8 | private String roomId; 9 | 10 | private String messageId; 11 | } 12 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/MessageReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | import org.example.enums.MessageFetchTypeEnum; 5 | 6 | @Data 7 | public class MessageReqBody { 8 | 9 | /** 10 | * 群组id; 11 | */ 12 | private String roomId; 13 | 14 | /** 15 | * 请求消息类型 16 | */ 17 | private MessageFetchTypeEnum type; 18 | /** 19 | * 起始消息ID 20 | */ 21 | private String messageId; 22 | 23 | private Boolean returnDefault; 24 | } 25 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/MessageRespBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | import org.example.enums.MessageFetchTypeEnum; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class MessageRespBody { 10 | 11 | /** 12 | * 消息ID 13 | */ 14 | private MessageFetchTypeEnum type; 15 | 16 | /** 17 | * 回归正常的第一次请求 18 | */ 19 | private Boolean returnDefault; 20 | 21 | /** 22 | * 消息列表 23 | */ 24 | private List messages; 25 | } 26 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/message/MessageSearchReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.message; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class MessageSearchReqBody { 7 | 8 | /** 9 | * 群组id; 10 | */ 11 | private String roomId; 12 | /** 13 | * 查询内容 14 | */ 15 | private String content; 16 | /** 17 | * 消息开始时间; 18 | */ 19 | private String startDate; 20 | /** 21 | * 消息结束时间 22 | */ 23 | private String endDate; 24 | } 25 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/CreateGroupReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.Data; 4 | import org.example.packets.bean.User; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class CreateGroupReqBody { 10 | 11 | /** 12 | * 群组名称 13 | */ 14 | private String roomName; 15 | 16 | /** 17 | * 是否好友会话 18 | */ 19 | private Boolean isFriend; 20 | 21 | /** 22 | * 头像 (群组设置) 23 | */ 24 | private String avatar; 25 | 26 | /** 27 | * 群组创建时携带的人 28 | */ 29 | private List users; 30 | 31 | /** 32 | * 是否公开群组 33 | */ 34 | private Boolean publicRoom; 35 | } 36 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/GroupAdminReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.Data; 4 | import org.example.enums.GroupAdminTypeEnum; 5 | import org.example.enums.GroupOutTypeEnum; 6 | 7 | @Data 8 | public class GroupAdminReqBody { 9 | 10 | /** 11 | * 用户ID 12 | */ 13 | private String userId; 14 | 15 | /** 16 | * 群组ID 17 | */ 18 | private String roomId; 19 | 20 | /** 21 | * 移除类型: 设置\解除 22 | */ 23 | private GroupAdminTypeEnum type; 24 | } 25 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/GroupProfileReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class GroupProfileReqBody { 7 | 8 | private String roomId; 9 | 10 | private String roomName; 11 | 12 | private String avatar; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/GroupUserReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.Data; 4 | import org.example.enums.GroupOutTypeEnum; 5 | 6 | @Data 7 | public class GroupUserReqBody { 8 | 9 | /** 10 | * 用户ID 11 | */ 12 | private String userId; 13 | 14 | /** 15 | * 群组ID 16 | */ 17 | private String roomId; 18 | 19 | /** 20 | * 移除类型: 主动退出\踢出群组 21 | */ 22 | private GroupOutTypeEnum type; 23 | } 24 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/HandoverGroupRespBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.example.packets.bean.User; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Data 11 | public class HandoverGroupRespBody { 12 | 13 | private String roomId; 14 | 15 | private String oldAdmin; 16 | 17 | private String newAdmin; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/JoinGroupNotifyBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.example.packets.bean.Group; 8 | import org.example.packets.bean.User; 9 | 10 | import java.util.List; 11 | 12 | @Builder 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class JoinGroupNotifyBody { 17 | 18 | /** 19 | * 群组ID 20 | */ 21 | private Group group; 22 | 23 | /** 24 | * 加入的用户 25 | */ 26 | private List users; 27 | 28 | /** 29 | * 加入状态 30 | */ 31 | private int code; 32 | 33 | /** 34 | * 相关消息: 区分为创建成功, 加入群聊 退出群聊 移出群聊 35 | */ 36 | private String message; 37 | 38 | /** 39 | * 拉人进群时, 其他用户将会在这个群组中体现, 主要是避免发消息的问题 40 | */ 41 | private List otherUsers; 42 | } 43 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/SearchRoomReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class SearchRoomReqBody { 7 | 8 | /** 9 | * 用户名称 10 | */ 11 | private String name; 12 | 13 | /** 14 | * 搜索ID 15 | */ 16 | private String searchId; 17 | 18 | /** 19 | * 最后一个用户的ID 20 | */ 21 | private String roomId; 22 | } 23 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/SearchRoomRespBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.Data; 4 | import org.example.packets.bean.Group; 5 | import org.example.packets.bean.User; 6 | 7 | import java.util.List; 8 | 9 | @Data 10 | public class SearchRoomRespBody { 11 | 12 | private String searchId; 13 | 14 | private List roomList; 15 | } 16 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/SetPublicRoomReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class SetPublicRoomReqBody { 7 | 8 | private String roomId; 9 | 10 | private Boolean publicRoom; 11 | } 12 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/room/UserGroupConfigReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.room; 2 | 3 | import lombok.Data; 4 | import org.example.enums.UserGroupConfigTypeEnum; 5 | 6 | /** 7 | * 群组用户系统配置 8 | * 9 | * @author smart 10 | * @since 1.0.0 11 | */ 12 | @Data 13 | public class UserGroupConfigReqBody { 14 | 15 | /** 16 | * 群组ID 17 | */ 18 | private String roomId; 19 | 20 | /** 21 | * 打开或关闭该群组通知 22 | */ 23 | private Boolean notice; 24 | 25 | /** 26 | * 置顶或取消置顶 27 | */ 28 | private Boolean moveTop; 29 | 30 | /** 31 | * 置顶或取消置顶返回的排序号 32 | */ 33 | private Long index; 34 | 35 | /** 36 | * 配置类型 37 | */ 38 | private UserGroupConfigTypeEnum type; 39 | } 40 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/system/HeartbeatBody.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.example.packets.handler.system; 5 | 6 | import lombok.Data; 7 | 8 | /** 9 | * @author WChao 10 | * 11 | */ 12 | @Data 13 | public class HeartbeatBody { 14 | 15 | private static final long serialVersionUID = -1773817279179288833L; 16 | 17 | private byte hbbyte; 18 | 19 | public HeartbeatBody(){} 20 | public HeartbeatBody(byte hbbyte){ 21 | this.hbbyte = hbbyte; 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/system/LoginReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.system; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class LoginReqBody { 11 | 12 | private String account; 13 | 14 | private String password; 15 | } 16 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/system/LoginRespBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.system; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Builder 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class LoginRespBody { 13 | 14 | private String _id; 15 | } 16 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/system/RegisterReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.system; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class RegisterReqBody { 7 | 8 | /** 9 | * 账号 10 | */ 11 | private String account; 12 | 13 | /** 14 | * 用户名 15 | */ 16 | private String username; 17 | 18 | /** 19 | * 密码 20 | */ 21 | private String password; 22 | 23 | /** 24 | * 重复密码 25 | */ 26 | private String repeatPassword; 27 | 28 | /** 29 | * 问题 30 | */ 31 | private String question; 32 | 33 | /** 34 | * 答案 35 | */ 36 | private String answer; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/system/RespBody.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.example.packets.handler.system; 5 | 6 | import com.alibaba.fastjson.JSON; 7 | import com.alibaba.fastjson.serializer.SerializerFeature; 8 | import lombok.Data; 9 | import org.example.enums.CommandEnum; 10 | 11 | import java.io.Serializable; 12 | 13 | /** 14 | * 版本: [1.0] 15 | * 功能说明: 16 | * 作者: WChao 创建时间: 2017年7月26日 上午11:31:48 17 | */ 18 | @Data 19 | public class RespBody implements Serializable { 20 | 21 | protected static final long serialVersionUID = 1L; 22 | 23 | /** 24 | * 响应状态码; 25 | */ 26 | protected Boolean success; 27 | /** 28 | * 响应状态信息提示; 29 | */ 30 | protected String msg; 31 | /** 32 | * 响应cmd命令码; 33 | */ 34 | protected int command; 35 | /** 36 | * 响应数据; 37 | */ 38 | protected Object data; 39 | 40 | protected int page; 41 | 42 | protected int count; 43 | 44 | public RespBody(CommandEnum command) { 45 | this.command = command.getValue(); 46 | } 47 | 48 | 49 | public static String success(CommandEnum command) { 50 | return success(command, null,"操作成功", 0, 0); 51 | } 52 | 53 | public static String success(CommandEnum command, Object data) { 54 | return success(command, data,"操作成功", 0, 0); 55 | } 56 | public static String success(CommandEnum command, Object data, String msg) { 57 | return success(command, data,msg, 0, 0); 58 | } 59 | 60 | public static String successPage(CommandEnum command, Object data, int page, int count) { 61 | return success(command, data,"操作成功", page, count); 62 | } 63 | 64 | public static String success(CommandEnum command, Object data,String msg, int page, int count) { 65 | RespBody respBody = new RespBody(command); 66 | respBody.setData(data); 67 | respBody.setSuccess(true); 68 | respBody.setMsg(msg); 69 | respBody.setPage(page); 70 | respBody.setCount(count); 71 | 72 | return JSON.toJSONString(respBody, SerializerFeature.DisableCircularReferenceDetect); 73 | } 74 | 75 | public static String fail(CommandEnum command, String msg) { 76 | RespBody respBody = new RespBody(command); 77 | respBody.setData(null); 78 | respBody.setSuccess(false); 79 | respBody.setMsg(msg); 80 | return JSON.toJSONString(respBody, SerializerFeature.DisableCircularReferenceDetect); 81 | } 82 | 83 | public RespBody setData(Object data) { 84 | this.data = data; 85 | return this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/system/SetNewPasswordReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.system; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class SetNewPasswordReqBody { 7 | 8 | private String oldPassword; 9 | 10 | private String password; 11 | 12 | private String repeatPassword; 13 | } 14 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/system/SystemMessageReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.system; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.example.enums.SystemMessageTypeEnum; 7 | 8 | import java.util.List; 9 | 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Data 13 | public class SystemMessageReqBody { 14 | 15 | private SystemMessageTypeEnum type; 16 | 17 | private String senderId; 18 | 19 | /** 20 | * 接收人列表 21 | */ 22 | private List receivers; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/system/SystemTextMessage.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.system; 2 | 3 | import cn.hutool.core.date.DateUtil; 4 | import cn.hutool.core.util.IdUtil; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.example.enums.SystemMessageTypeEnum; 9 | import org.example.packets.bean.Message; 10 | 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | @NoArgsConstructor 15 | @Data 16 | @AllArgsConstructor 17 | public class SystemTextMessage extends SystemMessageReqBody { 18 | 19 | private String content; 20 | 21 | public static SystemTextMessage create(String content, List receivers) { 22 | 23 | SystemTextMessage systemTextMessage = new SystemTextMessage(); 24 | systemTextMessage.setContent(content); 25 | 26 | systemTextMessage.setType(SystemMessageTypeEnum.TEXT); 27 | systemTextMessage.setReceivers(receivers); 28 | systemTextMessage.setSenderId("SYSTEM"); 29 | return systemTextMessage; 30 | } 31 | 32 | public Message build(String senderId) { 33 | Message message = new Message(); 34 | message.setId(IdUtil.getSnowflake().nextIdStr()); 35 | Date date = new Date(); 36 | message.setDate(DateUtil.formatDate(date)); 37 | message.setTimestamp(DateUtil.formatTime(date)); 38 | message.setSenderId(senderId); 39 | message.setSystem(false); 40 | message.setDeleted(false); 41 | message.setSaved(true); 42 | message.setDistributed(true); 43 | message.setSeen(false); 44 | message.setSendTime(System.currentTimeMillis()); 45 | message.setContent(this.content); 46 | return message; 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/user/EditProfileReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.user; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class EditProfileReqBody { 7 | 8 | /** 9 | * 用户ID 10 | */ 11 | private String userId; 12 | 13 | /** 14 | * 头像路径 15 | */ 16 | private String avatar; 17 | 18 | /** 19 | * 修改后的名称 20 | */ 21 | private String name; 22 | } 23 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/user/SearchUserReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.user; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class SearchUserReqBody { 7 | 8 | /** 9 | * 用户名称 10 | */ 11 | private String name; 12 | 13 | /** 14 | * 搜索ID 15 | */ 16 | private String searchId; 17 | 18 | /** 19 | * 最后一个用户的ID 20 | */ 21 | private String userId; 22 | } 23 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/user/SearchUserRespBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.user; 2 | 3 | import lombok.Data; 4 | import org.example.packets.bean.User; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class SearchUserRespBody { 10 | 11 | private String searchId; 12 | 13 | private List userList; 14 | } 15 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/user/UserReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.user; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class UserReqBody { 7 | 8 | /** 9 | * 用户id; 10 | */ 11 | private String userId; 12 | /** 13 | * 0:单个,1:所有在线用户,2:所有用户(在线+离线); 14 | */ 15 | private Integer type; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/user/UserStatusBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.user; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.example.packets.bean.Group; 8 | import org.example.packets.bean.User; 9 | 10 | @Builder 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Data 14 | public class UserStatusBody { 15 | 16 | /** 17 | * 状态变化的用户 18 | */ 19 | private User user; 20 | 21 | private Group group; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/packets/handler/video/VideoReqBody.java: -------------------------------------------------------------------------------- 1 | package org.example.packets.handler.video; 2 | 3 | import lombok.Data; 4 | import org.example.enums.VideoCommandEnum; 5 | 6 | /** 7 | * 音视频通话请求 8 | */ 9 | @Data 10 | public class VideoReqBody { 11 | 12 | /** 13 | * 用户ID 14 | */ 15 | private String userId; 16 | 17 | /** 18 | * 来源ID 19 | */ 20 | private String fromId; 21 | 22 | /** 23 | * 房间ID 24 | */ 25 | private String roomId; 26 | 27 | /** 28 | * 执行命令 29 | */ 30 | private VideoCommandEnum command; 31 | 32 | /** 33 | * VIDEO / AUDIO 音频/视频 34 | */ 35 | private String type; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/AuthService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import cn.hutool.core.util.IdUtil; 4 | import cn.hutool.crypto.SecureUtil; 5 | import com.mongodb.client.model.Filters; 6 | import org.example.dao.AuthRepository; 7 | import org.example.packets.bean.Auth; 8 | import org.example.packets.handler.system.RegisterReqBody; 9 | 10 | import java.util.Date; 11 | 12 | public class AuthService { 13 | 14 | private final AuthRepository authRepository; 15 | 16 | public AuthService() { 17 | authRepository = new AuthRepository(); 18 | } 19 | 20 | public Auth getByUserId(String userId) { 21 | return authRepository.findOne(Filters.eq("userId",userId)); 22 | } 23 | 24 | public Auth getByAccount(String account) { 25 | return authRepository.findOne(Filters.eq("account",account)); 26 | } 27 | 28 | public Auth createAccount(RegisterReqBody reqBody, String userId) { 29 | Auth auth = new Auth(); 30 | auth.setId(IdUtil.getSnowflake().nextIdStr()); 31 | auth.setPassword(SecureUtil.md5(reqBody.getPassword())); 32 | auth.setAccount(reqBody.getAccount()); 33 | auth.setRegisterDate(new Date()); 34 | auth.setUserId(userId); 35 | auth.setQuestion(reqBody.getQuestion()); 36 | auth.setAnswer(reqBody.getAnswer()); 37 | authRepository.insert(auth); 38 | return auth; 39 | } 40 | 41 | public void update(Auth auth) { 42 | authRepository.updateById(auth); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/EmoticonService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import cn.hutool.core.io.file.FileNameUtil; 4 | import cn.hutool.core.util.IdUtil; 5 | import cn.hutool.core.util.StrUtil; 6 | import org.bson.conversions.Bson; 7 | import org.example.config.CourierConfig; 8 | import org.example.dao.EmoticonRepository; 9 | import org.example.packets.bean.Emoticon; 10 | 11 | import java.util.List; 12 | import java.util.regex.Pattern; 13 | 14 | import static com.mongodb.client.model.Filters.*; 15 | import static com.mongodb.client.model.Filters.eq; 16 | 17 | public class EmoticonService { 18 | 19 | private final EmoticonRepository emoticonRepository; 20 | 21 | public EmoticonService() { 22 | emoticonRepository = new EmoticonRepository(); 23 | } 24 | 25 | public List getEmoticons(String id, String content) { 26 | Emoticon emoticon = getEmoticon(id); 27 | Pattern pattern = Pattern.compile("^.*" + content + ".*$", Pattern.CASE_INSENSITIVE); 28 | Bson filter = and(eq("isPrivate", false)); 29 | if (StrUtil.isNotBlank(id) && StrUtil.isNotBlank(content)) { 30 | filter = and(eq("isPrivate", false), lt("index", emoticon.getIndex()), regex("name", pattern)); 31 | } 32 | 33 | if(StrUtil.isNotBlank(id) && StrUtil.isBlank(content)) { 34 | filter = and(eq("isPrivate", false), lt("index", emoticon.getIndex())); 35 | } 36 | 37 | if(StrUtil.isBlank(id) && StrUtil.isNotBlank(content)) { 38 | filter = and(eq("isPrivate", false), regex("name", pattern)); 39 | } 40 | 41 | return emoticonRepository.findSortLimit(filter, eq("index", -1), 20); 42 | } 43 | 44 | public Emoticon getEmoticon(String emoticonId) { 45 | return emoticonRepository.findById(emoticonId); 46 | } 47 | 48 | public String insert(String name, int size, String url, Boolean isPrivate) { 49 | if(!url.startsWith("http://") && !url.startsWith("https://")){ 50 | url = CourierConfig.fileUrl + url; 51 | } 52 | 53 | Emoticon emoticon = new Emoticon(); 54 | emoticon.setId(IdUtil.getSnowflakeNextIdStr()); 55 | emoticon.setName(name); 56 | emoticon.setSize((long) size); 57 | emoticon.setIsPrivate(isPrivate); 58 | emoticon.setType(FileNameUtil.getSuffix(name)); 59 | emoticon.setUrl(url); 60 | emoticon.setIndex(System.currentTimeMillis()); 61 | 62 | emoticonRepository.insert(emoticon); 63 | return emoticon.getId(); 64 | } 65 | 66 | public void update(Emoticon emoticon) { 67 | emoticonRepository.updateById(emoticon); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/FileService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import cn.hutool.core.io.file.FileNameUtil; 4 | import cn.hutool.core.util.IdUtil; 5 | import org.example.dao.FileRepository; 6 | import org.example.packets.bean.FileInfo; 7 | 8 | import static com.mongodb.client.model.Filters.and; 9 | import static com.mongodb.client.model.Filters.eq; 10 | 11 | public class FileService { 12 | 13 | private final FileRepository fileRepository; 14 | 15 | public FileService() { 16 | fileRepository = new FileRepository(); 17 | } 18 | 19 | public String getFileUrl(String md5, Long size, String type) { 20 | FileInfo fileInfo = fileRepository.findOne(and(eq(FileInfo.COL_MD5, md5), eq(FileInfo.COL_SIZE, size), eq(FileInfo.COL_TYPE, type))); 21 | return fileInfo == null ? null : fileInfo.getUrl(); 22 | } 23 | 24 | public String getFileUrl(String md5) { 25 | FileInfo fileInfo = fileRepository.findOne(eq(FileInfo.COL_MD5, md5)); 26 | return fileInfo == null ? null : fileInfo.getUrl(); 27 | } 28 | 29 | public void setFileUrl(String md5, String objectName, String name, Long size) { 30 | FileInfo fileInfo = new FileInfo(); 31 | fileInfo.setId(IdUtil.getSnowflake().nextIdStr()); 32 | fileInfo.setMd5(md5); 33 | fileInfo.setUrl(objectName); 34 | fileInfo.setName(name); 35 | fileInfo.setSize(size); 36 | fileInfo.setType(FileNameUtil.getSuffix(name)); 37 | fileRepository.saveOrUpdateById(fileInfo); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/FriendInfoService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import org.example.dao.FriendInfoRepository; 4 | import org.example.packets.bean.FriendInfo; 5 | 6 | import static com.mongodb.client.model.Filters.and; 7 | import static com.mongodb.client.model.Filters.eq; 8 | 9 | public class FriendInfoService { 10 | 11 | private final FriendInfoRepository friendInfoRepository; 12 | 13 | public FriendInfoService() { 14 | this.friendInfoRepository = new FriendInfoRepository(); 15 | } 16 | 17 | /** 18 | * 创建双向好友 19 | * @param roomId 房间号 20 | * @param self 自己 21 | * @param friendId 好友ID 22 | */ 23 | public void createFriendTwoWay(String roomId, String self, String friendId) { 24 | FriendInfo friendInfo = new FriendInfo(); 25 | friendInfo.setRoomId(roomId); 26 | friendInfo.setRemark(""); 27 | friendInfo.setSelf(self); 28 | friendInfo.setFriendId(friendId); 29 | friendInfoRepository.insert(friendInfo); 30 | 31 | friendInfo.setSelf(friendId); 32 | friendInfo.setFriendId(self); 33 | friendInfoRepository.insert(friendInfo); 34 | } 35 | 36 | public FriendInfo getFriendInfo(String roomId, String userId) { 37 | return friendInfoRepository.findOne(and(eq("roomId", roomId), eq("self", userId))); 38 | } 39 | 40 | public FriendInfo getRoomInfo(String selfId, String userId) { 41 | return friendInfoRepository.findOne(and(eq("friendId", userId), eq("self", selfId))); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/GroupService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.collection.CollUtil; 5 | import cn.hutool.core.util.StrUtil; 6 | import org.bson.conversions.Bson; 7 | import org.example.dao.GroupRepository; 8 | import org.example.packets.LastMessage; 9 | import org.example.packets.bean.Group; 10 | import org.example.packets.bean.Message; 11 | 12 | import java.util.List; 13 | import java.util.regex.Pattern; 14 | 15 | import static com.mongodb.client.model.Filters.*; 16 | 17 | public class GroupService { 18 | private final GroupRepository groupRepository; 19 | 20 | public GroupService() { 21 | groupRepository = new GroupRepository(); 22 | } 23 | 24 | public void insertOne(Group group) { 25 | groupRepository.insert(group); 26 | } 27 | 28 | public void saveOrUpdate(Group build) { 29 | groupRepository.saveOrUpdate(eq("roomId", build.getRoomId()), build); 30 | } 31 | 32 | public void saveOrUpdateById(Group build) { 33 | groupRepository.saveOrUpdateById(build.clone()); 34 | } 35 | 36 | public Group getGroupInfo(String roomId) { 37 | return groupRepository.findById(roomId); 38 | } 39 | 40 | public void updateLastMessage(Message message) { 41 | Group group = groupRepository.findById(message.getRoomId()); 42 | 43 | LastMessage lastMessage = BeanUtil.copyProperties(message, LastMessage.class); 44 | lastMessage.setMessageId(message.getId()); 45 | if (message.getDeleted()) { 46 | lastMessage.setContent("删除了一条消息"); 47 | } 48 | lastMessage.setIndexId(message.getSendTime()); 49 | if (StrUtil.isBlank(message.getContent()) && CollUtil.isNotEmpty(message.getFiles()) && !message.getDeleted()) { 50 | if (message.getFiles().size() == 1 && Boolean.TRUE.equals(message.getFiles().get(0).getIsEmoticon())) { 51 | lastMessage.setContent("[表情包]"); 52 | } else if (message.getFiles().size() == 1 && !Boolean.TRUE.equals(message.getFiles().get(0).getIsEmoticon())) { 53 | lastMessage.setContent("[文件] - " + message.getFiles().get(0).getName()); 54 | } else { 55 | lastMessage.setContent("[文件] - " + message.getFiles().get(0).getName() + "等多个文件"); 56 | } 57 | } 58 | 59 | group.setIndex(System.currentTimeMillis()); 60 | group.setLastMessage(lastMessage); 61 | groupRepository.updateById(group); 62 | } 63 | 64 | public void updateById(Group userGroup) { 65 | groupRepository.updateById(userGroup.clone()); 66 | } 67 | 68 | public void delete(String roomId) { 69 | Group group = groupRepository.findById(roomId); 70 | group.setIsDeleted(true); 71 | groupRepository.updateById(group); 72 | } 73 | 74 | public void readLastMessage(Group groupInfo) { 75 | groupInfo.getLastMessage().setSeen(true); 76 | groupRepository.updateById(groupInfo); 77 | } 78 | 79 | public List getRoomList(String name, String roomId) { 80 | Bson filter = null; 81 | if (StrUtil.isNotBlank(name) && StrUtil.isNotBlank(roomId)) { 82 | Pattern pattern = Pattern.compile("^.*" + name + ".*$", Pattern.CASE_INSENSITIVE); 83 | filter = and(gte("_id", roomId), regex("roomName", pattern),eq("publicRoom",true),ne("isDeleted", true)); 84 | } 85 | if (StrUtil.isNotBlank(name) && StrUtil.isBlank(roomId)) { 86 | Pattern pattern = Pattern.compile("^.*" + name + ".*$", Pattern.CASE_INSENSITIVE); 87 | filter = and(regex("roomName", pattern),eq("publicRoom",true),ne("isDeleted", true)); 88 | } 89 | if (StrUtil.isBlank(name) && StrUtil.isNotBlank(roomId)) { 90 | filter = and(gte("_id", roomId),eq("publicRoom",true),ne("isDeleted", true)); 91 | } 92 | if(StrUtil.isBlank(name) && StrUtil.isBlank(roomId)){ 93 | filter = and(eq("publicRoom",true),ne("isDeleted", true)); 94 | } 95 | return groupRepository.findSortLimit(filter, eq("_id", 1), 20); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/MessageService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import cn.hutool.core.util.ObjectUtil; 5 | import org.bson.conversions.Bson; 6 | import org.example.dao.MessageRepository; 7 | import org.example.enums.MessageFetchTypeEnum; 8 | import org.example.packets.bean.Message; 9 | 10 | import java.util.*; 11 | import java.util.regex.Pattern; 12 | 13 | import static com.mongodb.client.model.Filters.*; 14 | 15 | public class MessageService { 16 | 17 | private final MessageRepository messageRepository; 18 | 19 | public MessageService() { 20 | messageRepository = new MessageRepository(); 21 | } 22 | 23 | public List getHistoryMessage(String roomId, String messageId, int asc) { 24 | Message message = messageId == null ? getLastMessage(roomId) : getMessage(messageId); 25 | if (asc == 1) { 26 | var a = message == null ? and(eq("roomId", roomId)) : and(eq("roomId", roomId), lte(Message.COL_SEND_TIME, message.getSendTime())); 27 | return messageRepository.findSortLimit(a, eq(Message.COL_SEND_TIME, -1), 20); 28 | } else { 29 | return messageRepository.findSortLimit(and(eq("roomId", roomId), gte(Message.COL_SEND_TIME, message.getSendTime())), eq(Message.COL_SEND_TIME, 1), 20); 30 | } 31 | } 32 | 33 | public List getHistoryMessage(String roomId, String messageId, MessageFetchTypeEnum type) { 34 | return this.getHistoryMessage(roomId, messageId, MessageFetchTypeEnum.DOWN.equals(type) ? -1 : 1); 35 | } 36 | 37 | public void addReaction(String messageId, String reaction, Boolean remove, String userId) { 38 | 39 | Message message = messageRepository.findById(messageId); 40 | 41 | Map> reactions = ObjectUtil.defaultIfNull(message.getReactions(), new HashMap<>()); 42 | 43 | List userIds = reactions.get(reaction); 44 | if (Boolean.TRUE.equals(remove)) { 45 | userIds.remove(userId); 46 | } else { 47 | if (CollUtil.isEmpty(userIds)) { 48 | reactions.put(reaction, CollUtil.newArrayList(userId)); 49 | } else { 50 | userIds.add(userId); 51 | } 52 | } 53 | message.setReactions(reactions); 54 | messageRepository.updateById(message); 55 | } 56 | 57 | public Map> getReaction(String messageId) { 58 | Message message = messageRepository.findById(messageId); 59 | return message.getReactions(); 60 | } 61 | 62 | 63 | public Message getMessage(String messageId) { 64 | return messageRepository.findById(messageId); 65 | } 66 | 67 | public void putGroupMessage(Message message) { 68 | messageRepository.insert(message); 69 | } 70 | 71 | public List getFileHistory(String roomId, String date) { 72 | return messageRepository.find(and(eq("roomId", roomId), eq("date", date), not(size("files", 0)))); 73 | } 74 | 75 | public Message getStartMessage(String roomId) { 76 | return messageRepository.findOneLimit(eq("roomId", roomId), eq(Message.COL_SEND_TIME, 1), 1); 77 | } 78 | 79 | public int getCount(String roomId) { 80 | return messageRepository.count(eq("roomId", roomId)); 81 | } 82 | 83 | public void update(Message message) { 84 | messageRepository.updateById(message); 85 | } 86 | 87 | public Message getLastMessage(String roomId) { 88 | return messageRepository.findOne(eq("roomId", roomId), eq(Message.COL_SEND_TIME, -1)); 89 | } 90 | 91 | public void read(String messageId) { 92 | Message message = messageRepository.findById(messageId); 93 | message.setSeen(true); 94 | messageRepository.updateById(message); 95 | } 96 | 97 | public List getMessage(String roomId, String content, Long startTime, Long endTime) { 98 | Pattern pattern = Pattern.compile("^.*" + content + ".*$", Pattern.CASE_INSENSITIVE); 99 | Bson filter = and(eq("roomId", roomId), regex("content", pattern), ne("deleted", true)); 100 | if (startTime != null && endTime != null) { 101 | filter = and(eq("roomId", roomId), regex("content", pattern), gte(Message.COL_SEND_TIME, startTime), lte(Message.COL_SEND_TIME, endTime), ne("deleted", true)); 102 | } 103 | return messageRepository.findSort(filter, eq(Message.COL_SEND_TIME, -1)); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/UnReadMessageService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import org.example.dao.UnReadMessageRepository; 4 | import org.example.packets.bean.UnReadMessage; 5 | 6 | import java.util.List; 7 | 8 | import static com.mongodb.client.model.Filters.and; 9 | import static com.mongodb.client.model.Filters.eq; 10 | 11 | public class UnReadMessageService { 12 | 13 | private final UnReadMessageRepository unReadMessageRepository; 14 | 15 | public UnReadMessageService() { 16 | unReadMessageRepository = new UnReadMessageRepository(); 17 | } 18 | 19 | 20 | public void putUnReadMessage(String userId, String roomId, String messageId, Long sendTime) { 21 | UnReadMessage unReadMessage = new UnReadMessage(messageId, roomId, userId,sendTime); 22 | unReadMessageRepository.insert(unReadMessage); 23 | } 24 | 25 | public List getUnReadMessage(String userId, String roomId) { 26 | return unReadMessageRepository.findSort(and(eq("userId", userId), eq("roomId", roomId)),eq("sendTime",1)); 27 | } 28 | 29 | public UnReadMessage getLastUnReadMessage(String userId, String roomId) { 30 | return unReadMessageRepository.findOne(and(eq("userId", userId), eq("roomId", roomId)), eq("_id", -1)); 31 | } 32 | 33 | public void clearUnReadMessage(String userId, String roomId) { 34 | unReadMessageRepository.delete(and(eq("userId", userId), eq("roomId", roomId))); 35 | } 36 | 37 | public List getMessageUnReads(String messageId) { 38 | return unReadMessageRepository.find(eq("messageId",messageId)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/UserEmoticonService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import cn.hutool.core.util.IdUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import org.bson.conversions.Bson; 6 | import org.example.dao.EmoticonRepository; 7 | import org.example.dao.UserEmoticonRepository; 8 | import org.example.packets.bean.Emoticon; 9 | import org.example.packets.bean.UserEmoticon; 10 | 11 | import java.util.List; 12 | 13 | import static com.mongodb.client.model.Filters.*; 14 | 15 | public class UserEmoticonService { 16 | 17 | private final UserEmoticonRepository userEmoticonRepository; 18 | private final EmoticonRepository emoticonRepository; 19 | 20 | public UserEmoticonService() { 21 | userEmoticonRepository = new UserEmoticonRepository(); 22 | emoticonRepository = new EmoticonRepository(); 23 | } 24 | 25 | 26 | public List getUserEmoticons(String emoticonId, String userId) { 27 | 28 | Bson filter = and( eq("userId", userId)); 29 | if(StrUtil.isNotBlank(emoticonId)){ 30 | UserEmoticon one = userEmoticonRepository.findOne(and(eq("emoticonId", emoticonId), eq("userId", userId))); 31 | filter = and(lt("index", one.getIndex()), eq("userId", userId)); 32 | } 33 | return userEmoticonRepository.findSort(filter, eq("index", -1)); 34 | } 35 | 36 | public void insert(String emoticonId, String userId) { 37 | 38 | UserEmoticon one = userEmoticonRepository.findOne(and(eq("emoticonId", emoticonId), eq("userId", userId))); 39 | if (one != null) { 40 | return; 41 | } 42 | 43 | UserEmoticon userEmoticon = new UserEmoticon(); 44 | userEmoticon.setId(IdUtil.getSnowflakeNextIdStr()); 45 | userEmoticon.setEmoticonId(emoticonId); 46 | userEmoticon.setUserId(userId); 47 | userEmoticon.setIndex(System.currentTimeMillis()); 48 | userEmoticonRepository.insert(userEmoticon); 49 | } 50 | 51 | public void delete(String emoticonId, String userId) { 52 | userEmoticonRepository.delete(and(eq("emoticonId", emoticonId), eq("userId", userId))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/UserGroupService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import com.mongodb.client.model.Field; 4 | import org.example.dao.GroupRepository; 5 | import org.example.dao.UserGroupRepository; 6 | import org.example.dao.UserRepository; 7 | import org.example.enums.RoomRoleEnum; 8 | import org.example.packets.LastMessage; 9 | import org.example.packets.bean.Group; 10 | import org.example.packets.bean.User; 11 | import org.example.packets.bean.UserGroup; 12 | 13 | import java.util.Comparator; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | import static com.mongodb.client.model.Aggregates.set; 18 | import static com.mongodb.client.model.Filters.*; 19 | import static com.mongodb.client.model.Updates.combine; 20 | 21 | public class UserGroupService { 22 | 23 | private final UserGroupRepository userGroupRepository; 24 | 25 | private final UserRepository userRepository; 26 | private final GroupRepository groupRepository; 27 | 28 | public UserGroupService() { 29 | userRepository = new UserRepository(); 30 | userGroupRepository = new UserGroupRepository(); 31 | groupRepository = new GroupRepository(); 32 | } 33 | 34 | public void addGroupUser(String roomId, String userId) { 35 | addGroupUser(roomId, userId, RoomRoleEnum.GENERAL); 36 | } 37 | 38 | public void addGroupUser(String roomId, String userId,Boolean isSystem) { 39 | addGroupUser(roomId, userId, RoomRoleEnum.GENERAL,isSystem); 40 | } 41 | 42 | public void addGroupUser(String roomId, String userId, RoomRoleEnum role, Boolean isSystem) { 43 | UserGroup userGroup = new UserGroup(); 44 | userGroup.setUserId(userId); 45 | userGroup.setRoomId(roomId); 46 | userGroup.setNotice(true); 47 | userGroup.setRole(role); 48 | userGroup.setIsSystem(isSystem); 49 | userGroupRepository.saveOrUpdate(and(eq("userId", userId), eq("roomId", roomId)), userGroup); 50 | } 51 | 52 | public void addGroupUser(String roomId, String userId, RoomRoleEnum role) { 53 | addGroupUser(roomId, userId, role, false); 54 | } 55 | 56 | public List getGroupUsers(String roomId) { 57 | List roomIds = userGroupRepository.find(eq("roomId", roomId)); 58 | return roomIds.stream().map(userGroup -> { 59 | User user = userRepository.findById(userGroup.getUserId()); 60 | user.setRole(userGroup.getRole()); 61 | return user; 62 | }).collect(Collectors.toList()); 63 | } 64 | 65 | public List getGroupUsers(String roomId, List ids) { 66 | List roomIds = userGroupRepository.find(eq("roomId", roomId)); 67 | return roomIds.stream().filter(x -> !ids.contains(x.getUserId())).map(userGroup -> { 68 | User user = userRepository.findById(userGroup.getUserId()); 69 | user.setRole(userGroup.getRole()); 70 | return user; 71 | }).collect(Collectors.toList()); 72 | } 73 | 74 | public List getUserGroups(String userId) { 75 | List userIds = userGroupRepository.find(and(eq("userId", userId), ne("roomDeleted", true))); 76 | return userIds.stream().map(userGroup -> groupRepository.findById(userGroup.getRoomId())) 77 | .sorted(Comparator.comparing(Group::getIndex).reversed()).collect(Collectors.toList()); 78 | } 79 | 80 | public void remove(String roomId, String userId) { 81 | userGroupRepository.delete(and(eq("roomId", roomId), eq("userId", userId))); 82 | } 83 | 84 | public UserGroup getUserGroup(String roomId, String userId) { 85 | return userGroupRepository.findOne(and(eq("roomId", roomId), eq("userId", userId))); 86 | } 87 | 88 | public void update(UserGroup userGroup) { 89 | userGroupRepository.replace(and(eq("roomId", userGroup.getRoomId()), eq("userId", userGroup.getUserId())), userGroup); 90 | } 91 | 92 | public void delete(String roomId) { 93 | userGroupRepository.updateMany(eq("roomId", roomId), set(new Field<>("roomDeleted", true))); 94 | } 95 | 96 | public UserGroup getSystemUserGroup(String userId) { 97 | return userGroupRepository.findOne(and(eq("isSystem", true), eq("userId", userId))); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/service/UserService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import org.bson.conversions.Bson; 5 | import org.example.dao.UserRepository; 6 | import org.example.packets.Status; 7 | import org.example.packets.bean.Message; 8 | import org.example.packets.bean.User; 9 | 10 | import java.util.List; 11 | import java.util.regex.Pattern; 12 | 13 | import static com.mongodb.client.model.Filters.*; 14 | 15 | public class UserService { 16 | 17 | private final UserRepository userRepository; 18 | 19 | public UserService() { 20 | userRepository = new UserRepository(); 21 | } 22 | 23 | public User getUserInfo(String userId) { 24 | return userRepository.findById(userId); 25 | } 26 | 27 | public void updateById(User userInfo) { 28 | userRepository.updateById(userInfo); 29 | } 30 | 31 | public User getByAccount(String account) { 32 | return userRepository.findOne(eq("account", account)); 33 | } 34 | 35 | public void saveOrUpdate(User user) { 36 | userRepository.saveOrUpdateById(user.clone()); 37 | } 38 | 39 | public List getUserList(String name, String userId, String selfUserId) { 40 | 41 | Bson filter = null; 42 | if (StrUtil.isNotBlank(name) && StrUtil.isNotBlank(userId)) { 43 | Pattern pattern = Pattern.compile("^.*" + name + ".*$", Pattern.CASE_INSENSITIVE); 44 | filter = and(gte("_id", userId), regex("username", pattern),ne("isSystem",true),ne("_id",selfUserId)); 45 | } 46 | if (StrUtil.isNotBlank(name) && StrUtil.isBlank(userId)) { 47 | Pattern pattern = Pattern.compile("^.*" + name + ".*$", Pattern.CASE_INSENSITIVE); 48 | filter = and(regex("username", pattern),ne("isSystem",true),ne("_id",selfUserId)); 49 | } 50 | if (StrUtil.isBlank(name) && StrUtil.isNotBlank(userId)) { 51 | filter = and(gte("_id", userId),ne("isSystem",true),ne("_id",selfUserId)); 52 | } 53 | if(StrUtil.isBlank(name) && StrUtil.isBlank(userId)){ 54 | filter = and(ne("isSystem",true),ne("_id",selfUserId)); 55 | } 56 | return userRepository.findSortLimit(filter, eq("_id", 1), 20); 57 | } 58 | 59 | public void userOffline(String userId) { 60 | User user = userRepository.findById(userId); 61 | user.setStatus(Status.offline()); 62 | userRepository.updateById(user); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /im-core/src/main/java/org/example/util/TestUtil.java: -------------------------------------------------------------------------------- 1 | package org.example.util; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.date.DateUnit; 5 | import cn.hutool.core.date.DateUtil; 6 | import cn.hutool.core.util.RandomUtil; 7 | import com.alibaba.fastjson.JSON; 8 | import com.alibaba.fastjson.JSONArray; 9 | import com.alibaba.fastjson.JSONObject; 10 | import com.mongodb.client.model.Projections; 11 | import org.bson.conversions.Bson; 12 | import org.example.dao.MessageRepository; 13 | import org.example.packets.bean.Message; 14 | 15 | import java.util.Date; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.regex.Pattern; 19 | 20 | import static com.mongodb.client.model.Filters.*; 21 | 22 | public class TestUtil { 23 | 24 | private static String[] familyName = new String[]{"刘", "张", "李", "胡", "沈", "朱", "钱", "王", "伍", "赵", "孙", "吕", "马", "秦", "毛", "成", "梅", "黄", "郭", "杨", "季", "童", "习", "郑", 25 | "吴", "周", "蒋", "卫", "尤", "何", "魏", "章", "郎", " 唐", "汤", "苗", "孔", "鲁", "韦", "任", "袁", "贺", "狄朱"}; 26 | 27 | private static String[] secondName = new String[]{"艺昕", "红薯", "明远", "天蓬", "三丰", "德华", "歌", "佳", "乐", "天", "燕子", "子牛", "海", "燕", "花", "娟", "冰冰", "丽娅", "大为", "无为", "渔民", "大赋", 28 | "明", "远平", "克弱", "亦菲", "靓颖", "富城", "岳", "先觉", "牛", "阿狗", "阿猫", "辰", "蝴蝶", "文化", "冲之", "悟空", "行者", "悟净", "悟能", "观", "音", "乐天", "耀扬", "伊健", "炅", "娜", "春花", "秋香", "春香", 29 | "大为", "如来", "佛祖", "科比", "罗斯", "詹姆屎", "科神", "科蜜", "库里", "卡特", "麦迪", "乔丹", "魔术师", "加索尔", "法码尔", "南斯", "伊哥", "杜兰特", "保罗", "杭州", "爱湘", "湘湘", "昕", "函", "鬼谷子", "膑", "荡", 30 | "子家", "德利优视", "五方会谈", "来电话了", "轨迹", "超"}; 31 | 32 | public static String chineseName() { 33 | return familyName[RandomUtil.randomInt(0, familyName.length - 1)] + secondName[RandomUtil.randomInt(0, secondName.length - 1)]; 34 | } 35 | 36 | public static String avatar() { 37 | return "https://t1.huishahe.com/uploads/tu/202107/9999/7690765ea7.jpg"; 38 | } 39 | 40 | public static int betweenDay(Date startTime, Date endTime) { 41 | long between = DateUtil.between(startTime, endTime, DateUnit.MS, true); 42 | int day = Math.toIntExact(between / (1000 * 60 * 60 * 24)); 43 | return between % (1000 * 60 * 60 * 24) == 0 ? day : day + 1; 44 | } 45 | public static void main(String[] args) { 46 | 47 | 48 | // System.out.println(betweenDay(DateUtil.parse("2021-12-01 00:00:00"), DateUtil.parse("2021-12-02 23:59:59"))); 49 | // TimeInterval timer = DateUtil.timer(); 50 | // MessageRepository messageRepository = new MessageRepository(); 51 | // for (int i = 0; i < 10000; i++) { 52 | // Message message = new Message(); 53 | // message.setId(IdUtil.getSnowflake().nextIdStr()); 54 | // message.setContent(i + "你好"); 55 | // message.setRoomId("1457312478603968512"); 56 | //// message.set("1457312478603968512"); 57 | // message.setDate(DateUtil.formatDate(new Date())); 58 | // message.setTimestamp(DateUtil.formatTime(new Date())); 59 | // message.setSenderId("1457227084663349248"); 60 | // messageRepository.insert(message); 61 | // } 62 | // System.out.println(timer.intervalRestart()); 63 | // List messages1 = messageRepository.find(and(eq("roomId", "1459053033268625408"), not(size("files", 0)))); 64 | // messages1.forEach(System.out::println); 65 | // Pattern compile = Pattern.compile("/2/",Pattern.CASE_INSENSITIVE); 66 | // Pattern pattern = Pattern.compile("^.*2.*$", Pattern.CASE_INSENSITIVE); 67 | // List messages = messageRepository.find(and(eq("roomId", "1462764224268779520"), regex("content", pattern))); 68 | // messages.forEach(System.out::println); 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /im-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | im 7 | org.example 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | im-server 13 | 14 | 15 | 16 | org.example 17 | im-core 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | 23 | UTF-8 24 | 13 25 | 13 26 | 27 | 28 | 29 | 30 | 31 | src/main/resources 32 | false 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-deploy-plugin 40 | 3.0.0-M1 41 | 42 | true 43 | 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-jar-plugin 49 | 3.2.0 50 | 51 | 52 | 53 | true 54 | true 55 | 56 | false 57 | 58 | 59 | ${project.artifactId} 60 | 61 | 62 | config/**/* 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-assembly-plugin 71 | 3.3.0 72 | 73 | 74 | 75 | org.example.ImServer 76 | 77 | 78 | 79 | jar-with-dependencies 80 | 81 | 82 | 83 | 84 | make-assembly 85 | package 86 | 87 | single 88 | 89 | 90 | 95 | ${project.artifactId} 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /im-server/script/_cmd.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | echo - 3 | echo #下载源代码 4 | echo mvn dependency:sources 5 | echo - 6 | 7 | echo #下载源代码jar。 -DdownloadJavadocs=true 下载javadoc包 8 | echo -DdownloadSources=true 9 | echo - 10 | echo - 11 | 12 | 13 | 14 | echo #将jar解压出来 15 | echo mvn dependency:unpack-dependencies 16 | echo - 17 | 18 | echo #将jar拷贝到某一目录中(所有jar在同一目录中) 19 | echo mvn dependency:copy-dependencies -Dmdep.useRepositoryLayout=false 20 | echo - 21 | 22 | echo #将jar按仓库目录拷贝出来() 23 | echo mvn dependency:copy-dependencies -Dmdep.useRepositoryLayout=true -Dmdep.copyPom=true 24 | echo - 25 | echo - 26 | 27 | 28 | 29 | echo #检查版本更新 30 | echo mvn versions:display-dependency-updates 31 | echo - 32 | 33 | echo #版本变更 34 | echo mvn versions:set -DnewVersion=4.0.0-talent-999 35 | echo - 36 | 37 | 38 | call cmd -------------------------------------------------------------------------------- /im-server/script/pkg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | dist 4 | 5 | 6 | dir 7 | 8 | 9 | false 10 | 11 | 12 | 13 | 14 | 15 | ${project.basedir}/src/main/resources 16 | config 17 | 18 | logback.* 19 | application.* 20 | 21 | 22 | 23 | 24 | ${project.basedir}/src/main/resources/img 25 | img 26 | 27 | 28 | 29 | ${project.basedir}/script 30 | 31 | 32 | startup.* 33 | debug.* 34 | _cmd.* 35 | 36 | 37 | 38 | 39 | 40 | 41 | lib 42 | 43 | 44 | -------------------------------------------------------------------------------- /im-server/script/startup.bat: -------------------------------------------------------------------------------- 1 | rem -Xms64m -Xmx2048m 2 | 3 | @echo off 4 | setlocal & pushd 5 | set APP_ENTRY=org.example.ImServer 6 | set BASE=%~dp0 7 | set CP=%BASE%\config;%BASE%\lib\* 8 | java -Xverify:none -XX:+HeapDumpOnOutOfMemoryError -Dtio.default.read.buffer.size=512 -XX:HeapDumpPath=c:/java-im-server-pid.hprof -cp "%CP%" %APP_ENTRY% 9 | endlocal & popd 10 | -------------------------------------------------------------------------------- /im-server/script/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | java -XX:+HeapDumpOnOutOfMemoryError -Dtio.default.read.buffer.size=512 -XX:HeapDumpPath=./java-im-server-pid.hprof -Dglobal.config.path=/data -jar im-server-jar-with-dependencies.jar -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/ImServer.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import org.example.config.ImServerHttpStart; 4 | import org.example.config.ImServerWebSocketStart; 5 | 6 | public class ImServer { 7 | 8 | public static void main(String[] args) throws Exception { 9 | ImServerWebSocketStart.start(); 10 | ImServerHttpStart.start(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/AbstractCmdHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond; 2 | 3 | import org.example.service.*; 4 | 5 | public abstract class AbstractCmdHandler implements CmdHandler { 6 | 7 | public UserService userService; 8 | public FriendInfoService friendInfoService; 9 | public GroupService groupService; 10 | public UserGroupService userGroupService; 11 | public MessageService messageService; 12 | public UnReadMessageService unReadMessageService; 13 | public AuthService authService; 14 | public EmoticonService emoticonService; 15 | 16 | 17 | public AbstractCmdHandler() { 18 | userService = new UserService(); 19 | friendInfoService = new FriendInfoService(); 20 | groupService = new GroupService(); 21 | userGroupService = new UserGroupService(); 22 | messageService = new MessageService(); 23 | unReadMessageService = new UnReadMessageService(); 24 | authService = new AuthService(); 25 | emoticonService = new EmoticonService(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/CmdHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond; 2 | 3 | 4 | import org.example.enums.CommandEnum; 5 | import org.tio.core.ChannelContext; 6 | import org.tio.core.intf.Packet; 7 | import org.tio.websocket.common.WsResponse; 8 | 9 | /** 10 | * 11 | * 版本: [1.0] 12 | * 功能说明: 13 | * @author : WChao 创建时间: 2017年9月8日 下午4:29:38 14 | */ 15 | public interface CmdHandler 16 | { 17 | /** 18 | * 功能描述:[命令主键] 19 | */ 20 | CommandEnum command(); 21 | 22 | /** 23 | * 处理Cmd命令 24 | */ 25 | WsResponse handler(Packet packet, ChannelContext channelContext); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/CommandManager.java: -------------------------------------------------------------------------------- 1 | package org.example.commond; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.commond.handler.*; 5 | import org.example.commond.handler.emoticon.EmoticonOperationReqHandler; 6 | import org.example.commond.handler.emoticon.EmoticonReqHandler; 7 | import org.example.commond.handler.emoticon.EmoticonSearchReqHandler; 8 | import org.example.commond.handler.message.*; 9 | import org.example.commond.handler.room.*; 10 | import org.example.commond.handler.system.EditProfileHandler; 11 | import org.example.commond.handler.system.SetNewPasswordReqHandler; 12 | import org.example.commond.handler.system.SystemTextMessageHandler; 13 | import org.example.commond.handler.video.VideoReqHandler; 14 | import org.example.enums.CommandEnum; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.Objects; 19 | 20 | @Slf4j 21 | public class CommandManager { 22 | 23 | private static final Map handlerMap = new HashMap<>(); 24 | 25 | static { 26 | try { 27 | registerCommand(new HeartbeatReqHandler()); 28 | registerCommand(new LoginReqHandler()); 29 | registerCommand(new UserReqHandler()); 30 | registerCommand(new MessageReqHandler()); 31 | registerCommand(new MessageSearchReqHandler()); 32 | registerCommand(new JoinGroupReqHandler()); 33 | registerCommand(new ChatReqHandler()); 34 | registerCommand(new CreatGroupReqHandler()); 35 | registerCommand(new CloseReqHandler()); 36 | registerCommand(new MessageReadReqHandler()); 37 | registerCommand(new UserListHandler()); 38 | registerCommand(new MessageReactionReqHandler()); 39 | registerCommand(new MessageForwardReqHandler()); 40 | registerCommand(new EditProfileHandler()); 41 | registerCommand(new SetNewPasswordReqHandler()); 42 | registerCommand(new RemoveGroupUserReqHandler()); 43 | registerCommand(new SetRoomAdminReqHandler()); 44 | registerCommand(new SystemTextMessageHandler()); 45 | registerCommand(new HandoverGroupHandler()); 46 | registerCommand(new DisbandGroupHandler()); 47 | registerCommand(new EditGroupProfileReqHandler()); 48 | registerCommand(new MessageDeleteHandler()); 49 | registerCommand(new UserGroupConfigReqHandler()); 50 | registerCommand(new VideoReqHandler()); 51 | registerCommand(new EmoticonSearchReqHandler()); 52 | registerCommand(new EmoticonReqHandler()); 53 | registerCommand(new EmoticonOperationReqHandler()); 54 | registerCommand(new SetPublicRoomReqHandler()); 55 | registerCommand(new SearchRoomReqHandler()); 56 | } catch (Exception e) { 57 | log.info("注册处理器失败"); 58 | } 59 | 60 | } 61 | 62 | public static void registerCommand(AbstractCmdHandler imCommandHandler) { 63 | if (imCommandHandler == null || imCommandHandler.command() == null) { 64 | return; 65 | } 66 | int cmd_number = imCommandHandler.command().getValue(); 67 | if (Objects.isNull(CommandEnum.forNumber(cmd_number))) { 68 | throw new RuntimeException("failed to register cmd handler, illegal cmd code:" + cmd_number + ",use Command.addAndGet () to add in the enumerated Command class!"); 69 | } 70 | if (Objects.isNull(handlerMap.get(cmd_number))) { 71 | handlerMap.put(cmd_number, imCommandHandler); 72 | } else { 73 | throw new RuntimeException("cmd code:" + cmd_number + ",has been registered, please correct!"); 74 | } 75 | } 76 | 77 | public static AbstractCmdHandler getCommand(CommandEnum command) { 78 | if (command == null) { 79 | throw new RuntimeException("不存在的包解析指令"); 80 | } 81 | return handlerMap.get(command.getValue()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/CloseReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import org.example.commond.AbstractCmdHandler; 5 | import org.example.config.Im; 6 | import org.example.enums.CommandEnum; 7 | import org.tio.core.ChannelContext; 8 | import org.tio.core.intf.Packet; 9 | import org.tio.websocket.common.WsRequest; 10 | import org.tio.websocket.common.WsResponse; 11 | 12 | public class CloseReqHandler extends AbstractCmdHandler { 13 | 14 | @Override 15 | public CommandEnum command() { 16 | return CommandEnum.COMMAND_CLOSE_REQ; 17 | } 18 | 19 | @Override 20 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 21 | WsRequest request = (WsRequest) packet; 22 | String userId = request.getWsBodyText(); 23 | Im.remove(channelContext, "收到关闭请求"); 24 | // 强制下线使用 25 | /* if(StrUtil.isBlank(userId)){ 26 | Im.remove(channelContext, "收到关闭请求"); 27 | }else{ 28 | Im.remove(userId, "收到关闭请求!"); 29 | }*/ 30 | return null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/HeartbeatReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler; 2 | 3 | import org.example.commond.AbstractCmdHandler; 4 | import org.example.config.ImConfig; 5 | import org.example.enums.CommandEnum; 6 | import org.example.packets.handler.system.HeartbeatBody; 7 | import org.example.packets.handler.system.RespBody; 8 | import org.tio.core.ChannelContext; 9 | import org.tio.core.intf.Packet; 10 | import org.tio.websocket.common.WsResponse; 11 | 12 | public class HeartbeatReqHandler extends AbstractCmdHandler { 13 | @Override 14 | public CommandEnum command() { 15 | return CommandEnum.COMMAND_HEARTBEAT_REQ; 16 | } 17 | 18 | @Override 19 | public WsResponse handler(Packet packet, ChannelContext imChannelContext) { 20 | return WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_HEARTBEAT_REQ, new HeartbeatBody((byte) -128)), ImConfig.CHARSET); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/LoginReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler; 2 | 3 | import cn.hutool.core.util.ByteUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.crypto.SecureUtil; 6 | import cn.hutool.jwt.JWT; 7 | import cn.hutool.jwt.JWTUtil; 8 | import com.alibaba.fastjson.JSON; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.example.commond.AbstractCmdHandler; 11 | import org.example.commond.CommandManager; 12 | import org.example.config.Chat; 13 | import org.example.config.Im; 14 | import org.example.config.ImConfig; 15 | import org.example.enums.CommandEnum; 16 | import org.example.packets.Status; 17 | import org.example.packets.bean.Auth; 18 | import org.example.packets.bean.Group; 19 | import org.example.packets.bean.User; 20 | import org.example.packets.bean.UserGroup; 21 | import org.example.packets.handler.system.LoginReqBody; 22 | import org.example.packets.handler.system.LoginRespBody; 23 | import org.example.packets.handler.system.RespBody; 24 | import org.example.packets.handler.user.UserReqBody; 25 | import org.example.packets.handler.user.UserStatusBody; 26 | import org.tio.core.ChannelContext; 27 | import org.tio.core.intf.Packet; 28 | import org.tio.http.common.HttpRequest; 29 | import org.tio.websocket.common.WsRequest; 30 | import org.tio.websocket.common.WsResponse; 31 | 32 | import java.util.Arrays; 33 | import java.util.List; 34 | 35 | @Slf4j 36 | public class LoginReqHandler extends AbstractCmdHandler { 37 | 38 | @Override 39 | public CommandEnum command() { 40 | return CommandEnum.COMMAND_LOGIN_REQ; 41 | } 42 | 43 | @Override 44 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 45 | 46 | WsRequest httpRequest = (WsRequest) packet; 47 | String token = new String(httpRequest.getBody()); 48 | // String token = StrUtil.toString(httpRequest.getBody()); 49 | System.out.println(token); 50 | // String token = (String) JSON.parseObject(Arrays.toString(httpRequest.getBody())).get("token"); 51 | 52 | JWT jwt = JWTUtil.parseToken(token); 53 | String uid = (String) jwt.getPayload("uid"); 54 | 55 | Auth auth = authService.getByUserId(uid); 56 | 57 | // 持久化获取用户信息 58 | User user = userService.getUserInfo(uid); 59 | 60 | // 获取持久化用户群组信息 61 | List groups = userGroupService.getUserGroups(user.getId()); 62 | user.setStatus(Status.online()); 63 | user.setGroups(groups); 64 | 65 | String success = RespBody.success(CommandEnum.COMMAND_LOGIN_RESP, new LoginRespBody(user.getId())); 66 | Im.bSend(channelContext, WsResponse.fromText(success, ImConfig.CHARSET)); 67 | 68 | log.info("登录{}", uid); 69 | Im.bindUser(channelContext, user); 70 | 71 | UserStatusBody build = UserStatusBody.builder().user(Im.getUser(channelContext, false)).build(); 72 | 73 | for (Group group : groups) { 74 | UserGroup userGroup = userGroupService.getUserGroup(group.getRoomId(), user.getId()); 75 | build.getUser().setRole(userGroup.getRole()); 76 | // 绑定群组 77 | Im.bindGroup(channelContext, group); 78 | // 给所在群组发送上线消息 用户状态更新 79 | List groupUsers = userGroupService.getGroupUsers(group.getRoomId()); 80 | group.setUsers(groupUsers); 81 | build.setGroup(group); 82 | Chat.sendToGroup(build, channelContext); 83 | } 84 | auth.setLastLoginIp(channelContext.getClientNode().getIp()); 85 | authService.update(auth); 86 | 87 | UserReqHandler userReqHandler = (UserReqHandler) CommandManager.getCommand(CommandEnum.COMMAND_GET_USER_REQ); 88 | UserReqBody userReqBody = new UserReqBody(); 89 | userReqBody.setUserId(user.getId()); 90 | WsRequest wsRequest = WsRequest.fromText(JSON.toJSONString(userReqBody), ImConfig.CHARSET); 91 | userReqHandler.handler(wsRequest, channelContext); 92 | return null; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/UserListHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.example.commond.AbstractCmdHandler; 7 | import org.example.config.Im; 8 | import org.example.enums.CommandEnum; 9 | import org.example.packets.bean.User; 10 | import org.example.packets.handler.system.RespBody; 11 | import org.example.packets.handler.user.SearchUserReqBody; 12 | import org.example.packets.handler.user.SearchUserRespBody; 13 | import org.tio.core.ChannelContext; 14 | import org.tio.core.intf.Packet; 15 | import org.tio.websocket.common.WsRequest; 16 | import org.tio.websocket.common.WsResponse; 17 | 18 | import java.util.List; 19 | import java.util.stream.Collectors; 20 | 21 | @Slf4j 22 | public class UserListHandler extends AbstractCmdHandler { 23 | 24 | @Override 25 | public CommandEnum command() { 26 | return CommandEnum.COMMAND_SEARCH_USER_REQ; 27 | } 28 | 29 | @Override 30 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 31 | 32 | WsRequest request = (WsRequest) packet; 33 | User user = Im.getUser(channelContext); 34 | if(user == null){ 35 | log.info("当前用户获取失败"); 36 | return null; 37 | } 38 | 39 | SearchUserReqBody reqBody = JSON.parseObject(request.getBody(),SearchUserReqBody.class); 40 | 41 | SearchUserRespBody respBody = new SearchUserRespBody(); 42 | respBody.setSearchId(reqBody.getSearchId()); 43 | 44 | List userList = userService.getUserList(reqBody.getName(), reqBody.getUserId(),user.getId()); 45 | 46 | respBody.setUserList(userList); 47 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_SEARCH_USER_RESP, respBody), Im.CHARSET); 48 | 49 | Im.send(channelContext,response); 50 | 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/UserReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.collection.CollUtil; 5 | import com.alibaba.fastjson.JSON; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.example.commond.AbstractCmdHandler; 8 | import org.example.config.Chat; 9 | import org.example.config.Im; 10 | import org.example.config.ImConfig; 11 | import org.example.enums.CommandEnum; 12 | import org.example.packets.bean.*; 13 | import org.example.packets.handler.message.ChatRespBody; 14 | import org.example.packets.handler.system.RespBody; 15 | import org.example.packets.handler.user.UserReqBody; 16 | import org.tio.core.ChannelContext; 17 | import org.tio.core.intf.Packet; 18 | import org.tio.websocket.common.WsRequest; 19 | import org.tio.websocket.common.WsResponse; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | @Slf4j 25 | public class UserReqHandler extends AbstractCmdHandler { 26 | 27 | @Override 28 | public CommandEnum command() { 29 | return CommandEnum.COMMAND_GET_USER_REQ; 30 | } 31 | 32 | @Override 33 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 34 | 35 | WsRequest wsRequest = (WsRequest) packet; 36 | UserReqBody userReqBody = JSON.parseObject(wsRequest.getBody(), UserReqBody.class); 37 | log.info("userReqBody : {}", userReqBody); 38 | User user = Im.getUser(channelContext); 39 | 40 | List chats = new ArrayList<>(); 41 | 42 | for (Group group : user.getGroups()) { 43 | // 组织群组用户信息 44 | String roomId = group.getRoomId(); 45 | List groupUsers = userGroupService.getGroupUsers(roomId); 46 | group.setUsers(groupUsers); 47 | UserGroup userGroup = userGroupService.getUserGroup(group.getRoomId(), user.getId()); 48 | group.setNotice(userGroup.getNotice()); 49 | group.setIndex(Boolean.TRUE.equals(userGroup.getTop()) ? 9999999999999L : group.getIndex()); 50 | Chat.resetGroup(group, user.getId()); 51 | } 52 | user.setChats(chats); 53 | 54 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_GET_USER_RESP, user), ImConfig.CHARSET); 55 | Im.send(channelContext, response); 56 | 57 | // 倒着循环, 解决未读上线的问题 58 | for (int i = user.getGroups().size() - 1; i >= 0; i--) { 59 | Group group = user.getGroups().get(i); 60 | 61 | // 获取到最后一条消息未读消息,并且发重新发送 62 | List unReadMessages = unReadMessageService.getUnReadMessage(user.getId(), group.getRoomId()); 63 | if (CollUtil.isNotEmpty(unReadMessages)) { 64 | UnReadMessage unReadMessage = unReadMessages.get(unReadMessages.size() - 1); 65 | Message message = messageService.getMessage(unReadMessage.getMessageId()); 66 | ChatRespBody chatRespBody = BeanUtil.copyProperties(message, ChatRespBody.class); 67 | User userInfo = userService.getUserInfo(chatRespBody.getSenderId()); 68 | chatRespBody.setAvatar(userInfo.getAvatar()); 69 | chatRespBody.setUsername(userInfo.getUsername()); 70 | chatRespBody.setUnreadCount(CollUtil.isEmpty(unReadMessages) ? 0 : unReadMessages.size()); 71 | WsResponse wsResponse = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_CHAT_REQ, chatRespBody), Im.CHARSET); 72 | Im.send(channelContext, wsResponse); 73 | } 74 | } 75 | return null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/emoticon/EmoticonReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.emoticon; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.example.commond.AbstractCmdHandler; 5 | import org.example.config.Im; 6 | import org.example.enums.CommandEnum; 7 | import org.example.packets.bean.Emoticon; 8 | import org.example.packets.bean.User; 9 | import org.example.packets.bean.UserEmoticon; 10 | import org.example.packets.handler.emoticon.EmoticonSearchReqBody; 11 | import org.example.packets.handler.system.RespBody; 12 | import org.example.service.UserEmoticonService; 13 | import org.tio.core.ChannelContext; 14 | import org.tio.core.intf.Packet; 15 | import org.tio.websocket.common.WsRequest; 16 | import org.tio.websocket.common.WsResponse; 17 | 18 | import java.util.List; 19 | import java.util.stream.Collectors; 20 | 21 | public class EmoticonReqHandler extends AbstractCmdHandler { 22 | 23 | private final UserEmoticonService userEmoticonService; 24 | 25 | public EmoticonReqHandler () { 26 | userEmoticonService = new UserEmoticonService(); 27 | } 28 | 29 | @Override 30 | public CommandEnum command() { 31 | return CommandEnum.COMMAND_EMOTICON_REQ; 32 | } 33 | 34 | @Override 35 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 36 | WsRequest request = (WsRequest) packet; 37 | 38 | User user = Im.getUser(channelContext); 39 | 40 | EmoticonSearchReqBody emoticonSearchReqBody = JSON.parseObject(request.getBody(), EmoticonSearchReqBody.class); 41 | 42 | List userEmoticons = userEmoticonService.getUserEmoticons(emoticonSearchReqBody.getId(),user.getId()); 43 | 44 | List emoticons = userEmoticons.stream().map(x -> emoticonService.getEmoticon(x.getEmoticonId())).collect(Collectors.toList()); 45 | 46 | String success = RespBody.success(CommandEnum.COMMAND_EMOTICON_RESP, emoticons); 47 | WsResponse response = WsResponse.fromText(success, Im.CHARSET); 48 | Im.send(channelContext, response); 49 | return null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/emoticon/EmoticonSearchReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.emoticon; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.example.commond.AbstractCmdHandler; 5 | import org.example.config.Im; 6 | import org.example.enums.CommandEnum; 7 | import org.example.packets.bean.Emoticon; 8 | import org.example.packets.handler.emoticon.EmoticonSearchReqBody; 9 | import org.example.packets.handler.system.RespBody; 10 | import org.tio.core.ChannelContext; 11 | import org.tio.core.intf.Packet; 12 | import org.tio.websocket.common.WsRequest; 13 | import org.tio.websocket.common.WsResponse; 14 | 15 | import java.util.List; 16 | 17 | 18 | public class EmoticonSearchReqHandler extends AbstractCmdHandler { 19 | 20 | @Override 21 | public CommandEnum command() { 22 | return CommandEnum.COMMAND_EMOTICON_SEARCH_REQ; 23 | } 24 | 25 | @Override 26 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 27 | WsRequest request = (WsRequest) packet; 28 | 29 | EmoticonSearchReqBody emoticonSearchReqBody = JSON.parseObject(request.getBody(), EmoticonSearchReqBody.class); 30 | 31 | List emoticons = emoticonService.getEmoticons(emoticonSearchReqBody.getId(), emoticonSearchReqBody.getContent()); 32 | 33 | String success = RespBody.success(CommandEnum.COMMAND_EMOTICON_SEARCH_RESP, emoticons); 34 | WsResponse response = WsResponse.fromText(success, Im.CHARSET); 35 | Im.send(channelContext, response); 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/message/ChatReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.message; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.collection.CollUtil; 5 | import cn.hutool.core.date.DateUtil; 6 | import cn.hutool.core.util.IdUtil; 7 | import cn.hutool.core.util.ObjectUtil; 8 | import cn.hutool.core.util.URLUtil; 9 | import com.alibaba.fastjson.JSONObject; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.example.commond.AbstractCmdHandler; 12 | import org.example.config.Chat; 13 | import org.example.config.CourierConfig; 14 | import org.example.config.Im; 15 | import org.example.enums.CommandEnum; 16 | import org.example.packets.bean.Message; 17 | import org.example.packets.bean.User; 18 | import org.example.packets.handler.message.ChatReqBody; 19 | import org.example.packets.handler.message.ChatRespBody; 20 | import org.tio.core.ChannelContext; 21 | import org.tio.core.intf.Packet; 22 | import org.tio.websocket.common.WsRequest; 23 | import org.tio.websocket.common.WsResponse; 24 | 25 | import java.util.Date; 26 | 27 | @Slf4j 28 | public class ChatReqHandler extends AbstractCmdHandler { 29 | 30 | @Override 31 | public CommandEnum command() { 32 | return CommandEnum.COMMAND_CHAT_REQ; 33 | } 34 | 35 | @Override 36 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 37 | WsRequest httpPacket = (WsRequest) packet; 38 | log.info(httpPacket.getWsBodyText()); 39 | User user = Im.getUser(channelContext); 40 | ChatReqBody request = JSONObject.parseObject(httpPacket.getBody(), ChatReqBody.class); 41 | Date date = new Date(); 42 | request.setDate(DateUtil.formatDate(date)); 43 | request.setTimestamp(DateUtil.formatTime(date)); 44 | 45 | if (CollUtil.isNotEmpty(request.getFiles())) { 46 | request.getFiles().forEach(x -> { 47 | if (!x.getUrl().startsWith("https") && !x.getUrl().startsWith("http")) { 48 | x.setUrl(CourierConfig.fileUrl + x.getUrl()); 49 | } 50 | }); 51 | } 52 | 53 | Message message = BeanUtil.copyProperties(request, Message.class); 54 | message.setId(ObjectUtil.defaultIfNull(request.get_id(), IdUtil.getSnowflake().nextIdStr())); 55 | message.setSystem(ObjectUtil.defaultIfNull(message.getSystem(), false)); 56 | message.setSenderId(user.getId()); 57 | message.setDeleted(false); 58 | message.setSaved(true); 59 | message.setDistributed(true); 60 | message.setSeen(false); 61 | message.setSendTime(System.currentTimeMillis()); 62 | 63 | // 消息缓存至redis 64 | messageService.putGroupMessage(message); 65 | ChatRespBody response = BeanUtil.copyProperties(message, ChatRespBody.class); 66 | // 发送给群组用户 67 | Chat.sendToGroup(response); 68 | 69 | // 更新群组最后一条信息 70 | groupService.updateLastMessage(message); 71 | 72 | return null; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/message/MessageDeleteHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.message; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.config.Im; 7 | import org.example.enums.CommandEnum; 8 | import org.example.packets.bean.Message; 9 | import org.example.packets.bean.User; 10 | import org.example.packets.handler.system.RespBody; 11 | import org.example.packets.handler.message.MessageDeleteReqBody; 12 | import org.example.packets.handler.message.MessageDeleteRespBody; 13 | import org.tio.core.ChannelContext; 14 | import org.tio.core.intf.Packet; 15 | import org.tio.websocket.common.WsRequest; 16 | import org.tio.websocket.common.WsResponse; 17 | 18 | public class MessageDeleteHandler extends AbstractCmdHandler { 19 | 20 | @Override 21 | public CommandEnum command() { 22 | return CommandEnum.COMMAND_MESSAGE_DELETE_REQ; 23 | } 24 | 25 | @Override 26 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 27 | 28 | WsRequest request = (WsRequest) packet; 29 | 30 | MessageDeleteReqBody body = JSON.parseObject(request.getBody(), MessageDeleteReqBody.class); 31 | 32 | Message message = messageService.getMessage(body.getMessageId()); 33 | message.setDeleted(true); 34 | messageService.update(message); 35 | 36 | MessageDeleteRespBody messageDeleteRespBody = BeanUtil.copyProperties(message, MessageDeleteRespBody.class); 37 | 38 | Message lastMessage = messageService.getLastMessage(message.getRoomId()); 39 | if (lastMessage.getId().equals(message.getId())) { 40 | 41 | User userInfo = userService.getUserInfo(message.getSenderId()); 42 | // 更新群组最后一条信息 43 | groupService.updateLastMessage(message); 44 | messageDeleteRespBody.setDeleteUserName(userInfo.getUsername()); 45 | messageDeleteRespBody.setIsLastMessage(true); 46 | } else { 47 | messageDeleteRespBody.setIsLastMessage(false); 48 | } 49 | 50 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_MESSAGE_DELETE_RESP, messageDeleteRespBody), Im.CHARSET); 51 | Im.sendToGroup(message.getRoomId(), response); 52 | 53 | return null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/message/MessageForwardReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.message; 2 | 3 | import cn.hutool.core.util.IdUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.JSONObject; 6 | import org.example.commond.AbstractCmdHandler; 7 | import org.example.commond.CommandManager; 8 | import org.example.commond.handler.LoginReqHandler; 9 | import org.example.commond.handler.room.CreatGroupReqHandler; 10 | import org.example.config.Im; 11 | import org.example.config.ImConfig; 12 | import org.example.enums.CommandEnum; 13 | import org.example.packets.bean.FriendInfo; 14 | import org.example.packets.bean.Group; 15 | import org.example.packets.bean.Message; 16 | import org.example.packets.bean.User; 17 | import org.example.packets.handler.message.MessageForwardReqBody; 18 | import org.example.packets.handler.room.CreateGroupReqBody; 19 | import org.example.packets.handler.system.RespBody; 20 | import org.tio.core.ChannelContext; 21 | import org.tio.core.intf.Packet; 22 | import org.tio.websocket.common.WsPacket; 23 | import org.tio.websocket.common.WsRequest; 24 | import org.tio.websocket.common.WsResponse; 25 | 26 | import java.util.Comparator; 27 | import java.util.List; 28 | import java.util.stream.Collectors; 29 | 30 | public class MessageForwardReqHandler extends AbstractCmdHandler { 31 | 32 | @Override 33 | public CommandEnum command() { 34 | return CommandEnum.COMMAND_FORWARD_MESSAGE_REQ; 35 | } 36 | 37 | @Override 38 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 39 | 40 | User self = Im.getUser(channelContext); 41 | WsPacket wsPacket = (WsPacket) packet; 42 | MessageForwardReqBody reqBody = JSON.parseObject(wsPacket.getBody(), MessageForwardReqBody.class); 43 | 44 | ChatReqHandler chatReqHandler = (ChatReqHandler) CommandManager.getCommand(CommandEnum.COMMAND_CHAT_REQ); 45 | 46 | List messages = reqBody.getMessages().stream().sorted(Comparator.comparing(Message::getSendTime)).map(x -> { 47 | Message message = new Message(); 48 | message.setContent(x.getContent()); 49 | message.setSenderId(self.getId()); 50 | message.setFiles(x.getFiles()); 51 | return message; 52 | }).collect(Collectors.toList()); 53 | 54 | // 给这些chat发消息,不需要审核。 55 | for (String chat : reqBody.getChats()) { 56 | for (Message message : messages) { 57 | message.setId(IdUtil.getSnowflake().nextIdStr()); 58 | message.setRoomId(chat); 59 | WsRequest wsRequest = WsRequest.fromText(JSONObject.toJSONString(message), ImConfig.CHARSET); 60 | chatReqHandler.handler(wsRequest, channelContext); 61 | } 62 | } 63 | 64 | CreatGroupReqHandler command = (CreatGroupReqHandler)CommandManager.getCommand(CommandEnum.COMMAND_CREATE_GROUP_REQ); 65 | for (String user : reqBody.getUsers()) { 66 | FriendInfo room = friendInfoService.getRoomInfo(self.getId(), user); 67 | if(room == null){ 68 | CreateGroupReqBody createGroupReqBody = new CreateGroupReqBody(); 69 | createGroupReqBody.setIsFriend(true); 70 | createGroupReqBody.setRoomName("好友会话"); 71 | createGroupReqBody.setUsers(List.of(User.builder().id(user).build())); 72 | WsRequest wsRequest = WsRequest.fromText(JSONObject.toJSONString(createGroupReqBody), ImConfig.CHARSET); 73 | command.handler(wsRequest,channelContext); 74 | room = friendInfoService.getRoomInfo(self.getId(), user); 75 | } 76 | if(reqBody.getChats().contains(room.getRoomId())){ 77 | continue; 78 | } 79 | for (Message message : reqBody.getMessages()) { 80 | message.setId(IdUtil.getSnowflake().nextIdStr()); 81 | message.setRoomId(room.getRoomId()); 82 | WsRequest wsRequest = WsRequest.fromText(JSONObject.toJSONString(message), ImConfig.CHARSET); 83 | chatReqHandler.handler(wsRequest, channelContext); 84 | } 85 | } 86 | 87 | WsResponse wsResponse = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_FORWARD_MESSAGE_RESP), Im.CHARSET); 88 | Im.send(channelContext,wsResponse); 89 | 90 | return null; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/message/MessageReactionReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.message; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.config.Im; 7 | import org.example.enums.CommandEnum; 8 | import org.example.packets.bean.User; 9 | import org.example.packets.handler.system.RespBody; 10 | import org.example.packets.handler.message.MessageReactionReqBody; 11 | import org.example.packets.handler.message.MessageReactionRespBody; 12 | import org.tio.core.ChannelContext; 13 | import org.tio.core.intf.Packet; 14 | import org.tio.websocket.common.WsRequest; 15 | import org.tio.websocket.common.WsResponse; 16 | 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | @Slf4j 21 | public class MessageReactionReqHandler extends AbstractCmdHandler { 22 | 23 | @Override 24 | public CommandEnum command() { 25 | return CommandEnum.COMMAND_SEND_MESSAGE_REACTION_REQ; 26 | } 27 | 28 | @Override 29 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 30 | 31 | WsRequest request = (WsRequest) packet; 32 | 33 | MessageReactionReqBody messageReactionReqBody = JSON.parseObject(request.getBody(), MessageReactionReqBody.class); 34 | 35 | log.info("{}", messageReactionReqBody); 36 | 37 | User user = Im.getUser(channelContext); 38 | 39 | MessageReactionRespBody messageReactionRespBody = new MessageReactionRespBody(); 40 | messageReactionRespBody.setRoomId(messageReactionReqBody.getRoomId()); 41 | messageReactionRespBody.setMessageId(messageReactionReqBody.getMessageId()); 42 | 43 | // 添加一个表情回复 44 | messageService.addReaction(messageReactionReqBody.getMessageId() 45 | , messageReactionReqBody.getReaction(), messageReactionReqBody.getRemove(), user.getId()); 46 | 47 | Map> reaction = messageService.getReaction(messageReactionReqBody.getMessageId()); 48 | 49 | messageReactionRespBody.setReactions(reaction); 50 | 51 | WsResponse wsResponse = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_SEND_MESSAGE_REACTION_RESP, messageReactionRespBody), Im.CHARSET); 52 | Im.sendToGroup(messageReactionRespBody.getRoomId(), wsResponse); 53 | 54 | return null; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/message/MessageReadReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.example.commond.AbstractCmdHandler; 5 | import org.example.config.Im; 6 | import org.example.enums.CommandEnum; 7 | import org.example.packets.bean.Group; 8 | import org.example.packets.bean.Message; 9 | import org.example.packets.bean.UnReadMessage; 10 | import org.example.packets.handler.message.MessageReadReqBody; 11 | import org.example.packets.handler.system.RespBody; 12 | import org.tio.core.ChannelContext; 13 | import org.tio.core.intf.Packet; 14 | import org.tio.utils.hutool.CollUtil; 15 | import org.tio.websocket.common.WsRequest; 16 | import org.tio.websocket.common.WsResponse; 17 | 18 | import java.util.List; 19 | 20 | public class MessageReadReqHandler extends AbstractCmdHandler { 21 | 22 | @Override 23 | public CommandEnum command() { 24 | return CommandEnum.COMMAND_MESSAGE_READ_REQ; 25 | } 26 | 27 | @Override 28 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 29 | 30 | WsRequest request = (WsRequest) packet; 31 | 32 | // 清理当前房间未读消息 33 | MessageReadReqBody messageReadReqBody = JSONObject.parseObject(request.getBody(), MessageReadReqBody.class); 34 | List unReadMessage = unReadMessageService.getUnReadMessage(Im.getUser(channelContext).getId(), messageReadReqBody.getRoomId()); 35 | 36 | unReadMessageService.clearUnReadMessage(Im.getUser(channelContext).getId(), messageReadReqBody.getRoomId()); 37 | 38 | Group groupInfo = groupService.getGroupInfo(messageReadReqBody.getRoomId()); 39 | 40 | // 检查这个消息是不是全部已读 41 | for (UnReadMessage readMessage : unReadMessage) { 42 | List messageUnReads = unReadMessageService.getMessageUnReads(readMessage.getMessageId()); 43 | // 如果为空 全部已读 44 | if (CollUtil.isEmpty(messageUnReads)) { 45 | messageReadReqBody.setMessageId(readMessage.getMessageId()); 46 | 47 | // 更新消息为已读状态 48 | messageService.read(readMessage.getMessageId()); 49 | 50 | // 如果当前群组消息等于最后一条消息 51 | if (groupInfo.getLastMessage().getMessageId().equals(readMessage.getMessageId())) { 52 | groupService.readLastMessage(groupInfo); 53 | } 54 | 55 | Message message = messageService.getMessage(readMessage.getMessageId()); 56 | // 发送给消息发送者 57 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_MESSAGE_READ_RESP, messageReadReqBody), Im.CHARSET); 58 | List channelContexts = Im.getChannelByUserId(message.getSenderId()); 59 | for (ChannelContext context : channelContexts) { 60 | Im.send(context, response); 61 | } 62 | } 63 | } 64 | 65 | 66 | return null; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/message/MessageReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.message; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.config.Im; 7 | import org.example.enums.CommandEnum; 8 | import org.example.enums.MessageFetchTypeEnum; 9 | import org.example.packets.bean.Message; 10 | import org.example.packets.bean.User; 11 | import org.example.packets.handler.message.ChatRespBody; 12 | import org.example.packets.handler.message.MessageReqBody; 13 | import org.example.packets.handler.message.MessageRespBody; 14 | import org.example.packets.handler.system.RespBody; 15 | import org.tio.core.ChannelContext; 16 | import org.tio.core.intf.Packet; 17 | import org.tio.websocket.common.WsRequest; 18 | import org.tio.websocket.common.WsResponse; 19 | 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | public class MessageReqHandler extends AbstractCmdHandler { 24 | 25 | @Override 26 | public CommandEnum command() { 27 | return CommandEnum.COMMAND_GET_MESSAGE_REQ; 28 | } 29 | 30 | @Override 31 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 32 | 33 | WsRequest request = (WsRequest) packet; 34 | MessageReqBody messageReqBody = JSON.parseObject(request.getBody(), MessageReqBody.class); 35 | if (messageReqBody.getType() == null) { 36 | messageReqBody.setType(MessageFetchTypeEnum.TOP); 37 | } 38 | List messages = messageService.getHistoryMessage(messageReqBody.getRoomId(), messageReqBody.getMessageId(), messageReqBody.getType()); 39 | 40 | User user = Im.getUser(channelContext); 41 | 42 | List collect = messages.stream().map(x -> { 43 | ChatRespBody chatRespBody = BeanUtil.copyProperties(x, ChatRespBody.class); 44 | User userInfo = userService.getUserInfo(chatRespBody.getSenderId()); 45 | chatRespBody.setAvatar(userInfo.getAvatar()); 46 | chatRespBody.setUsername(userInfo.getUsername()); 47 | chatRespBody.setCurrentUserId(user.getId()); 48 | return chatRespBody; 49 | }).collect(Collectors.toList()); 50 | 51 | MessageRespBody messageRespBody = new MessageRespBody(); 52 | messageRespBody.setMessages(collect); 53 | messageRespBody.setType(messageReqBody.getType()); 54 | messageRespBody.setReturnDefault(messageReqBody.getReturnDefault()); 55 | 56 | String success = RespBody.success(CommandEnum.COMMAND_GET_MESSAGE_RESP, messageRespBody); 57 | WsResponse response = WsResponse.fromText(success, Im.CHARSET); 58 | Im.send(channelContext, response); 59 | 60 | return null; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/message/MessageSearchReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.message; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.date.DateUtil; 5 | import cn.hutool.core.util.StrUtil; 6 | import com.alibaba.fastjson.JSON; 7 | import org.example.commond.AbstractCmdHandler; 8 | import org.example.config.Im; 9 | import org.example.enums.CommandEnum; 10 | import org.example.packets.bean.Message; 11 | import org.example.packets.bean.User; 12 | import org.example.packets.handler.message.ChatRespBody; 13 | import org.example.packets.handler.message.MessageSearchReqBody; 14 | import org.example.packets.handler.system.RespBody; 15 | import org.tio.core.ChannelContext; 16 | import org.tio.core.intf.Packet; 17 | import org.tio.websocket.common.WsRequest; 18 | import org.tio.websocket.common.WsResponse; 19 | 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | public class MessageSearchReqHandler extends AbstractCmdHandler { 24 | 25 | @Override 26 | public CommandEnum command() { 27 | return CommandEnum.COMMAND_SEARCH_MESSAGE_REQ; 28 | } 29 | 30 | @Override 31 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 32 | 33 | WsRequest request = (WsRequest) packet; 34 | 35 | MessageSearchReqBody messageReqBody = JSON.parseObject(request.getBody(), MessageSearchReqBody.class); 36 | 37 | Long beginTime = null; 38 | if (StrUtil.isNotBlank(messageReqBody.getStartDate())) { 39 | beginTime = DateUtil.beginOfDay(DateUtil.parse(messageReqBody.getStartDate())).getTime(); 40 | } 41 | Long endTime = null; 42 | if (StrUtil.isNotBlank(messageReqBody.getEndDate())) { 43 | endTime = DateUtil.beginOfDay(DateUtil.parse(messageReqBody.getEndDate())).getTime(); 44 | } 45 | 46 | List messages = messageService.getMessage(messageReqBody.getRoomId(), messageReqBody.getContent(), beginTime, endTime); 47 | 48 | User user = Im.getUser(channelContext); 49 | 50 | List collect = messages.stream().map(x -> { 51 | ChatRespBody chatRespBody = BeanUtil.copyProperties(x, ChatRespBody.class); 52 | User userInfo = userService.getUserInfo(chatRespBody.getSenderId()); 53 | chatRespBody.setAvatar(userInfo.getAvatar()); 54 | chatRespBody.setUsername(userInfo.getUsername()); 55 | chatRespBody.setCurrentUserId(user.getId()); 56 | return chatRespBody; 57 | }).collect(Collectors.toList()); 58 | 59 | String success = RespBody.success(CommandEnum.COMMAND_SEARCH_MESSAGE_RESP, collect); 60 | WsResponse response = WsResponse.fromText(success, Im.CHARSET); 61 | Im.send(channelContext, response); 62 | 63 | return null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/room/CreatGroupReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.room; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.util.IdUtil; 5 | import cn.hutool.core.util.StrUtil; 6 | import com.alibaba.fastjson.JSON; 7 | import com.alibaba.fastjson.JSONObject; 8 | import com.alibaba.fastjson.serializer.SerializerFeature; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.example.commond.AbstractCmdHandler; 11 | import org.example.commond.CommandManager; 12 | import org.example.config.Im; 13 | import org.example.enums.CommandEnum; 14 | import org.example.enums.DefaultEnum; 15 | import org.example.enums.JoinGroupEnum; 16 | import org.example.enums.RoomRoleEnum; 17 | import org.example.packets.bean.FriendInfo; 18 | import org.example.packets.bean.Group; 19 | import org.example.packets.bean.User; 20 | import org.example.packets.handler.room.CreateGroupReqBody; 21 | import org.example.packets.handler.room.JoinGroupNotifyBody; 22 | import org.example.protocol.http.service.UploadService; 23 | import org.tio.core.ChannelContext; 24 | import org.tio.core.intf.Packet; 25 | import org.tio.websocket.common.WsRequest; 26 | import org.tio.websocket.common.WsResponse; 27 | 28 | import java.util.ArrayList; 29 | 30 | @Slf4j 31 | public class CreatGroupReqHandler extends AbstractCmdHandler { 32 | 33 | @Override 34 | public CommandEnum command() { 35 | return CommandEnum.COMMAND_CREATE_GROUP_REQ; 36 | } 37 | 38 | @Override 39 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 40 | 41 | log.info("创建群组"); 42 | WsRequest httpPacket = (WsRequest) packet; 43 | log.info(httpPacket.getWsBodyText()); 44 | CreateGroupReqBody request = JSONObject.parseObject(httpPacket.getBody(), CreateGroupReqBody.class); 45 | 46 | String roomName = request.getRoomName(); 47 | // 当前用户, 并设置用户为管理员 48 | User user = Im.getUser(channelContext, false); 49 | user.setRole(RoomRoleEnum.ADMIN); 50 | 51 | // 如果是好友的情况下校验是不是好友 52 | if(request.getIsFriend()) { 53 | FriendInfo roomInfo = friendInfoService.getRoomInfo(user.getId(), request.getUsers().get(0).getId()); 54 | if(roomInfo != null){ 55 | return null; 56 | } 57 | } 58 | 59 | String url = UploadService.uploadDefault(DefaultEnum.ACCOUNT_GROUP); 60 | // 创建群聊 61 | Group build = Group.builder().roomId(IdUtil.getSnowflake().nextIdStr()) 62 | .isFriend(request.getIsFriend()).index(System.currentTimeMillis()).roomName(roomName) 63 | .publicRoom(request.getPublicRoom()) 64 | .users(new ArrayList<>()) 65 | .build(); 66 | request.getUsers().add(user); 67 | if (!request.getIsFriend()) { 68 | build.setAvatar(request.getAvatar()); 69 | } 70 | if (!request.getIsFriend() && StrUtil.isBlank(request.getAvatar())) { 71 | build.setAvatar(url); 72 | } 73 | for (User addUser : request.getUsers()) { 74 | User userInfo = userService.getUserInfo(addUser.getId()); 75 | build.getUsers().add(userInfo); 76 | BeanUtil.copyProperties(userInfo, addUser,"role"); 77 | } 78 | 79 | // 如果是好友会话 1. 将群组的好友ID和备注字段放进各自的信息中 80 | if (request.getIsFriend()) { 81 | friendInfoService.createFriendTwoWay(build.getRoomId(), user.getId(), request.getUsers().get(0).getId()); 82 | } 83 | 84 | groupService.saveOrUpdateById(build); 85 | JoinGroupNotifyBody joinGroupNotifyBody = JoinGroupNotifyBody.builder().group(build).users(request.getUsers()) 86 | .code(JoinGroupEnum.STATE_CREATE.getValue()).build(); 87 | 88 | // 发送加入群聊消息 89 | WsRequest wsRequest = WsRequest.fromText(JSON.toJSONString(joinGroupNotifyBody, SerializerFeature.DisableCircularReferenceDetect), Im.CHARSET); 90 | AbstractCmdHandler command = CommandManager.getCommand(CommandEnum.COMMAND_JOIN_GROUP_REQ); 91 | command.handler(wsRequest, channelContext); 92 | 93 | return null; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/room/DisbandGroupHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.room; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.example.commond.AbstractCmdHandler; 5 | import org.example.commond.CommandManager; 6 | import org.example.config.Im; 7 | import org.example.enums.CommandEnum; 8 | import org.example.packets.bean.Group; 9 | import org.example.packets.bean.User; 10 | import org.example.packets.handler.system.RespBody; 11 | import org.example.packets.handler.room.GroupUserReqBody; 12 | import org.example.packets.handler.system.SystemTextMessage; 13 | import org.tio.core.ChannelContext; 14 | import org.tio.core.intf.Packet; 15 | import org.tio.websocket.common.WsRequest; 16 | import org.tio.websocket.common.WsResponse; 17 | 18 | import java.util.List; 19 | import java.util.stream.Collectors; 20 | 21 | public class DisbandGroupHandler extends AbstractCmdHandler { 22 | 23 | @Override 24 | public CommandEnum command() { 25 | return CommandEnum.COMMAND_DISBAND_GROUP_REQ; 26 | } 27 | 28 | @Override 29 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 30 | 31 | WsRequest request = (WsRequest) packet; 32 | 33 | GroupUserReqBody body = JSON.parseObject(request.getBody(), GroupUserReqBody.class); 34 | 35 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_DISBAND_GROUP_RESP, body), Im.CHARSET); 36 | 37 | Im.bSendToGroup(body.getRoomId(), response); 38 | 39 | 40 | Group groupInfo = groupService.getGroupInfo(body.getRoomId()); 41 | 42 | groupService.delete(body.getRoomId()); 43 | 44 | 45 | // 移除群组 (给当前群组所有的人员发送系统消息) 46 | List groupUsers = userGroupService.getGroupUsers(body.getRoomId()); 47 | List userIds = groupUsers.stream().map(User::getId).collect(Collectors.toList()); 48 | 49 | User user = Im.getUser(channelContext); 50 | 51 | SystemTextMessage systemMessageReqBody = SystemTextMessage.create("群聊" + groupInfo.getRoomName() + "已被群主" + user.getUsername() + "解散!", userIds); 52 | 53 | WsRequest wsRequest = WsRequest.fromText(JSON.toJSONString(systemMessageReqBody), Im.CHARSET); 54 | AbstractCmdHandler command = CommandManager.getCommand(CommandEnum.COMMAND_SYSTEM_MESSAGE_REQ); 55 | command.handler(wsRequest, channelContext); 56 | 57 | userGroupService.delete(body.getRoomId()); 58 | 59 | Im.removeGroup(body.getRoomId()); 60 | 61 | return null; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/room/EditGroupProfileReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.room; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.serializer.SerializerFeature; 6 | import org.example.commond.AbstractCmdHandler; 7 | import org.example.commond.CommandManager; 8 | import org.example.config.Im; 9 | import org.example.enums.CommandEnum; 10 | import org.example.packets.bean.Group; 11 | import org.example.packets.bean.User; 12 | import org.example.packets.handler.message.ChatReqBody; 13 | import org.example.packets.handler.system.RespBody; 14 | import org.example.packets.handler.room.GroupProfileReqBody; 15 | import org.tio.core.ChannelContext; 16 | import org.tio.core.intf.Packet; 17 | import org.tio.websocket.common.WsRequest; 18 | import org.tio.websocket.common.WsResponse; 19 | 20 | import java.util.stream.Collectors; 21 | 22 | public class EditGroupProfileReqHandler extends AbstractCmdHandler { 23 | 24 | @Override 25 | public CommandEnum command() { 26 | return CommandEnum.COMMAND_EDIT_GROUP_PROFILE_REQ; 27 | } 28 | 29 | @Override 30 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 31 | 32 | WsRequest request = (WsRequest) packet; 33 | 34 | GroupProfileReqBody body = JSON.parseObject(request.getBody(), GroupProfileReqBody.class); 35 | boolean changeName = false; 36 | boolean changeAvatar = false; 37 | Group groupInfo = groupService.getGroupInfo(body.getRoomId()); 38 | if (!groupInfo.getRoomName().equals(body.getRoomName())) { 39 | changeName = true; 40 | } 41 | groupInfo.setRoomName(body.getRoomName()); 42 | if (StrUtil.isNotBlank(body.getAvatar())) { 43 | groupInfo.setAvatar(body.getAvatar()); 44 | changeAvatar = true; 45 | } else { 46 | body.setAvatar(groupInfo.getAvatar()); 47 | } 48 | 49 | groupService.updateById(groupInfo); 50 | 51 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_EDIT_GROUP_PROFILE_RESP, body), Im.CHARSET); 52 | Im.sendToGroup(body.getRoomId(), response); 53 | 54 | // 发送修改信息 55 | User nowUser = Im.getUser(channelContext); 56 | AbstractCmdHandler command = CommandManager.getCommand(CommandEnum.COMMAND_CHAT_REQ); 57 | if (changeName) { 58 | ChatReqBody chatReqBody = ChatReqBody.buildSystem(body.getRoomId(), nowUser.getId(), nowUser.getUsername() + "修改了群名称"); 59 | WsRequest wsRequest = WsRequest.fromText(JSON.toJSONString(chatReqBody, SerializerFeature.DisableCircularReferenceDetect), Im.CHARSET); 60 | command.handler(wsRequest, channelContext); 61 | } 62 | if (changeAvatar) { 63 | ChatReqBody chatReqBody = ChatReqBody.buildSystem(body.getRoomId(), nowUser.getId(), nowUser.getUsername() + "修改了群头像"); 64 | WsRequest wsRequest = WsRequest.fromText(JSON.toJSONString(chatReqBody, SerializerFeature.DisableCircularReferenceDetect), Im.CHARSET); 65 | command.handler(wsRequest, channelContext); 66 | } 67 | 68 | return null; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/room/HandoverGroupHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.room; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.serializer.SerializerFeature; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.commond.CommandManager; 7 | import org.example.config.Im; 8 | import org.example.enums.CommandEnum; 9 | import org.example.enums.RoomRoleEnum; 10 | import org.example.packets.bean.User; 11 | import org.example.packets.bean.UserGroup; 12 | import org.example.packets.handler.message.ChatReqBody; 13 | import org.example.packets.handler.system.RespBody; 14 | import org.example.packets.handler.room.GroupUserReqBody; 15 | import org.example.packets.handler.room.HandoverGroupRespBody; 16 | import org.tio.core.ChannelContext; 17 | import org.tio.core.intf.Packet; 18 | import org.tio.websocket.common.WsRequest; 19 | import org.tio.websocket.common.WsResponse; 20 | 21 | public class HandoverGroupHandler extends AbstractCmdHandler { 22 | @Override 23 | public CommandEnum command() { 24 | return CommandEnum.COMMAND_HANDOVER_GROUP_REQ; 25 | } 26 | 27 | @Override 28 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 29 | 30 | WsRequest request = (WsRequest) packet; 31 | 32 | GroupUserReqBody body = JSON.parseObject(request.getBody(), GroupUserReqBody.class); 33 | 34 | User user = Im.getUser(channelContext); 35 | UserGroup userGroup = userGroupService.getUserGroup(body.getRoomId(), user.getId()); 36 | if (userGroup == null) { 37 | return null; 38 | } 39 | if (!RoomRoleEnum.ADMIN.equals(userGroup.getRole())) { 40 | 41 | WsResponse response = WsResponse.fromText(RespBody.fail(CommandEnum.COMMAND_HANDOVER_GROUP_RESP, "你不是群主"), Im.CHARSET); 42 | Im.send(channelContext, response); 43 | return null; 44 | } 45 | UserGroup wait = userGroupService.getUserGroup(body.getRoomId(), body.getUserId()); 46 | wait.setRole(RoomRoleEnum.ADMIN); 47 | userGroupService.update(wait); 48 | 49 | userGroup.setRole(RoomRoleEnum.GENERAL); 50 | userGroupService.update(userGroup); 51 | 52 | HandoverGroupRespBody handoverGroupRespBody = new HandoverGroupRespBody(body.getRoomId(), userGroup.getUserId(), wait.getUserId()); 53 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_HANDOVER_GROUP_RESP, handoverGroupRespBody), Im.CHARSET); 54 | Im.sendToGroup(body.getRoomId(), response); 55 | 56 | 57 | // 移交群主消息 58 | AbstractCmdHandler command = CommandManager.getCommand(CommandEnum.COMMAND_CHAT_REQ); 59 | // 发送退出群聊消息 60 | String content = "群主已由" + "\"" + user.getUsername() + "\" 变更为" + "\"" + userService.getUserInfo(body.getUserId()).getUsername() + "\" "; 61 | ChatReqBody chatReqBody = ChatReqBody.buildSystem(body.getRoomId(), user.getId(), content); 62 | 63 | WsRequest wsRequest = WsRequest.fromText(JSON.toJSONString(chatReqBody, SerializerFeature.DisableCircularReferenceDetect), Im.CHARSET); 64 | 65 | command.handler(wsRequest, channelContext); 66 | 67 | return null; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/room/RemoveGroupUserReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.room; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.serializer.SerializerFeature; 6 | import org.example.commond.AbstractCmdHandler; 7 | import org.example.commond.CommandManager; 8 | import org.example.config.Im; 9 | import org.example.enums.CommandEnum; 10 | import org.example.enums.GroupOutTypeEnum; 11 | import org.example.packets.bean.Group; 12 | import org.example.packets.bean.User; 13 | import org.example.packets.handler.message.ChatReqBody; 14 | import org.example.packets.handler.system.RespBody; 15 | import org.example.packets.handler.room.GroupUserReqBody; 16 | import org.tio.core.ChannelContext; 17 | import org.tio.core.intf.Packet; 18 | import org.tio.websocket.common.WsRequest; 19 | import org.tio.websocket.common.WsResponse; 20 | 21 | import java.util.List; 22 | 23 | public class RemoveGroupUserReqHandler extends AbstractCmdHandler { 24 | 25 | @Override 26 | public CommandEnum command() { 27 | return CommandEnum.COMMAND_REMOVE_GROUP_USER_REQ; 28 | } 29 | 30 | @Override 31 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 32 | WsRequest request = (WsRequest) packet; 33 | GroupUserReqBody body = JSON.parseObject(request.getBody(), GroupUserReqBody.class); 34 | 35 | // 给剩下的人发送人员离开消息 36 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_REMOVE_GROUP_USER_RESP, body), Im.CHARSET); 37 | Im.sendToGroup(body.getRoomId(), response); 38 | 39 | Group group = groupService.getGroupInfo(body.getRoomId()); 40 | if (group.getIsFriend()) { 41 | 42 | } 43 | userGroupService.remove(body.getRoomId(), body.getUserId()); 44 | 45 | // 将当前所有的连接端都解除掉 46 | List channelContexts = Im.getChannelByUserId(body.getUserId()); 47 | if (CollUtil.isNotEmpty(channelContexts)) { 48 | channelContexts.forEach(x -> { 49 | Im.unBindGroup(x, body.getRoomId()); 50 | }); 51 | } 52 | 53 | User user = Im.getUser(channelContext); 54 | AbstractCmdHandler command = CommandManager.getCommand(CommandEnum.COMMAND_CHAT_REQ); 55 | // 发送退出群聊消息 56 | String content = "\"" + userService.getUserInfo(body.getUserId()).getUsername() + "\" " + (body.getType().equals(GroupOutTypeEnum.OUT) ? "已退出群聊" : "已被移出群聊"); 57 | ChatReqBody chatReqBody = ChatReqBody.buildSystem(body.getRoomId(), user.getId(), content); 58 | WsRequest wsRequest = WsRequest.fromText(JSON.toJSONString(chatReqBody, SerializerFeature.DisableCircularReferenceDetect), Im.CHARSET); 59 | 60 | command.handler(wsRequest, channelContext); 61 | 62 | return null; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/room/SearchRoomReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.room; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.config.Im; 7 | import org.example.enums.CommandEnum; 8 | import org.example.packets.bean.Group; 9 | import org.example.packets.bean.User; 10 | import org.example.packets.handler.room.SearchRoomReqBody; 11 | import org.example.packets.handler.room.SearchRoomRespBody; 12 | import org.example.packets.handler.system.RespBody; 13 | import org.tio.core.ChannelContext; 14 | import org.tio.core.intf.Packet; 15 | import org.tio.websocket.common.WsRequest; 16 | import org.tio.websocket.common.WsResponse; 17 | 18 | import java.util.List; 19 | 20 | @Slf4j 21 | public class SearchRoomReqHandler extends AbstractCmdHandler { 22 | 23 | 24 | @Override 25 | public CommandEnum command() { 26 | return CommandEnum.COMMAND_SEARCH_ROOM_REQ; 27 | } 28 | 29 | @Override 30 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 31 | WsRequest request = (WsRequest) packet; 32 | User user = Im.getUser(channelContext); 33 | if(user == null){ 34 | log.info("当前用户获取失败"); 35 | return null; 36 | } 37 | 38 | SearchRoomReqBody reqBody = JSON.parseObject(request.getBody(), SearchRoomReqBody.class); 39 | 40 | SearchRoomRespBody respBody = new SearchRoomRespBody(); 41 | respBody.setSearchId(reqBody.getSearchId()); 42 | 43 | List roomList = groupService.getRoomList(reqBody.getName(), reqBody.getRoomId()); 44 | 45 | respBody.setRoomList(roomList); 46 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_SEARCH_ROOM_RESP, respBody), Im.CHARSET); 47 | 48 | Im.send(channelContext,response); 49 | return null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/room/SetPublicRoomReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.room; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.serializer.SerializerFeature; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.commond.CommandManager; 7 | import org.example.config.Im; 8 | import org.example.enums.CommandEnum; 9 | import org.example.enums.GroupOutTypeEnum; 10 | import org.example.packets.bean.Group; 11 | import org.example.packets.bean.User; 12 | import org.example.packets.handler.message.ChatReqBody; 13 | import org.example.packets.handler.room.GroupAdminReqBody; 14 | import org.example.packets.handler.room.SetPublicRoomReqBody; 15 | import org.example.packets.handler.system.RespBody; 16 | import org.tio.core.ChannelContext; 17 | import org.tio.core.intf.Packet; 18 | import org.tio.websocket.common.WsRequest; 19 | import org.tio.websocket.common.WsResponse; 20 | 21 | public class SetPublicRoomReqHandler extends AbstractCmdHandler { 22 | 23 | @Override 24 | public CommandEnum command() { 25 | return CommandEnum.COMMAND_SET_PUBLIC_ROOM_REQ; 26 | } 27 | 28 | @Override 29 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 30 | WsRequest request = (WsRequest) packet; 31 | 32 | SetPublicRoomReqBody body = JSON.parseObject(request.getBody(), SetPublicRoomReqBody.class); 33 | 34 | Group groupInfo = groupService.getGroupInfo(body.getRoomId()); 35 | groupInfo.setPublicRoom(body.getPublicRoom()); 36 | groupService.updateById(groupInfo); 37 | 38 | 39 | WsResponse wsResponse = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_SET_PUBLIC_ROOM_RESP), Im.CHARSET); 40 | Im.send(channelContext, wsResponse); 41 | return null; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/room/SetRoomAdminReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.room; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.serializer.SerializerFeature; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.commond.CommandManager; 7 | import org.example.config.Chat; 8 | import org.example.config.Im; 9 | import org.example.enums.CommandEnum; 10 | import org.example.enums.GroupAdminTypeEnum; 11 | import org.example.enums.RoomRoleEnum; 12 | import org.example.packets.bean.User; 13 | import org.example.packets.bean.UserGroup; 14 | import org.example.packets.handler.message.ChatReqBody; 15 | import org.example.packets.handler.user.UserStatusBody; 16 | import org.example.packets.handler.room.GroupAdminReqBody; 17 | import org.tio.core.ChannelContext; 18 | import org.tio.core.intf.Packet; 19 | import org.tio.websocket.common.WsRequest; 20 | import org.tio.websocket.common.WsResponse; 21 | 22 | public class SetRoomAdminReqHandler extends AbstractCmdHandler { 23 | 24 | @Override 25 | public CommandEnum command() { 26 | return CommandEnum.COMMAND_SET_ROOM_ADMIN_REQ; 27 | } 28 | 29 | @Override 30 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 31 | 32 | WsRequest request = (WsRequest) packet; 33 | 34 | GroupAdminReqBody body = JSON.parseObject(request.getBody(), GroupAdminReqBody.class); 35 | 36 | User user = Im.getUser(channelContext); 37 | 38 | UserGroup userGroup = userGroupService.getUserGroup(body.getRoomId(), body.getUserId()); 39 | if (userGroup == null) { 40 | return null; 41 | } 42 | 43 | userGroup.setRole(body.getType().equals(GroupAdminTypeEnum.SET) ? RoomRoleEnum.SUB_ADMIN : RoomRoleEnum.GENERAL); 44 | userGroupService.update(userGroup); 45 | 46 | User userInfo = userService.getUserInfo(body.getUserId()); 47 | userInfo.setRole(userGroup.getRole()); 48 | 49 | // 用户状态变化消息 50 | UserStatusBody userStatusBody = new UserStatusBody(); 51 | userStatusBody.setGroup(groupService.getGroupInfo(body.getRoomId())); 52 | userStatusBody.setUser(userInfo); 53 | 54 | Chat.sendToGroup(userStatusBody, channelContext, true); 55 | 56 | // 移交群主消息 57 | AbstractCmdHandler command = CommandManager.getCommand(CommandEnum.COMMAND_CHAT_REQ); 58 | String content; 59 | if (body.getType().equals(GroupAdminTypeEnum.SET)) { 60 | // 发送退出群聊消息 61 | content = user.getUsername() + "已设置\"" + userService.getUserInfo(body.getUserId()).getUsername() + "\"为管理员"; 62 | } else { 63 | content = user.getUsername() + "已解除\"" + userService.getUserInfo(body.getUserId()).getUsername() + "\"管理员身份"; 64 | } 65 | 66 | ChatReqBody chatReqBody = ChatReqBody.buildSystem(body.getRoomId(), user.getId(), content); 67 | 68 | WsRequest wsRequest = WsRequest.fromText(JSON.toJSONString(chatReqBody, SerializerFeature.DisableCircularReferenceDetect), Im.CHARSET); 69 | 70 | command.handler(wsRequest, channelContext); 71 | return null; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/room/UserGroupConfigReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.room; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.example.commond.AbstractCmdHandler; 5 | import org.example.config.Im; 6 | import org.example.enums.CommandEnum; 7 | import org.example.packets.bean.Group; 8 | import org.example.packets.bean.User; 9 | import org.example.packets.bean.UserGroup; 10 | import org.example.packets.handler.system.RespBody; 11 | import org.example.packets.handler.room.UserGroupConfigReqBody; 12 | import org.tio.core.ChannelContext; 13 | import org.tio.core.intf.Packet; 14 | import org.tio.websocket.common.WsRequest; 15 | import org.tio.websocket.common.WsResponse; 16 | 17 | /** 18 | * 群组用户修改自己的配置 19 | *

20 | * 通知 / 群组备注 / 成员备注 等 21 | *

22 | * 23 | * @author smart 24 | * @since 1.0.0 25 | */ 26 | public class UserGroupConfigReqHandler extends AbstractCmdHandler { 27 | 28 | @Override 29 | public CommandEnum command() { 30 | return CommandEnum.COMMAND_USER_GROUP_CONFIG_REQ; 31 | } 32 | 33 | @Override 34 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 35 | 36 | WsRequest request = (WsRequest) packet; 37 | 38 | UserGroupConfigReqBody body = JSON.parseObject(request.getBody(), UserGroupConfigReqBody.class); 39 | 40 | switch (body.getType()) { 41 | case NOTICE: 42 | setNotice(body, channelContext); 43 | break; 44 | case GROUP_REMARK: 45 | setGroupRemark(body, channelContext); 46 | break; 47 | case MOVE_TOP: 48 | setMoveTop(body, channelContext); 49 | break; 50 | default: 51 | break; 52 | } 53 | 54 | return null; 55 | } 56 | 57 | private void setMoveTop(UserGroupConfigReqBody body, ChannelContext channelContext) { 58 | User user = Im.getUser(channelContext); 59 | UserGroup userGroup = userGroupService.getUserGroup(body.getRoomId(), user.getId()); 60 | userGroup.setTop(body.getMoveTop()); 61 | userGroupService.update(userGroup); 62 | 63 | if (Boolean.TRUE.equals(body.getMoveTop())) { 64 | body.setIndex(9999999999999L); 65 | } else { 66 | Group group = groupService.getGroupInfo(body.getRoomId()); 67 | body.setIndex(group.getIndex()); 68 | } 69 | 70 | String success = RespBody.success(CommandEnum.COMMAND_USER_GROUP_CONFIG_RESP, body); 71 | WsResponse response = WsResponse.fromText(success, Im.CHARSET); 72 | Im.send(channelContext, response); 73 | 74 | } 75 | 76 | /** 77 | * 设置通知 78 | * 79 | * @param config 配置信息 80 | * @param channelContext 上下文信息 81 | */ 82 | private void setNotice(UserGroupConfigReqBody config, ChannelContext channelContext) { 83 | 84 | User user = Im.getUser(channelContext); 85 | UserGroup userGroup = userGroupService.getUserGroup(config.getRoomId(), user.getId()); 86 | userGroup.setNotice(config.getNotice()); 87 | userGroupService.update(userGroup); 88 | 89 | WsResponse wsResponse = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_USER_GROUP_CONFIG_RESP, config), Im.CHARSET); 90 | Im.send(channelContext, wsResponse); 91 | } 92 | 93 | /** 94 | * 设置群组备注 95 | * 96 | * @param config 配置信息 97 | * @param channelContext 上下文信息 98 | */ 99 | private void setGroupRemark(UserGroupConfigReqBody config, ChannelContext channelContext) { 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/system/EditProfileHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.system; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.config.Chat; 7 | import org.example.config.Im; 8 | import org.example.enums.CommandEnum; 9 | import org.example.packets.bean.Group; 10 | import org.example.packets.bean.User; 11 | import org.example.packets.handler.user.EditProfileReqBody; 12 | import org.example.packets.handler.system.RespBody; 13 | import org.example.packets.handler.user.UserStatusBody; 14 | import org.tio.core.ChannelContext; 15 | import org.tio.core.intf.Packet; 16 | import org.tio.websocket.common.WsRequest; 17 | import org.tio.websocket.common.WsResponse; 18 | 19 | import java.util.List; 20 | 21 | public class EditProfileHandler extends AbstractCmdHandler { 22 | 23 | @Override 24 | public CommandEnum command() { 25 | return CommandEnum.COMMAND_EDIT_PROFILE_REQ; 26 | } 27 | 28 | @Override 29 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 30 | 31 | WsRequest request = (WsRequest) packet; 32 | 33 | EditProfileReqBody editProfileReqBody = JSON.parseObject(request.getBody(), EditProfileReqBody.class); 34 | 35 | User userInfo = userService.getUserInfo(editProfileReqBody.getUserId()); 36 | if (StrUtil.isNotBlank(editProfileReqBody.getAvatar())) { 37 | userInfo.setAvatar(editProfileReqBody.getAvatar()); 38 | } 39 | 40 | if (StrUtil.isNotBlank(editProfileReqBody.getName())) { 41 | userInfo.setUsername(editProfileReqBody.getName()); 42 | } 43 | userService.updateById(userInfo); 44 | Im.getUser(channelContext).setUsername(userInfo.getUsername()); 45 | Im.getUser(channelContext).setAvatar(userInfo.getAvatar()); 46 | 47 | // 发送修改响应消息 48 | UserStatusBody userStatusBody = new UserStatusBody(); 49 | userStatusBody.setUser(userInfo); 50 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_EDIT_PROFILE_RESP, userStatusBody), Im.CHARSET); 51 | 52 | Im.send(channelContext, response); 53 | 54 | // 给用户所在的群组发送消息 55 | List userGroups = userGroupService.getUserGroups(userInfo.getId()); 56 | for (Group userGroup : userGroups) { 57 | /* List groupUsers = messageHelper.getGroupUsers(userGroup.getRoomId()); 58 | userGroup.setUsers(groupUsers);*/ 59 | userStatusBody.setGroup(userGroup); 60 | Chat.sendToGroup(userStatusBody, channelContext, true); 61 | } 62 | 63 | return null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/commond/handler/system/SetNewPasswordReqHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.commond.handler.system; 2 | 3 | import cn.hutool.crypto.SecureUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.config.Im; 7 | import org.example.enums.CommandEnum; 8 | import org.example.packets.bean.Auth; 9 | import org.example.packets.bean.User; 10 | import org.example.packets.handler.system.RespBody; 11 | import org.example.packets.handler.system.SetNewPasswordReqBody; 12 | import org.example.packets.handler.user.EditProfileReqBody; 13 | import org.tio.core.ChannelContext; 14 | import org.tio.core.intf.Packet; 15 | import org.tio.websocket.common.WsRequest; 16 | import org.tio.websocket.common.WsResponse; 17 | 18 | public class SetNewPasswordReqHandler extends AbstractCmdHandler { 19 | @Override 20 | public CommandEnum command() { 21 | return CommandEnum.COMMAND_SET_NEW_PASSWORD_REQ; 22 | } 23 | 24 | @Override 25 | public WsResponse handler(Packet packet, ChannelContext channelContext) { 26 | WsRequest request = (WsRequest) packet; 27 | 28 | SetNewPasswordReqBody newPasswordReqBody = JSON.parseObject(request.getBody(), SetNewPasswordReqBody.class); 29 | 30 | if(!newPasswordReqBody.getPassword().equals(newPasswordReqBody.getRepeatPassword())){ 31 | // 发送修改响应消息 32 | WsResponse response = WsResponse.fromText(RespBody.fail(CommandEnum.COMMAND_SET_NEW_PASSWORD_RESP, "密码与重复密码不相同"), Im.CHARSET); 33 | Im.send(channelContext, response); 34 | return null; 35 | } 36 | User user = Im.getUser(channelContext); 37 | Auth auth = authService.getByUserId(user.getId()); 38 | if(!auth.getPassword().equals(SecureUtil.md5(newPasswordReqBody.getOldPassword()))){ 39 | // 发送修改响应消息 40 | WsResponse response = WsResponse.fromText(RespBody.fail(CommandEnum.COMMAND_SET_NEW_PASSWORD_RESP, "旧密码不正确"), Im.CHARSET); 41 | Im.send(channelContext, response); 42 | return null; 43 | } 44 | auth.setPassword(SecureUtil.md5(newPasswordReqBody.getPassword())); 45 | authService.update(auth); 46 | 47 | // 发送修改响应消息 48 | WsResponse response = WsResponse.fromText(RespBody.success(CommandEnum.COMMAND_SET_NEW_PASSWORD_RESP), Im.CHARSET); 49 | Im.send(channelContext, response); 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/config/ImServerHttpStart.java: -------------------------------------------------------------------------------- 1 | package org.example.config; 2 | 3 | import org.example.ImServer; 4 | import org.tio.http.common.HttpConfig; 5 | import org.tio.http.common.handler.HttpRequestHandler; 6 | import org.tio.http.server.HttpServerStarter; 7 | import org.tio.http.server.handler.DefaultHttpRequestHandler; 8 | import org.tio.server.TioServerConfig; 9 | 10 | public class ImServerHttpStart { 11 | 12 | public static HttpConfig httpConfig; 13 | 14 | public static HttpRequestHandler requestHandler; 15 | 16 | public static HttpServerStarter httpServerStarter; 17 | 18 | public static TioServerConfig serverTioConfig; 19 | 20 | public static void start() throws Exception { 21 | 22 | httpConfig = new HttpConfig(CourierConfig.httpPort, null, null, null); 23 | httpConfig.setPageRoot("classpath:page"); 24 | httpConfig.setMaxLiveTimeOfStaticRes(CourierConfig.httpMaxLiveTime); 25 | httpConfig.setPage404("404.html"); 26 | httpConfig.setPage500("500.html"); 27 | httpConfig.setUseSession(CourierConfig.httpUseSession); 28 | httpConfig.setCheckHost(CourierConfig.httpCheckHost); 29 | 30 | requestHandler = new DefaultHttpRequestHandler(httpConfig, ImServer.class);//第二个参数也可以是数组 31 | 32 | httpServerStarter = new HttpServerStarter(httpConfig, requestHandler); 33 | serverTioConfig = httpServerStarter.getTioServerConfig(); 34 | httpServerStarter.start(); //启动http服务器 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/config/ImServerWebSocketStart.java: -------------------------------------------------------------------------------- 1 | package org.example.config; 2 | 3 | import org.example.listener.*; 4 | import org.example.protocol.ws.WsMsgHandler; 5 | import org.tio.server.TioServerConfig; 6 | import org.tio.websocket.server.WsServerStarter; 7 | 8 | import java.io.IOException; 9 | 10 | public class ImServerWebSocketStart { 11 | 12 | private final WsServerStarter wsServerStarter; 13 | private final TioServerConfig serverTioConfig; 14 | 15 | 16 | public ImServerWebSocketStart(int port, WsMsgHandler wsMsgHandler) throws IOException { 17 | wsServerStarter = new WsServerStarter(port, wsMsgHandler); 18 | 19 | serverTioConfig = wsServerStarter.getTioServerConfig(); 20 | serverTioConfig.setGroupListener(new ImGroupListenerAdapter(new ImServerGroupListener())); 21 | serverTioConfig.setName(ImConfig.PROTOCOL_NAME); 22 | serverTioConfig.setTioServerListener(ImTioServerListener.me); 23 | serverTioConfig.ipStats.addDurations(ImConfig.IpStatDuration.IPSTAT_DURATIONS); 24 | serverTioConfig.setHeartbeatTimeout(CourierConfig.socketHeartbeat); 25 | } 26 | 27 | public static void start() throws Exception { 28 | ImServerWebSocketStart appStarter = new ImServerWebSocketStart(CourierConfig.socketPort, WsMsgHandler.me); 29 | 30 | ImConfig imServerConfig = new ImConfig(); 31 | // imServerConfig.setMessageHelper(new MongoMessageHelper()); 32 | imServerConfig.setImUserListener(new ImUserListenerAdapter(new ImServerUserListener())); 33 | imServerConfig.setTioConfig(appStarter.serverTioConfig); 34 | 35 | appStarter.wsServerStarter.start(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/listener/ImServerGroupListener.java: -------------------------------------------------------------------------------- 1 | package org.example.listener; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.example.config.Im; 5 | import org.example.packets.bean.Group; 6 | import org.example.packets.bean.User; 7 | import org.example.service.GroupService; 8 | import org.example.service.UserGroupService; 9 | import org.tio.core.ChannelContext; 10 | 11 | @Slf4j 12 | public class ImServerGroupListener extends AbstractImGroupListener { 13 | 14 | public final UserGroupService userGroupService = new UserGroupService(); 15 | public final GroupService groupService = new GroupService(); 16 | 17 | @Override 18 | public void doAfterBind(ChannelContext channelContext, Group group) { 19 | log.info("群组绑定监听"); 20 | // 将绑定信息持久化到Redis 21 | // ImConfig.get().messageHelper.onAfterGroupBind(channelContext, group); 22 | 23 | String roomId = group.getRoomId(); 24 | User user = Im.getUser(channelContext); 25 | // 添加用户群组信息 顺手就完成了群组用户 用户群组的绑定 26 | // userGroupService.addGroupUser(roomId, user.getId()); 27 | 28 | // for (Group userGroup : user.getGroups()) { 29 | // if (!userGroup.getRoomId().equals(roomId)) { 30 | // continue; 31 | // } 32 | // groupService.updateById(userGroup); 33 | // } 34 | } 35 | 36 | @Override 37 | public void doAfterUnbind(ChannelContext channelContext, Group group) { 38 | log.info("群组绑监听"); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/listener/ImServerUserListener.java: -------------------------------------------------------------------------------- 1 | package org.example.listener; 2 | 3 | import org.example.packets.bean.User; 4 | import org.example.service.UserService; 5 | import org.tio.core.ChannelContext; 6 | 7 | public class ImServerUserListener extends AbstractImUserListener{ 8 | 9 | private final UserService userService ; 10 | 11 | public ImServerUserListener() { 12 | userService = new UserService(); 13 | } 14 | 15 | @Override 16 | public void doAfterBind(ChannelContext channelContext, User user) { 17 | userService.saveOrUpdate(user); 18 | } 19 | 20 | @Override 21 | public void doAfterUnbind(ChannelContext channelContext, User user) { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/listener/ImTioServerListener.java: -------------------------------------------------------------------------------- 1 | package org.example.listener; 2 | 3 | import org.example.config.Chat; 4 | import org.example.config.Im; 5 | import org.example.config.ImConfig; 6 | import org.example.config.ImSessionContext; 7 | import org.example.enums.KeyEnum; 8 | import org.example.packets.ImClientNode; 9 | import org.example.packets.bean.Group; 10 | import org.example.packets.bean.User; 11 | import org.example.packets.bean.UserGroup; 12 | import org.example.packets.handler.user.UserStatusBody; 13 | import org.example.service.UserGroupService; 14 | import org.example.service.UserService; 15 | import org.tio.core.ChannelContext; 16 | import org.tio.core.Node; 17 | import org.tio.core.Tio; 18 | import org.tio.core.intf.Packet; 19 | import org.tio.utils.lock.SetWithLock; 20 | import org.tio.websocket.server.WsTioServerListener; 21 | 22 | 23 | import java.util.List; 24 | 25 | public class ImTioServerListener extends WsTioServerListener { 26 | 27 | private final UserService userService = new UserService(); 28 | 29 | private final UserGroupService userGroupService = new UserGroupService(); 30 | 31 | public static final ImTioServerListener me = new ImTioServerListener(); 32 | 33 | @Override 34 | public boolean onHeartbeatTimeout(ChannelContext channelContext, Long interval, int heartbeatTimeoutCount) { 35 | return false; 36 | } 37 | 38 | @Override 39 | public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception { 40 | ImSessionContext sessionContext = new ImSessionContext(); 41 | Node clientNode = channelContext.getClientNode(); 42 | ImClientNode build = ImClientNode.builder().ip(clientNode.getIp()).port(clientNode.getPort()).build(); 43 | build.setId(channelContext.getId()); 44 | sessionContext.setImClientNode(build); 45 | channelContext.set(KeyEnum.IM_CHANNEL_SESSION_CONTEXT_KEY.getKey(), sessionContext); 46 | super.onAfterConnected(channelContext, isConnected, isReconnect); 47 | } 48 | 49 | @Override 50 | public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize) throws Exception { 51 | super.onAfterDecoded(channelContext, packet, packetSize); 52 | } 53 | 54 | @Override 55 | public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) throws Exception { 56 | super.onAfterReceivedBytes(channelContext, receivedBytes); 57 | } 58 | 59 | @Override 60 | public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) throws Exception { 61 | super.onAfterSent(channelContext, packet, isSentSuccess); 62 | } 63 | 64 | @Override 65 | public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) throws Exception { 66 | super.onAfterHandled(channelContext, packet, cost); 67 | } 68 | 69 | @Override 70 | public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception { 71 | 72 | 73 | User user = Im.getUser(channelContext); 74 | // 首先判断能不能拿到用户 75 | if (user != null) { 76 | // 然后判断是不是多机器登录, 当仅在当前登录时, 更新用户状态为离线 77 | SetWithLock userChannelContexts = Tio.getByUserid(ImConfig.get().getTioConfig(), user.getId()); 78 | if(userChannelContexts.size() == 1){ 79 | // 更新用户为离线状态 80 | userService.userOffline(user.getId()); 81 | 82 | UserStatusBody build = UserStatusBody.builder().user(userService.getUserInfo(user.getId())).build(); 83 | 84 | for (Group group : user.getGroups()) { 85 | UserGroup userGroup = userGroupService.getUserGroup(group.getRoomId(), user.getId()); 86 | build.getUser().setRole(userGroup.getRole()); 87 | // 给所在群组发送离线消息 用户状态更新 88 | List groupUsers = userGroupService.getGroupUsers(group.getRoomId()); 89 | group.setUsers(groupUsers); 90 | build.setGroup(group); 91 | Chat.sendToGroup(build, channelContext); 92 | } 93 | } 94 | 95 | } 96 | 97 | super.onBeforeClose(channelContext, throwable, remark, isRemove); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/protocol/http/LoginController.java: -------------------------------------------------------------------------------- 1 | package org.example.protocol.http; 2 | 3 | 4 | import cn.hutool.core.util.IdUtil; 5 | import cn.hutool.core.util.StrUtil; 6 | import cn.hutool.crypto.SecureUtil; 7 | import cn.hutool.jwt.JWTUtil; 8 | import com.alibaba.fastjson.JSON; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.example.enums.CommandEnum; 11 | import org.example.packets.bean.Auth; 12 | import org.example.packets.handler.system.LoginReqBody; 13 | import org.example.packets.handler.system.RespBody; 14 | import org.example.service.AuthService; 15 | import org.tio.http.common.HttpRequest; 16 | import org.tio.http.common.HttpResponse; 17 | import org.tio.http.server.annotation.RequestPath; 18 | import org.tio.http.server.util.Resps; 19 | 20 | import java.nio.charset.StandardCharsets; 21 | import java.util.HashMap; 22 | 23 | @Slf4j 24 | @RequestPath("/user") 25 | public class LoginController { 26 | 27 | private final AuthService authService; 28 | 29 | public LoginController() { 30 | authService = new AuthService(); 31 | } 32 | 33 | @RequestPath("/login") 34 | public HttpResponse login(HttpRequest httpRequest) { 35 | LoginReqBody loginReq = JSON.parseObject(httpRequest.getBodyString(), LoginReqBody.class); 36 | 37 | log.info(String.valueOf(loginReq)); 38 | 39 | if (StrUtil.isBlank(loginReq.getAccount())) { 40 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_LOGIN_RESP, "用户名不能为空")); 41 | } 42 | 43 | if (StrUtil.isBlank(loginReq.getPassword())) { 44 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_LOGIN_RESP, "密码不能为空")); 45 | } 46 | 47 | Auth auth = authService.getByAccount(loginReq.getAccount()); 48 | if (auth == null) { 49 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_LOGIN_RESP, "账号不存在")); 50 | } 51 | 52 | if (!auth.getPassword().equals(SecureUtil.md5(loginReq.getPassword()))) { 53 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_LOGIN_RESP, "密码错误")); 54 | } 55 | 56 | HashMap hashMap = new HashMap<>() { 57 | private static final long serialVersionUID = 1L; 58 | 59 | { 60 | put("uid", auth.getUserId()); 61 | put("expire_time", System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 60); 62 | } 63 | }; 64 | 65 | String token = JWTUtil.createToken(hashMap, "123456".getBytes(StandardCharsets.UTF_8)); 66 | 67 | return Resps.txt(httpRequest, RespBody.success(CommandEnum.COMMAND_LOGIN_RESP, token)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/protocol/http/RegisterController.java: -------------------------------------------------------------------------------- 1 | package org.example.protocol.http; 2 | 3 | import cn.hutool.core.util.IdUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.crypto.SecureUtil; 6 | import com.alibaba.fastjson.JSONObject; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.example.enums.CommandEnum; 9 | import org.example.enums.DefaultEnum; 10 | import org.example.packets.Status; 11 | import org.example.packets.bean.Auth; 12 | import org.example.packets.bean.User; 13 | import org.example.packets.handler.system.RegisterReqBody; 14 | import org.example.packets.handler.system.RespBody; 15 | import org.example.protocol.http.service.UploadService; 16 | import org.example.service.AuthService; 17 | import org.example.service.UserService; 18 | import org.tio.http.common.HttpRequest; 19 | import org.tio.http.common.HttpResponse; 20 | import org.tio.http.server.annotation.RequestPath; 21 | import org.tio.http.server.util.Resps; 22 | 23 | @Slf4j 24 | @RequestPath("/account") 25 | public class RegisterController { 26 | 27 | private final AuthService authService; 28 | 29 | private final UserService userService; 30 | 31 | public RegisterController() { 32 | authService = new AuthService(); 33 | userService = new UserService(); 34 | } 35 | 36 | @RequestPath("/register") 37 | public HttpResponse register(HttpRequest httpRequest) { 38 | RegisterReqBody reqBody = JSONObject.parseObject(httpRequest.getBodyString(), RegisterReqBody.class); 39 | 40 | log.info(String.valueOf(reqBody)); 41 | 42 | Auth authData = authService.getByAccount(reqBody.getAccount()); 43 | if (authData != null) { 44 | return Resps.txt(httpRequest, RespBody.fail(CommandEnum.COMMAND_REGISTER_RESP, "该登录账户已存在")); 45 | } 46 | 47 | String url = UploadService.uploadDefault(DefaultEnum.ACCOUNT); 48 | log.info("未查询到用户信息,创建用户"); 49 | User user = User.builder().id(IdUtil.getSnowflake().nextIdStr()).username(reqBody.getUsername()).status(Status.offline()) 50 | .avatar(url).build(); 51 | 52 | userService.saveOrUpdate(user); 53 | 54 | Auth auth = authService.createAccount(reqBody, user.getId()); 55 | 56 | return Resps.txt(httpRequest, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP)); 57 | } 58 | 59 | @RequestPath(value = "/check") 60 | public HttpResponse checkAccount(String account, HttpRequest request) { 61 | Auth auth = authService.getByAccount(account); 62 | if (auth != null) { 63 | return Resps.txt(request, RespBody.fail(CommandEnum.COMMAND_REGISTER_RESP, "该登录账户已存在")); 64 | } 65 | return Resps.txt(request, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP)); 66 | } 67 | 68 | @RequestPath(value = "/check/question") 69 | public HttpResponse checkAccountQuestion(String account, String question, String answer, HttpRequest request) { 70 | Auth auth = authService.getByAccount(account); 71 | if (auth != null) { 72 | if (StrUtil.isBlank(auth.getQuestion())){ 73 | auth.setQuestion(question); 74 | auth.setAnswer(answer); 75 | authService.update(auth); 76 | return Resps.txt(request, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP, "验证通过")); 77 | } 78 | if (auth.getQuestion().equals(question) && auth.getAnswer().equals(answer)) { 79 | return Resps.txt(request, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP, "验证通过")); 80 | } 81 | } 82 | return Resps.txt(request, RespBody.fail(CommandEnum.COMMAND_REGISTER_RESP, "问题或答案不正确")); 83 | } 84 | 85 | @RequestPath(value = "/reset") 86 | public HttpResponse reset(HttpRequest request) { 87 | RegisterReqBody reqBody = JSONObject.parseObject(request.getBodyString(), RegisterReqBody.class); 88 | 89 | Auth auth = authService.getByAccount(reqBody.getAccount()); 90 | if (auth != null) { 91 | if (auth.getQuestion().equals(reqBody.getQuestion()) && auth.getAnswer().equals(reqBody.getAnswer()) 92 | && reqBody.getPassword().equals(reqBody.getRepeatPassword())) { 93 | auth.setPassword(SecureUtil.md5(reqBody.getPassword())); 94 | authService.update(auth); 95 | return Resps.txt(request, RespBody.success(CommandEnum.COMMAND_REGISTER_RESP, "修改成功")); 96 | } 97 | } 98 | return Resps.txt(request, RespBody.fail(CommandEnum.COMMAND_REGISTER_RESP, "非法请求")); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/protocol/http/service/UploadService.java: -------------------------------------------------------------------------------- 1 | package org.example.protocol.http.service; 2 | 3 | import cn.hutool.core.io.file.FileNameUtil; 4 | import cn.hutool.core.io.resource.ResourceUtil; 5 | import cn.hutool.core.util.IdUtil; 6 | import cn.hutool.core.util.StrUtil; 7 | import com.google.common.collect.ImmutableList; 8 | import org.example.config.CourierConfig; 9 | import org.example.enums.DefaultEnum; 10 | import org.example.service.FileService; 11 | import org.example.util.MinIoUtils; 12 | 13 | import java.io.InputStream; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * 文件上传服务 19 | */ 20 | public class UploadService { 21 | private static final FileService fileService = new FileService(); 22 | 23 | public static Map initMultiPartUpload(String filename, Integer partCount, String contentType) { 24 | 25 | String filePath = IdUtil.getSnowflake().nextIdStr() + '.' + FileNameUtil.extName(filename); 26 | 27 | Map result = new HashMap<>(); 28 | if (partCount == 1) { 29 | String uploadObjectUrl = MinIoUtils.getUploadObjectUrl(filePath,contentType); 30 | if(uploadObjectUrl != null) { 31 | result.put("uploadUrls", ImmutableList.of(uploadObjectUrl)); 32 | } 33 | } else { 34 | result = MinIoUtils.initMultiPartUpload(filePath, partCount, contentType); 35 | } 36 | result.put("objectName", filePath); 37 | return result; 38 | } 39 | 40 | public static boolean mergeMultipartUpload(String objectName, String uploadId) { 41 | return MinIoUtils.mergeMultipartUpload(objectName, uploadId); 42 | } 43 | 44 | public static InputStream downloadGetStream(String filePath) { 45 | return MinIoUtils.download(filePath); 46 | } 47 | 48 | public static boolean uploadFile(byte[] bytes, String name) { 49 | return MinIoUtils.uploadByte(bytes, name); 50 | } 51 | 52 | public static String uploadDefault(DefaultEnum defaultEnum) { 53 | String url = fileService.getFileUrl(defaultEnum.getKey()); 54 | if (StrUtil.isBlank(url)) { 55 | byte[] bytes = ResourceUtil.readBytes("img/" + defaultEnum.getValue()); 56 | boolean b = uploadFile(bytes, defaultEnum.getValue()); 57 | if (b) { 58 | fileService.setFileUrl(defaultEnum.getKey(), defaultEnum.getValue(),defaultEnum.getValue(), (long) bytes.length); 59 | } 60 | } 61 | return CourierConfig.fileUrl + defaultEnum.getValue(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/protocol/ws/WsMsgHandler.java: -------------------------------------------------------------------------------- 1 | package org.example.protocol.ws; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import org.example.commond.AbstractCmdHandler; 6 | import org.example.commond.CommandManager; 7 | import org.example.commond.handler.LoginReqHandler; 8 | import org.example.config.Im; 9 | import org.example.config.ImConfig; 10 | import org.example.enums.CommandEnum; 11 | import org.example.packets.bean.User; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.tio.core.ChannelContext; 15 | import org.tio.http.common.HttpRequest; 16 | import org.tio.http.common.HttpResponse; 17 | import org.tio.websocket.common.WsRequest; 18 | import org.tio.websocket.common.WsResponse; 19 | import org.tio.websocket.server.handler.IWsMsgHandler; 20 | 21 | public class WsMsgHandler implements IWsMsgHandler { 22 | private static final Logger log = LoggerFactory.getLogger(WsMsgHandler.class); 23 | 24 | public static final WsMsgHandler me = new WsMsgHandler(); 25 | 26 | 27 | @Override 28 | public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) { 29 | String clientIp = httpRequest.getClientIp(); 30 | String username = httpRequest.getParam("username"); 31 | log.info("收到来自{}的ws握手包{}", clientIp, username); 32 | return httpResponse; 33 | } 34 | 35 | @Override 36 | public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) { 37 | LoginReqHandler loginHandler = (LoginReqHandler) CommandManager.getCommand(CommandEnum.COMMAND_LOGIN_REQ); 38 | 39 | String token = httpRequest.getParam("token"); 40 | WsRequest wsRequest = WsRequest.fromText(token, ImConfig.CHARSET); 41 | loginHandler.handler(wsRequest, channelContext); 42 | log.info("握手完毕{},{}", "66", "666"); 43 | } 44 | 45 | @Override 46 | public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) { 47 | return null; 48 | } 49 | 50 | @Override 51 | public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) { 52 | AbstractCmdHandler command = CommandManager.getCommand(CommandEnum.COMMAND_CLOSE_REQ); 53 | User user = Im.getUser(channelContext); 54 | if(user != null){ 55 | WsRequest request = WsRequest.fromText(user.getId(), Im.CHARSET); 56 | command.handler(request, channelContext); 57 | System.out.println("关闭连接"); 58 | } 59 | return null; 60 | } 61 | 62 | @Override 63 | public Object onText(WsRequest wsRequest, String text, ChannelContext channelContext) { 64 | Integer cmd = JSON.parseObject(text).getInteger("cmd"); 65 | if(cmd != 13){ 66 | log.info("socket消息:{}", text); 67 | } 68 | CommandEnum commandEnum = CommandEnum.forNumber(cmd); 69 | AbstractCmdHandler command = CommandManager.getCommand(commandEnum); 70 | WsResponse wsResponse = command.handler(wsRequest, channelContext); 71 | if (ObjectUtil.isNotNull(wsResponse)) { 72 | Im.send(channelContext, wsResponse); 73 | } 74 | return null; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/util/TestUtil2.java: -------------------------------------------------------------------------------- 1 | package org.example.util; 2 | 3 | public class TestUtil2 { 4 | 5 | // public static void main(String[] args) { 6 | // String uploadObjectUrl = MinIoUtils.getUploadObjectUrl("a.png"); 7 | // } 8 | 9 | // public static void main(String[] args) { 10 | // MinIoUtils minIoUtils = new MinIoUtils(); 11 | // try { 12 | // minIoUtils.init(); 13 | // System.out.println("连接完成"); 14 | // } catch (Exception e) { 15 | // e.printStackTrace(); 16 | // } 17 | // } 18 | 19 | public static void main(String[] args) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /im-server/src/main/java/org/example/util/ThreadPoolUtil.java: -------------------------------------------------------------------------------- 1 | package org.example.util; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.LinkedBlockingQueue; 5 | import java.util.concurrent.ScheduledThreadPoolExecutor; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public final class ThreadPoolUtil { 10 | 11 | private void ThreadPoolUtil() { 12 | 13 | } 14 | 15 | static volatile int theadCount = 0; 16 | 17 | /** 18 | * 通用的线程池。 19 | */ 20 | public static ThreadPoolExecutor commonPool = new ThreadPoolExecutor(5, 21 | 500, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5000) 22 | , r -> new Thread(r, "common thread " + theadCount++) 23 | ); 24 | 25 | /** 26 | * 定时任务线程池, 注意定时任务要设置合理的定时时间, 要根据任务的耗时来合理设置。 建议定时任务还是使用spring的定时任务功能 27 | */ 28 | public static ScheduledThreadPoolExecutor schedulePool = new ScheduledThreadPoolExecutor(1, 29 | r -> new Thread(r, "schedulePool thread " + theadCount++) 30 | ); 31 | 32 | /** 33 | * 短时间批量任务的执行 34 | * 35 | * @param poolSize 36 | * @param runnables 37 | */ 38 | public static void createPool(int poolSize, List runnables) { 39 | if (poolSize < 1) { 40 | throw new RuntimeException("请设置合理的线程池数量"); 41 | } 42 | ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(poolSize, 43 | poolSize, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(runnables.size()) 44 | , r -> new Thread(r, "create new Pool thread " + theadCount++)); 45 | 46 | runnables.forEach(threadPoolExecutor::execute); 47 | //这里只是通知线程池不在接收新任务了,并且所有任务结束后,线程池要关闭 48 | threadPoolExecutor.shutdown(); 49 | } 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /im-server/src/main/resources/application.setting: -------------------------------------------------------------------------------- 1 | applicationName=IM 2 | 3 | socketPort=9326 4 | socketHeartbeat=60000 5 | 6 | httpPort=8088 7 | httpMaxLiveTime=2000 8 | httpUseSession=false 9 | httpCheckHost=false 10 | 11 | checkFileMd5=true 12 | 13 | [dev] 14 | mongoHost=192.168.3.206 15 | mongoUserName=admin 16 | mongoPassword=QuniMade! 17 | fileUrl=https://192.168.3.206:9999/courier/ 18 | minioHost=192.168.3.206 19 | minioPort=9000 20 | minioUseSSL=truebv 21 | minioAccessKey=AKIAIOSFODNN7EXAMPLE 22 | minioSecretKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 23 | 24 | [prod] 25 | mongoHost=127.0.0.1 26 | mongoUserName=admin 27 | mongoPassword=QuniMade! 28 | fileUrl=https://miniodev.o0o0oo.com/courier/ 29 | minioHost=miniodev.o0o0oo.com 30 | minioPort=443 31 | minioUseSSL=true 32 | minioAccessKey=AKIAIOSFODNN7EXAMPLE 33 | minioSecretKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY -------------------------------------------------------------------------------- /im-server/src/main/resources/img/account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alisonispig/im/2117923905091aae516058e216505375265802ce/im-server/src/main/resources/img/account.png -------------------------------------------------------------------------------- /im-server/src/main/resources/img/accountGroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alisonispig/im/2117923905091aae516058e216505375265802ce/im-server/src/main/resources/img/accountGroup.png -------------------------------------------------------------------------------- /im-server/src/main/resources/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alisonispig/im/2117923905091aae516058e216505375265802ce/im-server/src/main/resources/img/logo.png -------------------------------------------------------------------------------- /im-server/src/main/resources/logback.properties: -------------------------------------------------------------------------------- 1 | #http://logback.qos.ch/manual/configuration.html 2 | # resource, file, url (??????????????) 3 | 4 | context.name=im 5 | 6 | log.dir=../logs/im 7 | 8 | rolling.policy.file.name.pattern=yyyy-MM-dd HH 9 | max.file.size=100MB 10 | max.history=50 11 | 12 | conversion.pattern=%d %-5level %logger{30}[%line]: %m%n 13 | root.level=info 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /im-server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | ${context.name} 6 | 7 | 8 | 10 | 11 | ${conversion.pattern} 12 | 13 | 14 | 15 | 16 | 18 | ${log.dir}/error.log 19 | 21 | ${log.dir}/error.%d{${rolling.policy.file.name.pattern}}%d{mmss}.%i.log.zip 22 | 23 | 25 | ${max.file.size} 26 | 27 | ${max.history} 28 | 29 | 30 | ${conversion.pattern} 31 | 32 | 33 | ERROR 34 | ACCEPT 35 | DENY 36 | 37 | 38 | 40 | ${log.dir}/warn.log 41 | 43 | ${log.dir}/warn.%d{${rolling.policy.file.name.pattern}}%d{mmss}.%i.log.zip 44 | 45 | 47 | ${max.file.size} 48 | 49 | ${max.history} 50 | 51 | 52 | ${conversion.pattern} 53 | 54 | 55 | warn 56 | ACCEPT 57 | DENY 58 | 59 | 60 | 62 | ${log.dir}/info.log 63 | 65 | ${log.dir}/info.%d{${rolling.policy.file.name.pattern}}%d{mmss}.%i.log.zip 66 | 67 | 69 | ${max.file.size} 70 | 71 | ${max.history} 72 | 73 | 74 | ${conversion.pattern} 75 | 76 | 77 | INFO 78 | ACCEPT 79 | DENY 80 | 81 | 82 | 84 | ${log.dir}/debug.log 85 | 87 | ${log.dir}/debug.%d{${rolling.policy.file.name.pattern}}%d{mmss}.%i.log.zip 88 | 89 | 91 | ${max.file.size} 92 | 93 | ${max.history} 94 | 95 | 96 | ${conversion.pattern} 97 | 98 | 99 | debug 100 | ACCEPT 101 | DENY 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | im 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | im-core 13 | im-server 14 | 15 | 16 | 17 | UTF-8 18 | 13 19 | 13 20 | 21 | 22 | 23 | --------------------------------------------------------------------------------