This commit is contained in:
dzc committed 2025-11-25 11:50:02 +08:00
commit 5a041ecaf5
1273 files changed
+158507

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
/.idea/
/box-im.iml
/im-server/im-server.iml
/im-platform/im-platform.iml
/im-client/im-client.iml
/im-common/im-common.iml
/im-server/target/
/im-platform/target/
/im-client/target/
/im-common/target/
/im-web/node_modules/
/im-web/dist/
/im-uniapp/node_modules/
/im-uniapp/package-lock.json
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 blue
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+145
View File
@@ -0,0 +1,145 @@
### **盒子IM**
![MIT协议](https://img.shields.io/badge/license-MIT-red)
[![star](https://gitee.com/bluexsx/box-im/badge/star.svg)](https://gitee.com/bluexsx/box-im)
[![star](https://img.shields.io/github/stars/bluexsx/box-im.svg?style=flat&logo=GitHub)](https://github.com/bluexsx/box-im)
<a href="#加入交流群"><img src="https://img.shields.io/badge/QQ交流群-green.svg?style=plasticr"></a>
1. 盒子IM是一个仿微信实现的网页版聊天软件,不依赖任何第三方收费组件。
1. 支持私聊、群聊、离线消息、发送语音、图片、文件、已读未读、群@等功能
1. 支持音视频通话(基于原生webrtc实现,需要ssl证书)
1. uniapp端兼容app、h5、微信小程序,可与web端同时在线,并保持消息同步
1. 后端采用springboot+netty实现,网页端使用vue,移动端使用uniapp
1. 服务器支持集群化部署,具有良好的横向扩展能力
详细文档:https://www.yuque.com/u1475064/mufu2a
官方论坛: https://bbs.boxim.online
#### 近期更新
发布3.0版本:
- 后台管理端上线,后台管理代码仓库地址:https://gitee.com/bluexsx/box-im-admin
- 框架和组件版本全面升级: jdk17、springboot3.3、node18等
- 部分界面,功能、性能优化
#### 在线体验
网页端:https://www.boxim.online
移动安卓端:已上架至各大主流手机应用市场以及腾讯应用宝,搜索"盒子IM",下载安装即可
移动ios端: 已上架至app store,搜索"盒子IM",下载安装即可
小程序段: 已上架至微信小程序,搜索“盒子IM”进入即可
移动H5端: https://www.boxim.online/h5/ ,或扫码:
![输入图片说明](%E6%88%AA%E5%9B%BE/h5%E4%BA%8C%E7%BB%B4%E7%A0%81.png)
测试账号:张三/Aa123123 李四/Aa123123,也可以自行注册
说明:
1.**请勿利用测试账号辱骂他人、发布低俗内容,否则将直接对您的IP进行封禁**
2.由于部分厂商上架审核要求实名制,app端隐藏了"用户名注册"注册通道,可通过长按注册页面蓝色文字标题解除限制
3.由于微信小程序每次发布审核过于严苛和繁琐,暂时不再提供体验环境,但uniapp端依然会继续兼容小程序
4.体验环境部署的是商业版本,与开源版本功能存在一定差异,具体请参考:
https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29#SbvXq
#### 付费服务
商业版源码: https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29
远程协助: https://www.yuque.com/u1475064/imk5n2/fettd57rvzc29s5r
环境搭建: https://www.yuque.com/u1475064/imk5n2/qgq5cvgmavallqnl
#### 项目结构
| 模块 | 功能 |
|-------------|----------------------------------|
| im-platform | 业务平台服务,负责处理来自用户的业务请求(http) |
| im-server | 消息推送服务,不依赖业务,负责将消息推送给用户(ws) |
| im-client | 消息推送sdk, 其他服务可集成此sdk与im-server通信 |
| im-common | 公共包,后端服务均依赖此包 |
| im-web | web页面 |
| im-uniapp | uniapp页面,可打包成app、h5、微信小程序 |
#### 消息推送方案
当消息的发送者和接收者连的不是同一个server时,消息是无法直接推送的,所以我们设计出了能够支持跨节点推送的方案:
![输入图片说明](%E6%88%AA%E5%9B%BE/%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81%E9%9B%86%E7%BE%A4%E5%8C%96.jpg)
- 利用了redis的list数据实现消息推送,其中key为im:unread:${serverid},每个key的数据可以看做一个queue,每个im-server根据自身的id只消费属于自己的queue
- redis记录了每个用户的websocket连接的是哪个im-server,当用户发送消息时,im-platform将根据所连接的im-server的id,决定将消息推向哪个queue
#### 本地启动
1.安装运行环境
- 安装node:v18.19.0
- 安装jdk:17
- 安装maven:3.9.6
- 安装mysql:8.0,账号密码分别为root/root,创建名为im_platform的数据库,运行db/im_platfrom.sql脚本
- 安装redis:6.2
- 安装minio:RELEASE.2024-xx,使用默认账号、密码、端口
2.启动后端服务
```
mvn clean package
java -jar ./im-platform/target/im-platform.jar
java -jar ./im-server/target/im-server.jar
```
3.启动前端web
```
cd im-web
npm install
npm run serve
```
访问 http://localhost:8080
4.启动uniapp-h5
将im-uniapp目录导入HBuilderX,点击菜单"运行"->"开发环境-h5"
访问 http://localhost:5173
#### 界面截图
私聊:
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%A7%81%E8%81%8A.jpg)
群聊:
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E8%81%8A.jpg)
群通话(商业版):
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E5%A4%9A%E4%BA%BA%E9%80%9A%E8%AF%9D.jpg)
好友列表:
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E5%A5%BD%E5%8F%8B.jpg)
群列表:
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E5%88%97%E8%A1%A8.jpg)
移动端APP:
![输入图片说明](%E6%88%AA%E5%9B%BE/app/1.png)
![输入图片说明](%E6%88%AA%E5%9B%BE/app/2.png)
#### 加入交流群
群1: 741174521(已满)
群2: 937470451(已满)
群3: 1012017031(已满)
群4: 765849133
欢迎进群与小伙们一起交流, **申请加群前请务必先star哦**
#### 点下star吧
如果项目对您有帮助,请点亮右上方的star,支持一下作者吧!
#### 说明几点
1. 本系统允许用于商业用途,且不收费,**但切记不要用于任何非法用途** ,本软件作者不会为此承担任何责任
1. 基于本系统二次开发后再次开源的项目,请注明引用出处,以避免引发不必要的误会
1. 为方便管理,要pr的同学请将代码提交到v_3.0.0分支,作者会在功能上线时合并到master分支
1. 允许商用不代表可以随意倒卖开源代码,尤其是代码、名字、截图一点都不改就拿去卖钱的,我劝你们善良一点!
+112
View File
@@ -0,0 +1,112 @@
create table `im_user`(
`id` bigint not null auto_increment primary key comment 'id',
`user_name` varchar(255) not null comment '用户名',
`nick_name` varchar(255) not null comment '用户昵称',
`head_image` varchar(255) default '' comment '用户头像',
`head_image_thumb` varchar(255) default '' comment '用户头像缩略图',
`password` varchar(255) not null comment '密码',
`sex` tinyint(1) default 0 comment '性别 0:男 1:女',
`is_banned` tinyint(1) default 0 comment '是否被封禁 0:否 1:是',
`reason` varchar(255) default '' comment '被封禁原因',
`type` smallint default 1 comment '用户类型 1:普通用户 2:审核账户',
`signature` varchar(1024) default '' comment '个性签名',
`last_login_time` datetime DEFAULT null comment '最后登录时间',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
unique key `idx_user_name`(user_name),
key `idx_nick_name`(nick_name)
) ENGINE=InnoDB CHARSET=utf8mb4 comment '用户';
create table `im_friend`(
`id` bigint not null auto_increment primary key comment 'id',
`user_id` bigint not null comment '用户id',
`friend_id` bigint not null comment '好友id',
`friend_nick_name` varchar(255) not null comment '好友昵称',
`friend_head_image` varchar(255) default '' comment '好友头像',
`is_dnd` tinyint comment '免打扰标识(Do Not Disturb) 0:关闭 1:开启',
`deleted` tinyint comment '删除标识 0:正常 1:已删除',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
key `idx_user_id` (`user_id`),
key `idx_friend_id` (`friend_id`)
) ENGINE=InnoDB CHARSET=utf8mb4 comment '好友';
create table `im_private_message`(
`id` bigint not null auto_increment primary key comment 'id',
`tmp_id` varchar(32) comment '临时id,由前端生成',
`send_id` bigint not null comment '发送用户id',
`recv_id` bigint not null comment '接收用户id',
`content` text comment '发送内容',
`type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 21:提示',
`status` tinyint(1) NOT NULL comment '状态 0:未读 1:已读 2:撤回 3:已读',
`send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间',
key `idx_send_id` (`send_id`),
key `idx_recv_id` (`recv_id`)
)ENGINE=InnoDB CHARSET=utf8mb4 comment '私聊消息';
create table `im_group`(
`id` bigint not null auto_increment primary key comment 'id',
`name` varchar(255) not null comment '群名字',
`owner_id` bigint not null comment '群主id',
`head_image` varchar(255) default '' comment '群头像',
`head_image_thumb` varchar(255) default '' comment '群头像缩略图',
`notice` varchar(1024) default '' comment '群公告',
`is_banned` tinyint(1) default 0 comment '是否被封禁 0:否 1:是',
`reason` varchar(255) default '' comment '被封禁原因',
`dissolve` tinyint(1) default 0 comment '是否已解散',
`created_time` datetime default CURRENT_TIMESTAMP comment '创建时间'
)ENGINE=InnoDB CHARSET=utf8mb4 comment '群';
create table `im_group_member`(
`id` bigint not null auto_increment primary key comment 'id',
`group_id` bigint not null comment '群id',
`user_id` bigint not null comment '用户id',
`user_nick_name` varchar(255) DEFAULT '' comment '用户昵称',
`remark_nick_name` varchar(255) DEFAULT '' comment '显示昵称备注',
`head_image` varchar(255) DEFAULT '' comment '用户头像',
`remark_group_name` varchar(255) DEFAULT '' comment '显示群名备注',
`is_dnd` tinyint comment '免打扰标识(Do Not Disturb) 0:关闭 1:开启',
`quit` tinyint(1) DEFAULT 0 comment '是否已退出',
`quit_time` datetime DEFAULT NULL comment '退出时间',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
key `idx_group_id`(`group_id`),
key `idx_user_id`(`user_id`)
)ENGINE=InnoDB CHARSET=utf8mb4 comment '群成员';
create table `im_group_message`(
`id` bigint not null auto_increment primary key comment 'id',
`tmp_id` varchar(32) comment '临时id,由前端生成',
`group_id` bigint not null comment '群id',
`send_id` bigint not null comment '发送用户id',
`send_nick_name` varchar(255) DEFAULT '' comment '发送用户昵称',
`recv_ids` varchar(1024) DEFAULT '' comment '接收用户id,逗号分隔,为空表示发给所有成员',
`content` text comment '发送内容',
`at_user_ids` varchar(1024) comment '被@的用户id列表,逗号分隔',
`receipt` tinyint DEFAULT 0 comment '是否回执消息',
`receipt_ok` tinyint DEFAULT 0 comment '回执消息是否完成',
`type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 21:提示' ,
`status` tinyint(1) DEFAULT 0 comment '状态 0:未发出 2:撤回 ',
`send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间',
key `idx_group_id` (group_id)
)ENGINE=InnoDB CHARSET=utf8mb4 comment '群消息';
create table `im_sensitive_word`(
`id` bigint not null auto_increment primary key comment 'id',
`content` varchar(64) not null comment '敏感词内容',
`enabled` tinyint DEFAULT 0 COMMENT '是否启用 0:未启用 1:启用',
`creator` bigint DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
)ENGINE=InnoDB CHARSET=utf8mb4 comment '敏感词';
CREATE TABLE `im_file_info` (
`id` BIGINT NOT NULL auto_increment PRIMARY key comment 'id',
`file_name` VARCHAR(255) NOT NULL comment '文件名',
`file_path` VARCHAR(255) NOT NULL comment '文件地址',
`file_size` INTEGER NOT NULL comment '文件大小',
`file_type` tinyint NOT NULL comment '0:普通文件 1:图片 2:视频',
`compressed_path` VARCHAR(255) DEFAULT NULL comment '压缩文件路径',
`cover_path` VARCHAR(255) DEFAULT NULL comment '封面文件路径,仅视频文件有效',
`upload_time` datetime DEFAULT CURRENT_TIMESTAMP comment '上传时间',
`is_permanent` tinyint DEFAULT 0 comment '是否永久文件',
`md5` VARCHAR(64) NOT NULL comment '文件md5',
KEY `idx_md5` (md5)
) ENGINE = InnoDB CHARSET = utf8mb4 comment '文件';
+470
View File
@@ -0,0 +1,470 @@
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爱液', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('按摩棒', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('拔出来', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爆草', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('包二奶', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('暴干', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('暴奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('暴乳', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爆乳', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('暴淫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('屄', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('被操', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('被插', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('被干', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('逼奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('仓井空', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插暴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操黑', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操烂', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肏你', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肏死', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操死', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操我', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('厕奴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插比', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插进', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插你', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插我', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插阴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('潮吹', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('潮喷', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人电影', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人论坛', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人小说', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人电', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人电影', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人卡通', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人聊', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人片', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人视', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人图', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人小', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人电影', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人论坛', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人色情', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人网站', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人小说', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('艳情小说', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人游戏', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('吃精', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('赤裸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('抽插', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('扌由插', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('抽一插', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('春药', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('大波', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('大力抽送', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('大乳', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('荡妇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('荡女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('盗撮', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('多人轮', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('发浪', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('放尿', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肥逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('粉穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('封面女郎', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('风月大陆', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干死你', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肛交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肛门', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('龟头', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裹本', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('好嫩', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('豪乳', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('黑逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('后庭', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('后穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('虎骑', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('花花公子', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('换妻俱乐部', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('黄片', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('几吧', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('鸡吧', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('鸡巴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('鸡奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('寂寞男', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('寂寞女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('妓女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('激情', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('集体淫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('奸情', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('叫床', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('脚交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('精液', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('就去日', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('巨屌', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('菊花洞', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('菊门', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('巨奶', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('巨乳', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('菊穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('开苞', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口爆', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口活', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口射', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口淫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裤袜', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狂操', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狂插', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浪逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浪妇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浪叫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浪女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狼友', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('聊性', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('流淫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('铃木麻', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('凌辱', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('漏乳', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱伦', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('轮暴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('轮操', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('轮奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸陪', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('买春', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美少妇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美乳', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美腿', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美幼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('秘唇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('迷奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('密穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('蜜穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('蜜液', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('摸奶', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('摸胸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('母奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('奈美', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('奶子', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('男奴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('内射', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('嫩逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('嫩女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('嫩穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('捏弄', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('女优', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('炮友', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('砲友', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('喷精', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('屁眼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('品香堂', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('前凸后翘', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('强暴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('强奸处女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('情趣用品', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('情色', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('拳交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('全裸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('群交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('惹火身材', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人妻', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人兽', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('日逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('日烂', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉棒', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉唇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉洞', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉缝', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉棍', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉茎', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉具', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('揉乳', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉欲', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳爆', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳房', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳沟', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳头', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('三级片', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚比', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚水', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色界', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色猫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色盟', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色情网站', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色区', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色诱', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色欲', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('射爽', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('射颜', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('食精', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('释欲', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兽奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兽交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('手淫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兽欲', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('熟妇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('熟母', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('熟女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爽片', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爽死我了', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('双臀', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('死逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('丝袜', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('丝诱', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('松岛枫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('酥痒', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('汤加丽', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('套弄', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('体奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('体位', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('舔脚', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('舔阴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('调教', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('偷欢', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('偷拍', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('推油', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('脱内裤', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('文做', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我就色', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('无码', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('舞女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('无修正', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('吸精', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('夏川纯', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('相奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('小逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('校鸡', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('小穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('写真', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性感妖娆', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性感诱惑', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性虎', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性饥渴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性技巧', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性奴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性虐', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性息', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性欲', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('胸推', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('穴口', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('学生妹', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('穴图', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('亚情', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('颜射', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阳具', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('杨思敏', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('要射了', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('夜勤病栋', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('一本道', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('一夜欢', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('一夜情', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴部', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫虫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴唇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴道', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫电影', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴阜', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫妇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫河', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴核', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴户', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫贱', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫叫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫教师', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴茎', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴精', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫浪', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫媚', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫糜', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫魔', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫母', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫虐', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫妻', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫情', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫色', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫声浪语', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫兽学园', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫书', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫术炼金士', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫水', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫娃', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫威', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫亵', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫样', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫液', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫照', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('应召', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('幼交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('幼男', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('幼女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('欲火', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('欲女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('玉女心经', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('玉蒲团', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('玉乳', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('欲仙欲死', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('玉穴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('援交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('原味内衣', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('援助交际', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('张筱雨', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('招鸡', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('招妓', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('中年美妇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('抓胸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('自拍', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('自慰', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('作爱', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('贱人', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('傻逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('煞逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('煞笔', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('刹笔', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('傻比', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('沙比', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('欠干', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('婊子养的', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我日你', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我操', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我草', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('卧艹', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('卧槽', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爆你菊', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('艹你', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('你他妈', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('真他妈', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('别他吗', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('草你吗', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('草你丫', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你妈', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('擦你妈', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你娘', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操他妈', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('日你妈', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干你妈', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干你娘', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('娘西皮', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗操', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗草', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗杂种', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗日的', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你祖宗', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你全家', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你大爷', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('妈逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('你麻痹', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('麻痹的', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('妈了个逼', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('马勒', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗娘养', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('贱比', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('下贱', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('死全家', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('全家死光', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('全家不得好死', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('全家死绝', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('白痴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('无耻', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('你妈的', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('婊子', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('贱货', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人渣', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('混蛋', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('媚外', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('和弦', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兼职', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('限量', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('铃声', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性伴侣', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('男公关', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('火辣', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('精子', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('射精', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('诱奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('强奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('做爱', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性爱', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('发生关系', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('按摩', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('快感', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('处男', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('猛男', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('少妇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('屌', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('屁股', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('下体', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('内裤', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浑圆', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('咪咪', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('发情', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('刺激', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('白嫩', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('粉嫩', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兽性', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('风骚', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('呻吟', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阉割', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('高潮', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸露', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('不穿', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('一丝不挂', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('脱光', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干你', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干死', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我干', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裙中性运动', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱奸', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱伦', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱伦类', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱伦小', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('伦理大', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('伦理电影', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('伦理毛', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('伦理片', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸聊', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸聊网', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸体写真', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸舞视', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸照', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美女裸体', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性交', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性交视频', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性交图片', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性奴', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性奴集中营', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性虐', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴唇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴道', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴蒂', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴户', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴间来电', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴茎', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴茎增大', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴茎助勃', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴毛', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('陰唇', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('陰道', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('陰戶', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡美女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡视频', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡照片', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫乱', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫靡', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫魔', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫魔舞', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫情女', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫肉', 1, 10000);
INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫騷妹', 1, 10000);
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>box-im</artifactId>
<groupId>com.bx</groupId>
<version>3.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>im-client</artifactId>
<dependencies>
<dependency>
<groupId>com.bx</groupId>
<artifactId>im-common</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,13 @@
package com.bx.imclient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
@ComponentScan(basePackages = {"com.bx.imclient", "com.bx.imcommon"})
public class IMAutoConfiguration {
}
@@ -0,0 +1,92 @@
package com.bx.imclient;
import com.bx.imclient.sender.IMSender;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMSystemMessage;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.Map;
@Configuration
@AllArgsConstructor
public class IMClient {
private final IMSender imSender;
/**
* 判断用户是否在线
*
* @param userId 用户id
*/
public Boolean isOnline(Long userId){
return imSender.isOnline(userId);
}
/**
* 判断用户是否在线
*
* @param userId 用户id
* @param terminal 终端可惜
*/
public Boolean isOnline(Long userId,IMTerminalType terminal){
return imSender.isOnline(userId,terminal);
}
/**
* 判断多个用户是否在线
*
* @param userIds 用户id列表
* @return 在线的用户列表
*/
public List<Long> getOnlineUser(List<Long> userIds){
return imSender.getOnlineUser(userIds);
}
/**
* 判断多个用户是否在线
*
* @param userIds 用户id列表
* @return 在线的用户终端
*/
public Map<Long,List<IMTerminalType>> getOnlineTerminal(List<Long> userIds){
return imSender.getOnlineTerminal(userIds);
}
/**
* 发送系统消息(发送结果通过MessageListener接收)
*
* @param message 私有消息
*/
public<T> void sendSystemMessage(IMSystemMessage<T> message){
imSender.sendSystemMessage(message);
}
/**
* 发送私聊消息(发送结果通过MessageListener接收)
*
* @param message 私有消息
*/
public<T> void sendPrivateMessage(IMPrivateMessage<T> message){
imSender.sendPrivateMessage(message);
}
/**
* 发送群聊消息(发送结果通过MessageListener接收)
*
* @param message 群聊消息
*/
public<T> void sendGroupMessage(IMGroupMessage<T> message){
imSender.sendGroupMessage(message);
}
}
@@ -0,0 +1,18 @@
package com.bx.imclient.annotation;
import com.bx.imcommon.enums.IMListenerType;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface IMListener {
IMListenerType type();
}
@@ -0,0 +1,12 @@
package com.bx.imclient.listener;
import com.bx.imcommon.model.IMSendResult;
import java.util.List;
public interface MessageListener<T> {
void process(List<IMSendResult<T>> result);
}
@@ -0,0 +1,46 @@
package com.bx.imclient.listener;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONObject;
import com.bx.imclient.annotation.IMListener;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.IMSendResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
@Component
public class MessageListenerMulticaster {
@Autowired(required = false)
private List<MessageListener> messageListeners = Collections.emptyList();
public void multicast(IMListenerType listenerType, List<IMSendResult> results){
if(CollUtil.isEmpty(results)){
return;
}
for(MessageListener listener:messageListeners){
IMListener annotation = listener.getClass().getAnnotation(IMListener.class);
if(annotation!=null && (annotation.type().equals(IMListenerType.ALL) || annotation.type().equals(listenerType))){
results.forEach(result->{
// 将data转回对象类型
if(result.getData() instanceof JSONObject){
Type superClass = listener.getClass().getGenericInterfaces()[0];
Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
JSONObject data = (JSONObject)result.getData();
result.setData(data.toJavaObject(type));
}
});
// 回调到调用方处理
listener.process(results);
}
}
}
}
@@ -0,0 +1,262 @@
package com.bx.imclient.sender;
import cn.hutool.core.collection.CollUtil;
import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.*;
import com.bx.imcommon.mq.RedisMQTemplate;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
@RequiredArgsConstructor
public class IMSender {
@Autowired
private RedisMQTemplate redisMQTemplate;
@Value("${spring.application.name}")
private String appName;
private final MessageListenerMulticaster listenerMulticaster;
public<T> void sendSystemMessage(IMSystemMessage<T> message){
// 根据群聊每个成员所连的IM-server,进行分组
Map<String, IMUserInfo> sendMap = new HashMap<>();
for (Integer terminal : message.getRecvTerminals()) {
message.getRecvIds().forEach(id -> {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString());
sendMap.put(key,new IMUserInfo(id, terminal));
});
}
// 批量拉取
List<Object> serverIds = redisMQTemplate.opsForValue().multiGet(sendMap.keySet());
// 格式:map<服务器id,list<接收方>>
Map<Integer, List<IMUserInfo>> serverMap = new HashMap<>();
List<IMUserInfo> offLineUsers = new LinkedList<>();
int idx = 0;
for (Map.Entry<String,IMUserInfo> entry : sendMap.entrySet()) {
Integer serverId = (Integer)serverIds.get(idx++);
if (!Objects.isNull(serverId)) {
List<IMUserInfo> list = serverMap.computeIfAbsent(serverId, o -> new LinkedList<>());
list.add(entry.getValue());
} else {
// 加入离线列表
offLineUsers.add(entry.getValue());
}
}
// 逐个server发送
for (Map.Entry<Integer, List<IMUserInfo>> entry : serverMap.entrySet()) {
IMRecvInfo recvInfo = new IMRecvInfo();
recvInfo.setCmd(IMCmdType.SYSTEM_MESSAGE.code());
recvInfo.setReceivers(new LinkedList<>(entry.getValue()));
recvInfo.setServiceName(appName);
recvInfo.setSendResult(message.getSendResult());
recvInfo.setData(message.getData());
// 推送至队列
String key = String.join(":", IMRedisKey.IM_MESSAGE_SYSTEM_QUEUE, entry.getKey().toString());
redisMQTemplate.opsForList().rightPush(key, recvInfo);
}
// 对离线用户回复消息状态
if(message.getSendResult() && !offLineUsers.isEmpty()){
List<IMSendResult> results = new LinkedList<>();
for (IMUserInfo offLineUser : offLineUsers) {
IMSendResult result = new IMSendResult();
result.setReceiver(offLineUser);
result.setCode(IMSendCode.NOT_ONLINE.code());
result.setData(message.getData());
results.add(result);
}
listenerMulticaster.multicast(IMListenerType.SYSTEM_MESSAGE, results);
}
}
public<T> void sendPrivateMessage(IMPrivateMessage<T> message) {
List<IMSendResult> results = new LinkedList<>();
if(!Objects.isNull(message.getRecvId())){
for (Integer terminal : message.getRecvTerminals()) {
// 获取对方连接的channelId
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getRecvId().toString(), terminal.toString());
Integer serverId = (Integer)redisMQTemplate.opsForValue().get(key);
// 如果对方在线,将数据存储至redis,等待拉取推送
if (serverId != null) {
String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString());
IMRecvInfo recvInfo = new IMRecvInfo();
recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code());
recvInfo.setSendResult(message.getSendResult());
recvInfo.setServiceName(appName);
recvInfo.setSender(message.getSender());
recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getRecvId(), terminal)));
recvInfo.setData(message.getData());
redisMQTemplate.opsForList().rightPush(sendKey, recvInfo);
} else {
IMSendResult result = new IMSendResult();
result.setSender(message.getSender());
result.setReceiver(new IMUserInfo(message.getRecvId(), terminal));
result.setCode(IMSendCode.NOT_ONLINE.code());
result.setData(message.getData());
results.add(result);
}
}
}
// 推送给自己的其他终端
if(message.getSendToSelf()){
for (Integer terminal : IMTerminalType.codes()) {
if (message.getSender().getTerminal().equals(terminal)) {
continue;
}
// 获取终端连接的channelId
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString());
Integer serverId = (Integer)redisMQTemplate.opsForValue().get(key);
// 如果终端在线,将数据存储至redis,等待拉取推送
if (serverId != null) {
String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString());
IMRecvInfo recvInfo = new IMRecvInfo();
// 自己的消息不需要回推消息结果
recvInfo.setSendResult(false);
recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code());
recvInfo.setSender(message.getSender());
recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal)));
recvInfo.setData(message.getData());
redisMQTemplate.opsForList().rightPush(sendKey, recvInfo);
}
}
}
// 对离线用户回复消息状态
if(message.getSendResult() && !results.isEmpty()){
listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, results);
}
}
public<T> void sendGroupMessage(IMGroupMessage<T> message) {
// 根据群聊每个成员所连的IM-server,进行分组
Map<String, IMUserInfo> sendMap = new HashMap<>();
for (Integer terminal : message.getRecvTerminals()) {
message.getRecvIds().forEach(id -> {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString());
sendMap.put(key,new IMUserInfo(id, terminal));
});
}
// 批量拉取
List<Object> serverIds = redisMQTemplate.opsForValue().multiGet(sendMap.keySet());
// 格式:map<服务器id,list<接收方>>
Map<Integer, List<IMUserInfo>> serverMap = new HashMap<>();
List<IMUserInfo> offLineUsers = new LinkedList<>();
int idx = 0;
for (Map.Entry<String,IMUserInfo> entry : sendMap.entrySet()) {
Integer serverId = (Integer)serverIds.get(idx++);
if (!Objects.isNull(serverId)) {
List<IMUserInfo> list = serverMap.computeIfAbsent(serverId, o -> new LinkedList<>());
list.add(entry.getValue());
} else {
// 加入离线列表
offLineUsers.add(entry.getValue());
}
}
// 逐个server发送
for (Map.Entry<Integer, List<IMUserInfo>> entry : serverMap.entrySet()) {
IMRecvInfo recvInfo = new IMRecvInfo();
recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code());
recvInfo.setReceivers(new LinkedList<>(entry.getValue()));
recvInfo.setSender(message.getSender());
recvInfo.setServiceName(appName);
recvInfo.setSendResult(message.getSendResult());
recvInfo.setData(message.getData());
// 推送至队列
String key = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, entry.getKey().toString());
redisMQTemplate.opsForList().rightPush(key, recvInfo);
}
// 推送给自己的其他终端
if (message.getSendToSelf()) {
for (Integer terminal : IMTerminalType.codes()) {
if (terminal.equals(message.getSender().getTerminal())) {
continue;
}
// 获取终端连接的channelId
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString());
Integer serverId = (Integer)redisMQTemplate.opsForValue().get(key);
// 如果终端在线,将数据存储至redis,等待拉取推送
if (!Objects.isNull(serverId)) {
IMRecvInfo recvInfo = new IMRecvInfo();
recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code());
recvInfo.setSender(message.getSender());
recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal)));
// 自己的消息不需要回推消息结果
recvInfo.setSendResult(false);
recvInfo.setData(message.getData());
String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, serverId.toString());
redisMQTemplate.opsForList().rightPush(sendKey, recvInfo);
}
}
}
// 对离线用户回复消息状态
if(message.getSendResult() && !offLineUsers.isEmpty()){
List<IMSendResult> results = new LinkedList<>();
for (IMUserInfo offLineUser : offLineUsers) {
IMSendResult result = new IMSendResult();
result.setSender(message.getSender());
result.setReceiver(offLineUser);
result.setCode(IMSendCode.NOT_ONLINE.code());
result.setData(message.getData());
results.add(result);
}
listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE, results);
}
}
public Map<Long,List<IMTerminalType>> getOnlineTerminal(List<Long> userIds){
if(CollUtil.isEmpty(userIds)){
return Collections.emptyMap();
}
// 把所有用户的key都存起来
Map<String,IMUserInfo> userMap = new HashMap<>();
for(Long id:userIds){
for (Integer terminal : IMTerminalType.codes()) {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString());
userMap.put(key,new IMUserInfo(id,terminal));
}
}
// 批量拉取
List<Object> serverIds = redisMQTemplate.opsForValue().multiGet(userMap.keySet());
int idx = 0;
Map<Long,List<IMTerminalType>> onlineMap = new HashMap<>();
for (Map.Entry<String,IMUserInfo> entry : userMap.entrySet()) {
// serverid有值表示用户在线
if(serverIds.get(idx++) != null){
IMUserInfo userInfo = entry.getValue();
List<IMTerminalType> terminals = onlineMap.computeIfAbsent(userInfo.getId(), o -> new LinkedList<>());
terminals.add(IMTerminalType.fromCode(userInfo.getTerminal()));
}
}
// 去重并返回
return onlineMap;
}
public Boolean isOnline(Long userId, IMTerminalType terminal) {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.code().toString());
return redisMQTemplate.hasKey(key);
}
public Boolean isOnline(Long userId) {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*");
return !Objects.requireNonNull(redisMQTemplate.keys(key)).isEmpty();
}
public List<Long> getOnlineUser(List<Long> userIds){
return new LinkedList<>(getOnlineTerminal(userIds).keySet());
}
}
@@ -0,0 +1,21 @@
package com.bx.imclient.task;
import cn.hutool.core.util.StrUtil;
import com.bx.imcommon.mq.RedisMQConsumer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@Slf4j
public abstract class AbstractMessageResultTask<T> extends RedisMQConsumer<T> {
@Value("${spring.application.name}")
private String appName;
@Override
public String generateKey() {
return StrUtil.join(":", super.generateKey(), appName);
}
}
@@ -0,0 +1,27 @@
package com.bx.imclient.task;
import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.IMSendResult;
import com.bx.imcommon.mq.RedisMQListener;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@RequiredArgsConstructor
@RedisMQListener(queue = IMRedisKey.IM_RESULT_GROUP_QUEUE, batchSize = 100)
public class GroupMessageResultResultTask extends AbstractMessageResultTask<IMSendResult> {
private final MessageListenerMulticaster listenerMulticaster;
@Override
public void onMessage(List<IMSendResult> results) {
listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE, results);
}
}
@@ -0,0 +1,26 @@
package com.bx.imclient.task;
import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.IMSendResult;
import com.bx.imcommon.mq.RedisMQListener;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@RequiredArgsConstructor
@RedisMQListener(queue = IMRedisKey.IM_RESULT_PRIVATE_QUEUE, batchSize = 100)
public class PrivateMessageResultResultTask extends AbstractMessageResultTask<IMSendResult> {
private final MessageListenerMulticaster listenerMulticaster;
@Override
public void onMessage(List<IMSendResult> results) {
listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, results);
}
}
@@ -0,0 +1,25 @@
package com.bx.imclient.task;
import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.IMSendResult;
import com.bx.imcommon.mq.RedisMQListener;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@RequiredArgsConstructor
@RedisMQListener(queue = IMRedisKey.IM_RESULT_SYSTEM_QUEUE, batchSize = 100)
public class SystemMessageResultResultTask extends AbstractMessageResultTask<IMSendResult> {
private final MessageListenerMulticaster listenerMulticaster;
@Override
public void onMessage(List<IMSendResult> results) {
listenerMulticaster.multicast(IMListenerType.SYSTEM_MESSAGE, results);
}
}
@@ -0,0 +1 @@
com.bx.imclient.IMAutoConfiguration
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>box-im</artifactId>
<groupId>com.bx</groupId>
<version>3.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>im-common</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--FastJson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- 引入操作JWT的相关依赖 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,18 @@
package com.bx.imcommon.contant;
public final class IMConstant {
private IMConstant() {
}
/**
* 在线状态过期时间 600s
*/
public static final long ONLINE_TIMEOUT_SECOND = 600;
/**
* 消息允许撤回时间 300s
*/
public static final long ALLOW_RECALL_SECOND = 300;
}
@@ -0,0 +1,40 @@
package com.bx.imcommon.contant;
public final class IMRedisKey {
/**
* im-server最大id,从0开始递增
*/
public static final String IM_MAX_SERVER_ID = "im:max_server_id";
/**
* 用户ID所连接的IM-server的ID
*/
public static final String IM_USER_SERVER_ID = "im:user:server_id";
/**
* 系统消息队列
*/
public static final String IM_MESSAGE_SYSTEM_QUEUE = "im:message:system";
/**
* 私聊消息队列
*/
public static final String IM_MESSAGE_PRIVATE_QUEUE = "im:message:private";
/**
* 群聊消息队列
*/
public static final String IM_MESSAGE_GROUP_QUEUE = "im:message:group";
/**
* 系统消息发送结果队列
*/
public static final String IM_RESULT_SYSTEM_QUEUE = "im:result:system";
/**
* 私聊消息发送结果队列
*/
public static final String IM_RESULT_PRIVATE_QUEUE = "im:result:private";
/**
* 群聊消息发送结果队列
*/
public static final String IM_RESULT_GROUP_QUEUE = "im:result:group";
}
@@ -0,0 +1,55 @@
package com.bx.imcommon.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum IMCmdType {
/**
* 登录
*/
LOGIN(0, "登录"),
/**
* 心跳
*/
HEART_BEAT(1, "心跳"),
/**
* 强制下线
*/
FORCE_LOGUT(2, "强制下线"),
/**
* 私聊消息
*/
PRIVATE_MESSAGE(3, "私聊消息"),
/**
* 群发消息
*/
GROUP_MESSAGE(4, "群发消息"),
/**
* 系统消息
*/
SYSTEM_MESSAGE(5,"系统消息");
private final Integer code;
private final String desc;
public static IMCmdType fromCode(Integer code) {
for (IMCmdType typeEnum : values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}
}
return null;
}
public Integer code() {
return this.code;
}
}
@@ -0,0 +1,34 @@
package com.bx.imcommon.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum IMListenerType {
/**
* 全部消息
*/
ALL(0, "全部消息"),
/**
* 私聊消息
*/
PRIVATE_MESSAGE(1, "私聊消息"),
/**
* 群聊消息
*/
GROUP_MESSAGE(2, "群聊消息"),
/**
* 系统消息
*/
SYSTEM_MESSAGE(3, "群聊消息");
private final Integer code;
private final String desc;
public Integer code() {
return this.code;
}
}
@@ -0,0 +1,32 @@
package com.bx.imcommon.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum IMSendCode {
/**
* 发送成功
*/
SUCCESS(0, "发送成功"),
/**
* 对方当前不在线
*/
NOT_ONLINE(1, "对方当前不在线"),
/**
* 未找到对方的channel
*/
NOT_FIND_CHANNEL(2, "未找到对方的channel"),
/**
* 未知异常
*/
UNKONW_ERROR(9999, "未知异常");
private final Integer code;
private final String desc;
public Integer code() {
return this.code;
}
}
@@ -0,0 +1,51 @@
package com.bx.imcommon.enums;
import lombok.AllArgsConstructor;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@AllArgsConstructor
public enum IMTerminalType {
/**
* web
*/
WEB(0, "web"),
/**
* app
*/
APP(1, "app"),
/**
* pc
*/
PC(2, "pc"),
/**
* 未知
*/
UNKNOW(-1, "未知");
private final Integer code;
private final String desc;
public static IMTerminalType fromCode(Integer code) {
for (IMTerminalType typeEnum : values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}
}
return null;
}
public static List<Integer> codes() {
return Arrays.stream(values()).map(IMTerminalType::code).collect(Collectors.toList());
}
public Integer code() {
return this.code;
}
}
@@ -0,0 +1,44 @@
package com.bx.imcommon.model;
import com.bx.imcommon.enums.IMTerminalType;
import lombok.Data;
import java.util.LinkedList;
import java.util.List;
@Data
public class IMGroupMessage<T> {
/**
* 发送方
*/
private IMUserInfo sender;
/**
* 接收者id列表(群成员列表,为空则不会推送)
*/
private List<Long> recvIds = new LinkedList<>();
/**
* 接收者终端类型,默认全部
*/
private List<Integer> recvTerminals = IMTerminalType.codes();
/**
* 是否发送给自己的其他终端,默认true
*/
private Boolean sendToSelf = true;
/**
* 是否需要回推发送结果,默认true
*/
private Boolean sendResult = true;
/**
* 消息内容
*/
private T data;
}
@@ -0,0 +1,7 @@
package com.bx.imcommon.model;
import lombok.Data;
@Data
public class IMHeartbeatInfo {
}
@@ -0,0 +1,9 @@
package com.bx.imcommon.model;
import lombok.Data;
@Data
public class IMLoginInfo {
private String accessToken;
}
@@ -0,0 +1,44 @@
package com.bx.imcommon.model;
import com.bx.imcommon.enums.IMTerminalType;
import lombok.Data;
import java.util.List;
@Data
public class IMPrivateMessage<T> {
/**
* 发送方
*/
private IMUserInfo sender;
/**
* 接收者id
*/
private Long recvId;
/**
* 接收者终端类型,默认全部
*/
private List<Integer> recvTerminals = IMTerminalType.codes();
/**
* 是否同步消息给自己的其他终端,默认true
*/
private Boolean sendToSelf = true;
/**
* 是否需要回推发送结果,默认true
*/
private Boolean sendResult = true;
/**
* 消息内容
*/
private T data;
}
@@ -0,0 +1,40 @@
package com.bx.imcommon.model;
import lombok.Data;
import java.util.List;
@Data
public class IMRecvInfo {
/**
* 命令类型 IMCmdType
*/
private Integer cmd;
/**
* 发送方
*/
private IMUserInfo sender;
/**
* 接收方用户列表
*/
List<IMUserInfo> receivers;
/**
* 是否需要回调发送结果
*/
private Boolean sendResult;
/**
* 当前服务名(回调发送结果使用)
*/
private String serviceName;
/**
* 推送消息体
*/
private Object data;
}
@@ -0,0 +1,18 @@
package com.bx.imcommon.model;
import lombok.Data;
@Data
public class IMSendInfo<T> {
/**
* 命令
*/
private Integer cmd;
/**
* 推送消息体
*/
private T data;
}
@@ -0,0 +1,28 @@
package com.bx.imcommon.model;
import lombok.Data;
@Data
public class IMSendResult<T> {
/**
* 发送方
*/
private IMUserInfo sender;
/**
* 接收方
*/
private IMUserInfo receiver;
/**
* 发送状态编码 IMSendCode
*/
private Integer code;
/**
* 消息内容
*/
private T data;
}
@@ -0,0 +1,17 @@
package com.bx.imcommon.model;
import lombok.Data;
@Data
public class IMSessionInfo {
/**
* 用户id
*/
private Long userId;
/**
* 终端类型
*/
private Integer terminal;
}
@@ -0,0 +1,34 @@
package com.bx.imcommon.model;
import com.bx.imcommon.enums.IMTerminalType;
import lombok.Data;
import java.util.LinkedList;
import java.util.List;
@Data
public class IMSystemMessage<T> {
/**
* 接收者id列表,为空表示向所有在线用户广播
*/
private List<Long> recvIds = new LinkedList<>();
/**
* 接收者终端类型,默认全部
*/
private List<Integer> recvTerminals = IMTerminalType.codes();
/**
* 是否需要回推发送结果,默认true
*/
private Boolean sendResult = true;
/**
* 消息内容
*/
private T data;
}
@@ -0,0 +1,28 @@
package com.bx.imcommon.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author: Blue
* @date: 2023-09-24 09:23:11
* @version: 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IMUserInfo {
/**
* 用户id
*/
private Long id;
/**
* 用户终端类型 IMTerminalType
*/
private Integer terminal;
}
@@ -0,0 +1,32 @@
package com.bx.imcommon.mq;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisMQConfig {
@Bean
public RedisMQTemplate redisMQTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisMQTemplate redisMQTemplate = new RedisMQTemplate();
redisMQTemplate.setConnectionFactory(redisConnectionFactory);
// 设置值(value)的序列化采用FastJsonRedisSerializer
redisMQTemplate.setValueSerializer(fastJsonRedisSerializer());
redisMQTemplate.setHashValueSerializer(fastJsonRedisSerializer());
// 设置键(key)的序列化采用StringRedisSerializer。
redisMQTemplate.setKeySerializer(new StringRedisSerializer());
redisMQTemplate.setHashKeySerializer(new StringRedisSerializer());
redisMQTemplate.afterPropertiesSet();
return redisMQTemplate;
}
@Bean
public FastJsonRedisSerializer fastJsonRedisSerializer(){
return new FastJsonRedisSerializer<>(Object.class);
}
}
@@ -0,0 +1,35 @@
package com.bx.imcommon.mq;
import java.util.List;
/**
* redis 队列消费者抽象类
*/
public abstract class RedisMQConsumer<T> {
/**
* 消费消息回调(单条)
*/
public void onMessage(T data){}
/**
* 消费消息回调(批量)
*/
public void onMessage(List<T> datas){}
/**
* 生成redis队列完整key
*/
public String generateKey(){
// 默认队列名就是redis的key
RedisMQListener annotation = this.getClass().getAnnotation(RedisMQListener.class);
return annotation.queue();
}
/**
* 队列是否就绪,返回true才会开始消费
*/
public Boolean isReady(){
return true;
}
}
@@ -0,0 +1,29 @@
package com.bx.imcommon.mq;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* redis 队列消费监听注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RedisMQListener {
/**
* 队列,也是redis的key
*/
String queue();
/**
* 一次性拉取的数据数量
*/
int batchSize() default 1;
/**
* 拉取间隔周期,单位:ms
*/
int period() default 100;
}
@@ -0,0 +1,116 @@
package com.bx.imcommon.mq;
import com.alibaba.fastjson.JSONObject;
import com.bx.imcommon.util.ThreadPoolExecutorFactory;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* reids 队列拉取定时任务
*
* @author: Blue
* @date: 2024-07-15
* @version: 1.0
*/
@Slf4j
@Component
public class RedisMQPullTask implements CommandLineRunner {
private static final ScheduledThreadPoolExecutor EXECUTOR = ThreadPoolExecutorFactory.getThreadPoolExecutor();
@Autowired(required = false)
private List<RedisMQConsumer> consumers = Collections.emptyList();
@Autowired
private RedisMQTemplate redisMQTemplate;
@Override
public void run(String... args) {
consumers.forEach((consumer -> {
// 注解参数
RedisMQListener annotation = consumer.getClass().getAnnotation(RedisMQListener.class);
String queue = annotation.queue();
int batchSize = annotation.batchSize();
int period = annotation.period();
// 获取泛型类型
Type superClass = consumer.getClass().getGenericSuperclass();
Type type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
EXECUTOR.execute(new Runnable() {
@Override
public void run() {
List<Object> datas = new LinkedList<>();
try {
if (consumer.isReady()) {
String key = consumer.generateKey();
// 拉取一个批次的数据
List<Object> objects = pullBatch(key, batchSize);
for (Object obj : objects) {
if (obj instanceof JSONObject) {
JSONObject jsonObject = (JSONObject)obj;
Object data = jsonObject.toJavaObject(type);
consumer.onMessage(data);
datas.add(data);
}
}
if (!datas.isEmpty()) {
consumer.onMessage(datas);
}
}
} catch (Exception e) {
log.error("数据消费异常,队列:{}", queue, e);
// 出现异常,10s后再重新尝试消费
EXECUTOR.schedule(this, 10, TimeUnit.SECONDS);
return;
}
// 继续消费数据
if (!EXECUTOR.isShutdown()) {
if (datas.size() < batchSize) {
// 数据已经消费完,等待下一个周期继续拉取
EXECUTOR.schedule(this, period, TimeUnit.MILLISECONDS);
} else {
// 数据没有消费完,直接开启下一个消费周期
EXECUTOR.execute(this);
}
}
}
});
}));
}
private List<Object> pullBatch(String key, Integer batchSize) {
List<Object> objects = new LinkedList<>();
if (redisMQTemplate.isSupportBatchPull()) {
// 版本大于6.2,支持批量拉取
objects = redisMQTemplate.opsForList().leftPop(key, batchSize);
} else {
// 版本小于6.2,只能逐条拉取
Object obj = redisMQTemplate.opsForList().leftPop(key);
while (!Objects.isNull(obj) && objects.size() < batchSize) {
objects.add(obj);
obj = redisMQTemplate.opsForList().leftPop(key);
}
if (!Objects.isNull(obj)){
objects.add(obj);
}
}
return objects;
}
@PreDestroy
public void destory() {
log.info("消费线程停止...");
ThreadPoolExecutorFactory.shutDown();
}
}
@@ -0,0 +1,44 @@
package com.bx.imcommon.mq;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Properties;
/**
* @author: Blue
* @date: 2024-07-16
* @version: 1.0
*/
public class RedisMQTemplate extends RedisTemplate<String, Object> {
private String version = Strings.EMPTY;
public String getVersion() {
if (version.isEmpty()) {
RedisConnection connection = RedisConnectionUtils.getConnection(getConnectionFactory());
Properties properties = connection.info();
version = properties.getProperty("redis_version");
RedisConnectionUtils.releaseConnection(connection,getConnectionFactory());
}
return version;
}
/**
* 是否支持批量拉取,redis版本大于6.2支持批量拉取
* @return
*/
Boolean isSupportBatchPull() {
String version = getVersion();
String[] arr = version.split("\\.");
if (arr.length < 2) {
return false;
}
Integer firVersion = Integer.valueOf(arr[0]);
Integer secVersion = Integer.valueOf(arr[1]);
return firVersion > 6 || (firVersion == 6 && secVersion >= 2);
}
}
@@ -0,0 +1,28 @@
package com.bx.imcommon.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import java.io.IOException;
import java.util.Date;
public class DateToLongSerializer extends JsonSerializer<Date> {
@Override
public void serialize(Date date, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumber(date.getTime());
}
@Override
public void serializeWithType(Date value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen,
typeSer.typeId(value, JsonToken.VALUE_STRING));
serialize(value, gen, serializers);
typeSer.writeTypeSuffix(gen, typeIdDef);
}
}
@@ -0,0 +1,89 @@
package com.bx.imcommon.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 逗号分格文本处理工具类
*
* @author: blue
* @date: 2023-11-09 09:52:49
* @version: 1.0
*/
public class CommaTextUtils {
/**
* 文本转列表
*
* @param strText 文件
* @return 列表
*/
public static List<String> asList(String strText) {
if (StrUtil.isEmpty(strText)) {
return new LinkedList<>();
}
return new LinkedList<>(Arrays.asList(strText.split(",")));
}
/**
* 列表转字符串,并且自动清空、去重、排序
*
* @param texts 列表
* @return 文本
*/
public static <T> String asText(Collection<T> texts) {
if (CollUtil.isEmpty(texts)) {
return StrUtil.EMPTY;
}
return texts.stream().map(text -> StrUtil.toString(text)).filter(StrUtil::isNotEmpty).distinct().sorted().collect(Collectors.joining(","));
}
/**
* 追加一个单词
*
* @param strText 文本
* @param word 单词
* @return 文本
*/
public static <T> String appendWord(String strText, T word) {
List<String> texts = asList(strText);
texts.add(StrUtil.toString(word));
return asText(texts);
}
/**
* 删除一个单词
*
* @param strText 文本
* @param word 单词
* @return 文本
*/
public static <T> String removeWord(String strText, T word) {
List<String> texts = asList(strText);
texts.remove(StrUtil.toString(word));
return asText(texts);
}
/**
* 合并
*
* @param strText1 文本1
* @param strText2 文本2
* @return 文本
*/
public static String merge(String strText1, String strText2) {
List<String> texts = asList(strText1);
texts.addAll(asList(strText2));
return asText(texts);
}
}
@@ -0,0 +1,89 @@
package com.bx.imcommon.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import java.util.Date;
public final class JwtUtil {
private JwtUtil() {
}
/**
* 生成jwt字符串 JWT(json web token)
*
* @param userId 用户id
* @param info 用户细腻系
* @param expireIn 过期时间
* @param secret 秘钥
* @return token
*/
public static String sign(Long userId, String info, long expireIn, String secret) {
try {
Date date = new Date(System.currentTimeMillis() + expireIn * 1000);
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
//将userId保存到token里面
.withAudience(userId.toString())
//存放自定义数据
.withClaim("info", info)
//过期时间
.withExpiresAt(date)
//token的密钥
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
/**
* 根据token获取userId
*
* @param token 登录token
* @return 用户id
*/
public static Long getUserId(String token) {
try {
String userId = JWT.decode(token).getAudience().get(0);
return Long.parseLong(userId);
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 根据token获取用户数据
*
* @param token 用户登录token
* @return 用户数据
*/
public static String getInfo(String token) {
try {
return JWT.decode(token).getClaim("info").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 校验token
*
* @param token 用户登录token
* @param secret 秘钥
* @return true/false
*/
public static Boolean checkSign(String token, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(token);
return true;
} catch (JWTVerificationException e) {
return false;
}
}
}
@@ -0,0 +1,87 @@
package com.bx.imcommon.util;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 创建单例线程池
*
* @author Andrews
* @date 2023/11/30 11:12
*/
@Slf4j
public final class ThreadPoolExecutorFactory {
/**
* 机器的CPU核数:Runtime.getRuntime().availableProcessors()
* corePoolSize 池中所保存的线程数,包括空闲线程。
* CPU 密集型:核心线程数 = CPU核数 + 1
* IO 密集型:核心线程数 = CPU核数 * 2
*/
private static final int CORE_POOL_SIZE =
Math.min(ThreadPoolExecutorFactory.MAX_IMUM_POOL_SIZE, Runtime.getRuntime().availableProcessors() * 2);
/**
* maximumPoolSize - 池中允许的最大线程数(采用LinkedBlockingQueue时没有作用)。
*/
private static final int MAX_IMUM_POOL_SIZE = 100;
/**
* keepAliveTime -当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间,线程池维护线程所允许的空闲时间
*/
private static final int KEEP_ALIVE_TIME = 1000;
/**
* 等待队列的大小。默认是无界的,性能损耗的关键
*/
private static final int QUEUE_SIZE = 200;
/**
* 线程池对象
*/
private static volatile ScheduledThreadPoolExecutor threadPoolExecutor = null;
/**
* 构造方法私有化
*/
private ThreadPoolExecutorFactory() {
if (null == threadPoolExecutor) {
threadPoolExecutor = ThreadPoolExecutorFactory.getThreadPoolExecutor();
}
}
/**
* 双检锁创建线程安全的单例
*/
public static ScheduledThreadPoolExecutor getThreadPoolExecutor() {
if (null == threadPoolExecutor) {
synchronized (ThreadPoolExecutorFactory.class) {
if (null == threadPoolExecutor) {
threadPoolExecutor = new ScheduledThreadPoolExecutor(
//核心线程数
CORE_POOL_SIZE,
//拒绝策略
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
}
return threadPoolExecutor;
}
/**
* 关闭线程池
*/
public static void shutDown() {
if (threadPoolExecutor != null) {
threadPoolExecutor.shutdown();
}
}
public static void execute(Runnable runnable) {
if (runnable == null) {
return;
}
threadPoolExecutor.execute(runnable);
}
}
+111
View File
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>box-im</artifactId>
<groupId>com.bx</groupId>
<version>3.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>im-platform</artifactId>
<dependencies>
<dependency>
<groupId>com.bx</groupId>
<artifactId>im-client</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>6.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--minio-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!--thumbnailator图片处理-->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,23 @@
package com.bx.implatform;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Slf4j
@EnableAspectJAutoProxy(exposeProxy = true)
@ComponentScan(basePackages = {"com.bx"})
@MapperScan(basePackages = {"com.bx.implatform.mapper"})
@SpringBootApplication
public class IMPlatformApp {
public static void main(String[] args) {
SpringApplication.run(IMPlatformApp.class, args);
}
}
@@ -0,0 +1,15 @@
package com.bx.implatform.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 在线校验,标注此注解的接口用户必须保持长连接,否则将抛异常
*/
@Retention(RetentionPolicy.RUNTIME)//运行时生效
@Target(ElementType.METHOD)//作用在方法上
public @interface OnlineCheck {
}
@@ -0,0 +1,37 @@
package com.bx.implatform.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁注解
*/
@Retention(RetentionPolicy.RUNTIME)//运行时生效
@Target(ElementType.METHOD)//作用在方法上
public @interface RedisLock {
/**
* key的前缀,prefixKey+key就是redis的key
*/
String prefixKey() ;
/**
* spel 表达式
*/
String key() default "";
/**
* 等待锁的时间,默认-1,不等待直接失败,redisson默认也是-1
*/
int waitTime() default -1;
/**
* 等待锁的时间单位,默认毫秒
*
*/
TimeUnit unit() default TimeUnit.MILLISECONDS;
}
@@ -0,0 +1,31 @@
package com.bx.implatform.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 防止表单重复提交注解
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
/**
* 间隔时间,小于此时间视为重复提交
*/
int interval() default 5000;
/**
* 间隔时间单位
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* 提示消息
*/
String message() default "请勿提交重复请求";
}
@@ -0,0 +1,36 @@
package com.bx.implatform.aspect;
import com.bx.imclient.IMClient;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author: blue
* @date: 2024-06-16
* @version: 1.0
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class OnlineCheckAspect {
private final IMClient imClient;
@Around("@annotation(com.bx.implatform.annotation.OnlineCheck)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
UserSession session = SessionContext.getSession();
if(!imClient.isOnline(session.getUserId())){
throw new GlobalException("您当前的网络连接已断开,请稍后重试");
}
return joinPoint.proceed();
}
}
@@ -0,0 +1,85 @@
package com.bx.implatform.aspect;
import cn.hutool.core.util.StrUtil;
import com.bx.implatform.annotation.RedisLock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* @author: blue
* @date: 2024-06-09
* @version: 1.0
*/
@Slf4j
@Aspect
@Order(0)
@Component
@RequiredArgsConstructor
public class RedisLockAspect {
private ExpressionParser parser = new SpelExpressionParser();
private DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
private final RedissonClient redissonClient;
@Around("@annotation(com.bx.implatform.annotation.RedisLock)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
RedisLock annotation = method.getAnnotation(RedisLock.class);
// 解析表达式中的key
String key = parseKey(joinPoint);
String lockKey = StrUtil.join(":",annotation.prefixKey(),key);
// 上锁
RLock lock = redissonClient.getLock(lockKey);
lock.lock(annotation.waitTime(),annotation.unit());
try {
// 执行方法
return joinPoint.proceed();
}finally {
lock.unlock();
}
}
private String parseKey(ProceedingJoinPoint joinPoint){
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
RedisLock annotation = method.getAnnotation(RedisLock.class);
String key = annotation.key();
if(StrUtil.isEmpty(key)){
return Strings.EMPTY;
}
// el解析需要的上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 参数名
String[] params = parameterNameDiscoverer.getParameterNames(method);
if(Objects.isNull(params)){
return key;
}
Object[] args = joinPoint.getArgs();
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);//所有参数都作为原材料扔进去
}
Expression expression = parser.parseExpression(key);
return expression.getValue(context, String.class);
}
}
@@ -0,0 +1,100 @@
package com.bx.implatform.aspect;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.bx.implatform.annotation.RepeatSubmit;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.session.SessionContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
/**
* @author: blue
* @date: 2024-12-08
* @version: 1.0
*/
@Aspect
@Component
@AllArgsConstructor
public class RepeatSubmitAspect {
private final RedisTemplate<String, Object> redisTemplate;
@Before("@annotation(repeatSubmit)")
public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
// 如果注解不为0 则使用注解数值
long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
HttpServletRequest request =
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
String url = request.getRequestURL().toString();
Long userId = SessionContext.getSession().getUserId();
String reqParams = argsArrayToString(point.getArgs());
String md5 = SecureUtil.md5(StrUtil.join(":", userId, url, reqParams));
// 唯一标识
String key = String.join(":",RedisKey.IM_REPEAT_SUBMIT,md5) ;
if(redisTemplate.hasKey(key)){
throw new GlobalException(repeatSubmit.message());
}
redisTemplate.opsForValue().set(key,1,repeatSubmit.interval(),repeatSubmit.timeUnit());
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
StringJoiner params = new StringJoiner(" ");
if (ArrayUtil.isEmpty(paramsArray)) {
return params.toString();
}
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
params.add(JSON.toJSONString(o));
}
}
return params.toString();
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection)o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map)o;
for (Object value : map.values()) {
return value instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
}
}
@@ -0,0 +1,11 @@
package com.bx.implatform.config;
import lombok.Data;
@Data
public class ICEServer {
private String urls;
private String username;
private String credential;
}
@@ -0,0 +1,19 @@
package com.bx.implatform.config;
import com.bx.implatform.config.props.MinioProperties;
import io.minio.MinioClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MinIoClientConfig {
@Bean
public MinioClient minioClient(MinioProperties minioProps) {
// 注入minio 客户端
return MinioClient.builder()
.endpoint(minioProps.getEndpoint())
.credentials(minioProps.getAccessKey(), minioProps.getSecretKey())
.build();
}
}
@@ -0,0 +1,34 @@
package com.bx.implatform.config;
import com.bx.implatform.interceptor.AuthInterceptor;
import com.bx.implatform.interceptor.XssInterceptor;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@AllArgsConstructor
public class MvcConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
private final XssInterceptor xssInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(xssInterceptor).addPathPatterns("/**").excludePathPatterns("/error");
registry.addInterceptor(authInterceptor).addPathPatterns("/**")
.excludePathPatterns("/login", "/logout", "/register", "/refreshToken", "/swagger/**", "/v3/api-docs/**",
"/swagger-resources/**", "/swagger-ui.html", "/swagger-ui/**", "/doc.html");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 使用BCrypt加密密码
return new BCryptPasswordEncoder();
}
}
@@ -0,0 +1,74 @@
package com.bx.implatform.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import jakarta.annotation.Resource;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Resource
private RedisConnectionFactory factory;
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置值(value)的序列化采用jackson2JsonRedisSerializer
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
// 设置键(key)的序列化采用StringRedisSerializer。
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public CacheManager cacheManager() {
// 设置redis缓存管理器
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
}
@Bean
public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 解决jackson2无法反序列化LocalDateTime的问题
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
om.registerModule(new JavaTimeModule());
om.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略空值
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//忽略无效字段
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return new Jackson2JsonRedisSerializer<>(om, Object.class);
}
}
@@ -0,0 +1,38 @@
package com.bx.implatform.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public GroupedOpenApi userApi() {
String[] paths = {"/**"};
String[] packagedToMatch = {"com.bx"};
return GroupedOpenApi.builder().group("IM-Platform")
.pathsToMatch(paths)
.packagesToScan(packagedToMatch).build();
}
@Bean
public OpenAPI customOpenAPI() {
Contact contact = new Contact();
contact.setName("Blue");
return new OpenAPI().info(new Info()
.title("盒子IM接口文档")
.description("盒子IM业务平台服务")
.contact(contact)
.version("3.0")
.termsOfService("https://www.boxim.online")
.license(new License().name("MIT")
.url("https://www.boxim.online")));
}
}
@@ -0,0 +1,27 @@
package com.bx.implatform.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* @author: Blue
* @date: 2024-09-01
* @version: 1.0
*/
@EnableScheduling
@Configuration
public class TaskSchedulerConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10); // 设置线程池大小
taskScheduler.setThreadNamePrefix("scheduled-task-");
taskScheduler.initialize();
return taskScheduler;
}
}
@@ -0,0 +1,19 @@
package com.bx.implatform.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Data
@Component
@ConfigurationProperties(prefix = "webrtc")
public class WebrtcConfig {
private Integer maxChannel = 9;
private List<ICEServer> iceServers = new ArrayList<>();
}
@@ -0,0 +1,22 @@
package com.bx.implatform.config.props;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component
public class JwtProperties {
@Value("${jwt.accessToken.expireIn}")
private Integer accessTokenExpireIn;
@Value("${jwt.accessToken.secret}")
private String accessTokenSecret;
@Value("${jwt.refreshToken.expireIn}")
private Integer refreshTokenExpireIn;
@Value("${jwt.refreshToken.secret}")
private String refreshTokenSecret;
}
@@ -0,0 +1,34 @@
package com.bx.implatform.config.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author: Blue
* @date: 2024-09-28
* @version: 1.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
private String endpoint;
private String accessKey;
private String secretKey;
private String domain;
private String bucketName;
private String imagePath;
private String filePath;
private String videoPath;
private Integer expireIn;
}
@@ -0,0 +1,33 @@
package com.bx.implatform.contant;
public final class Constant {
/**
* 系统用户id
*/
public static final Long SYS_USER_ID = 0L;
/**
* 最大图片上传大小
*/
public static final Long MAX_IMAGE_SIZE = 20 * 1024 * 1024L;
/**
* 最大上传文件大小
*/
public static final Long MAX_FILE_SIZE = 20 * 1024 * 1024L;
/**
* 最大文件名长度
*/
public static final Long MAX_FILE_NAME_LENGTH = 128L;
/**
* 大群人数上限
*/
public static final Long MAX_LARGE_GROUP_MEMBER = 3000L;
/**
* 普通群人数上限
*/
public static final Long MAX_NORMAL_GROUP_MEMBER = 500L;
}
@@ -0,0 +1,72 @@
package com.bx.implatform.contant;
public final class RedisKey {
/**
* 用户状态 无值:空闲 1:正在忙
*/
public static final String IM_USER_STATE = "im:user:state";
/**
* 已读群聊消息位置(已读最大id)
*/
public static final String IM_GROUP_READED_POSITION = "im:readed:group:position";
/**
* webrtc 单人通话
*/
public static final String IM_WEBRTC_PRIVATE_SESSION = "im:webrtc:private:session";
/**
* webrtc 群通话
*/
public static final String IM_WEBRTC_GROUP_SESSION = "im:webrtc:group:session";
/**
* 用户被封禁消息队列
*/
public static final String IM_QUEUE_USER_BANNED = "im:queue:user:banned";
/**
* 群聊被封禁消息队列
*/
public static final String IM_QUEUE_GROUP_BANNED = "im:queue:group:banned";
/**
* 群聊解封消息队列
*/
public static final String IM_QUEUE_GROUP_UNBAN = "im:queue:group:unban";
/**
* 缓存是否好友:bool
*/
public static final String IM_CACHE_FRIEND = "im:cache:friend";
/**
* 缓存群聊信息
*/
public static final String IM_CACHE_GROUP = "im:cache:group";
/**
* 缓存群聊成员id
*/
public static final String IM_CACHE_GROUP_MEMBER_ID = "im:cache:group_member_ids";
/**
* 重复提交
*/
public static final String IM_REPEAT_SUBMIT = "im:repeat:submit";
/**
* 分布式锁-添加好友
*/
public static final String IM_LOCK_FRIEND_ADD = "im:lock:friend:add";
/**
* 分布式锁-进入群聊
*/
public static final String IM_LOCK_GROUP_ENTER = "im:lock:group:enter";
/**
* 分布式锁-清理过期文件
*/
public static final String IM_LOCK_FILE_TASK = "im:lock:task:file";
}
@@ -0,0 +1,38 @@
package com.bx.implatform.controller;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.FileService;
import com.bx.implatform.vo.UploadImageVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@Slf4j
@RestController
@Tag(name = "文件上传")
@RequiredArgsConstructor
public class FileController {
private final FileService fileService;
@Operation(summary = "上传图片", description = "上传图片,上传后返回原图和缩略图的url")
@PostMapping("/image/upload")
public Result<UploadImageVO> uploadImage(@RequestParam("file") MultipartFile file,
@RequestParam(defaultValue = "true") Boolean isPermanent, @RequestParam(defaultValue = "50") Long thumbSize) {
return ResultUtils.success(fileService.uploadImage(file, isPermanent,thumbSize));
}
@Operation(summary = "上传文件", description = "上传文件,上传后返回文件url")
@PostMapping("/file/upload")
public Result<String> uploadFile(@RequestParam("file") MultipartFile file) {
return ResultUtils.success(fileService.uploadFile(file), Strings.EMPTY);
}
}
@@ -0,0 +1,64 @@
package com.bx.implatform.controller;
import com.bx.implatform.annotation.RepeatSubmit;
import com.bx.implatform.dto.FriendDndDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.FriendService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.vo.FriendVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "好友")
@RestController
@RequestMapping("/friend")
@RequiredArgsConstructor
public class FriendController {
private final FriendService friendService;
@GetMapping("/list")
@Operation(summary = "好友列表", description = "获取好友列表")
public Result<List<FriendVO>> findFriends() {
return ResultUtils.success(friendService.findFriends());
}
@RepeatSubmit
@PostMapping("/add")
@Operation(summary = "添加好友", description = "双方建立好友关系")
public Result addFriend(@NotNull(message = "好友id不可为空") @RequestParam Long friendId) {
friendService.addFriend(SessionContext.getSession().getUserId(),friendId);
return ResultUtils.success();
}
@GetMapping("/find/{friendId}")
@Operation(summary = "查找好友信息", description = "查找好友信息")
public Result<FriendVO> findFriend(@NotNull(message = "好友id不可为空") @PathVariable Long friendId) {
return ResultUtils.success(friendService.findFriend(friendId));
}
@DeleteMapping("/delete/{friendId}")
@Operation(summary = "删除好友", description = "解除好友关系")
public Result delFriend(@NotNull(message = "好友id不可为空") @PathVariable Long friendId) {
friendService.delFriend(friendId);
return ResultUtils.success();
}
@PutMapping("/dnd")
@Operation(summary = "开启/关闭免打扰状态", description = "开启/关闭免打扰状态")
public Result setFriendDnd(@Valid @RequestBody FriendDndDTO dto) {
friendService.setDnd(dto);
return ResultUtils.success();
}
}
@@ -0,0 +1,113 @@
package com.bx.implatform.controller;
import com.bx.implatform.annotation.RepeatSubmit;
import com.bx.implatform.dto.GroupDndDTO;
import com.bx.implatform.dto.GroupInviteDTO;
import com.bx.implatform.dto.GroupMemberRemoveDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.GroupService;
import com.bx.implatform.vo.GroupMemberVO;
import com.bx.implatform.vo.GroupVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "群聊")
@RestController
@RequestMapping("/group")
@RequiredArgsConstructor
public class GroupController {
private final GroupService groupService;
@RepeatSubmit
@Operation(summary = "创建群聊", description = "创建群聊")
@PostMapping("/create")
public Result<GroupVO> createGroup(@Valid @RequestBody GroupVO vo) {
return ResultUtils.success(groupService.createGroup(vo));
}
@RepeatSubmit
@Operation(summary = "修改群聊信息", description = "修改群聊信息")
@PutMapping("/modify")
public Result<GroupVO> modifyGroup(@Valid @RequestBody GroupVO vo) {
return ResultUtils.success(groupService.modifyGroup(vo));
}
@RepeatSubmit
@Operation(summary = "解散群聊", description = "解散群聊")
@DeleteMapping("/delete/{groupId}")
public Result deleteGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) {
groupService.deleteGroup(groupId);
return ResultUtils.success();
}
@Operation(summary = "查询群聊", description = "查询单个群聊信息")
@GetMapping("/find/{groupId}")
public Result<GroupVO> findGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) {
return ResultUtils.success(groupService.findById(groupId));
}
@Operation(summary = "查询群聊列表", description = "查询群聊列表")
@GetMapping("/list")
public Result<List<GroupVO>> findGroups() {
return ResultUtils.success(groupService.findGroups());
}
@RepeatSubmit
@Operation(summary = "邀请进群", description = "邀请好友进群")
@PostMapping("/invite")
public Result invite(@Valid @RequestBody GroupInviteDTO dto) {
groupService.invite(dto);
return ResultUtils.success();
}
@Operation(summary = "查询群聊成员", description = "查询群聊成员")
@GetMapping("/members/{groupId}")
public Result<List<GroupMemberVO>> findGroupMembers(
@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) {
return ResultUtils.success(groupService.findGroupMembers(groupId));
}
@RepeatSubmit
@Operation(summary = "将成员移出群聊", description = "将成员移出群聊")
@DeleteMapping("/members/remove")
public Result removeMembers(@Valid @RequestBody GroupMemberRemoveDTO dto) {
groupService.removeGroupMembers(dto);
return ResultUtils.success();
}
@RepeatSubmit
@Operation(summary = "退出群聊", description = "退出群聊")
@DeleteMapping("/quit/{groupId}")
public Result quitGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) {
groupService.quitGroup(groupId);
return ResultUtils.success();
}
@RepeatSubmit
@Operation(summary = "踢出群聊(已废弃)", description = "将用户踢出群聊")
@DeleteMapping("/kick/{groupId}")
public Result kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId,
@NotNull(message = "用户id不能为空") @RequestParam Long userId) {
groupService.kickGroup(groupId, userId);
return ResultUtils.success();
}
@Operation(summary = "开启/关闭免打扰", description = "开启/关闭免打扰")
@PutMapping("/dnd")
public Result setGroupDnd(@Valid @RequestBody GroupDndDTO dto) {
groupService.setDnd(dto);
return ResultUtils.success();
}
}
@@ -0,0 +1,73 @@
package com.bx.implatform.controller;
import com.bx.implatform.dto.GroupMessageDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.GroupMessageService;
import com.bx.implatform.vo.GroupMessageVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "群聊消息")
@RestController
@RequestMapping("/message/group")
@RequiredArgsConstructor
public class GroupMessageController {
private final GroupMessageService groupMessageService;
@PostMapping("/send")
@Operation(summary = "发送群聊消息", description = "发送群聊消息")
public Result<GroupMessageVO> sendMessage(@Valid @RequestBody GroupMessageDTO vo) {
return ResultUtils.success(groupMessageService.sendMessage(vo));
}
@DeleteMapping("/recall/{id}")
@Operation(summary = "撤回消息", description = "撤回群聊消息")
public Result<GroupMessageVO> recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) {
return ResultUtils.success(groupMessageService.recallMessage(id));
}
@GetMapping("/pullOfflineMessage")
@Operation(summary = "拉取离线消息(已废弃)", description = "拉取离线消息,消息将通过webscoket异步推送")
public Result pullOfflineMessage(@RequestParam Long minId) {
groupMessageService.pullOfflineMessage(minId);
return ResultUtils.success();
}
@GetMapping(value = "/loadOfflineMessage")
@Operation(summary = "拉取离线消息", description = "拉取离线消息")
public Result<List<GroupMessageVO>> loadOfflineMessage(@RequestParam Long minId) {
return ResultUtils.success(groupMessageService.loadOffineMessage(minId));
}
@PutMapping("/readed")
@Operation(summary = "消息已读", description = "将群聊中的消息状态置为已读")
public Result readedMessage(@RequestParam Long groupId) {
groupMessageService.readedMessage(groupId);
return ResultUtils.success();
}
@GetMapping("/findReadedUsers")
@Operation(summary = "获取已读用户id", description = "获取消息已读用户列表")
public Result<List<Long>> findReadedUsers(@RequestParam Long groupId,
@RequestParam Long messageId) {
return ResultUtils.success(groupMessageService.findReadedUsers(groupId, messageId));
}
@GetMapping("/history")
@Operation(summary = "查询聊天记录", description = "查询聊天记录")
public Result<List<GroupMessageVO>> recallMessage(@NotNull(message = "群聊id不能为空") @RequestParam Long groupId,
@NotNull(message = "页码不能为空") @RequestParam Long page,
@NotNull(message = "size不能为空") @RequestParam Long size) {
return ResultUtils.success(groupMessageService.findHistoryMessage(groupId, page, size));
}
}
@@ -0,0 +1,51 @@
package com.bx.implatform.controller;
import com.bx.implatform.dto.LoginDTO;
import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.dto.RegisterDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.UserService;
import com.bx.implatform.vo.LoginVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "注册登录")
@RestController
@RequiredArgsConstructor
public class LoginController {
private final UserService userService;
@PostMapping("/login")
@Operation(summary = "用户登录", description = "用户登录")
public Result<LoginVO> login(@Valid @RequestBody LoginDTO dto) {
LoginVO vo = userService.login(dto);
return ResultUtils.success(vo);
}
@PutMapping("/refreshToken")
@Operation(summary = "刷新token", description = "用refreshtoken换取新的token")
public Result refreshToken(@RequestHeader("refreshToken") String refreshToken) {
LoginVO vo = userService.refreshToken(refreshToken);
return ResultUtils.success(vo);
}
@PostMapping("/register")
@Operation(summary = "用户注册", description = "用户注册")
public Result register(@Valid @RequestBody RegisterDTO dto) {
userService.register(dto);
return ResultUtils.success();
}
@PutMapping("/modifyPwd")
@Operation(summary = "修改密码", description = "修改用户密码")
public Result modifyPassword(@Valid @RequestBody ModifyPwdDTO dto) {
userService.modifyPassword(dto);
return ResultUtils.success();
}
}
@@ -0,0 +1,74 @@
package com.bx.implatform.controller;
import com.bx.implatform.dto.PrivateMessageDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.PrivateMessageService;
import com.bx.implatform.vo.PrivateMessageVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "私聊消息")
@RestController
@RequestMapping("/message/private")
@RequiredArgsConstructor
public class PrivateMessageController {
private final PrivateMessageService privateMessageService;
@PostMapping("/send")
@Operation(summary = "发送消息", description = "发送私聊消息")
public Result<PrivateMessageVO> sendMessage(@Valid @RequestBody PrivateMessageDTO vo) {
return ResultUtils.success(privateMessageService.sendMessage(vo));
}
@DeleteMapping("/recall/{id}")
@Operation(summary = "撤回消息", description = "撤回私聊消息")
public Result<PrivateMessageVO> recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) {
return ResultUtils.success( privateMessageService.recallMessage(id));
}
@GetMapping("/pullOfflineMessage")
@Operation(summary = "拉取离线消息(已废弃)", description = "拉取离线消息,消息将通过webscoket异步推送")
public Result pullOfflineMessage(@RequestParam Long minId) {
privateMessageService.pullOfflineMessage(minId);
return ResultUtils.success();
}
@GetMapping(value = "/loadOfflineMessage")
@Operation(summary = "拉取离线消息", description = "拉取离线消息")
public Result<List<PrivateMessageVO>> loadOfflineMessage(@RequestParam Long minId) {
return ResultUtils.success(privateMessageService.loadOfflineMessage(minId));
}
@PutMapping("/readed")
@Operation(summary = "消息已读", description = "将会话中接收的消息状态置为已读")
public Result readedMessage(@RequestParam Long friendId) {
privateMessageService.readedMessage(friendId);
return ResultUtils.success();
}
@GetMapping("/maxReadedId")
@Operation(summary = "获取最大已读消息的id", description = "获取某个会话中已读消息的最大id")
public Result<Long> getMaxReadedId(@RequestParam Long friendId) {
return ResultUtils.success(privateMessageService.getMaxReadedId(friendId));
}
@GetMapping("/history")
@Operation(summary = "查询聊天记录", description = "查询聊天记录")
public Result<List<PrivateMessageVO>> recallMessage(
@NotNull(message = "好友id不能为空") @RequestParam Long friendId,
@NotNull(message = "页码不能为空") @RequestParam Long page,
@NotNull(message = "size不能为空") @RequestParam Long size) {
return ResultUtils.success(privateMessageService.findHistoryMessage(friendId, page, size));
}
}
@@ -0,0 +1,36 @@
package com.bx.implatform.controller;
import com.bx.implatform.config.WebrtcConfig;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.vo.SystemConfigVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: blue
* @date: 2024-06-10
* @version: 1.0
*/
@Tag(name = "系统相关")
@RestController
@RequestMapping("/system")
@RequiredArgsConstructor
public class SystemController {
private final WebrtcConfig webrtcConfig;
@GetMapping("/config")
@Operation(summary = "加载系统配置", description = "加载系统配置")
public Result<SystemConfigVO> loadConfig() {
return ResultUtils.success(new SystemConfigVO(webrtcConfig));
}
}
@@ -0,0 +1,65 @@
package com.bx.implatform.controller;
import com.bx.implatform.entity.User;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.UserService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.OnlineTerminalVO;
import com.bx.implatform.vo.UserVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "用户相关")
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/terminal/online")
@Operation(summary = "判断用户哪个终端在线", description = "返回在线的用户id的终端集合")
public Result<List<OnlineTerminalVO>> getOnlineTerminal(@NotNull @RequestParam("userIds") String userIds) {
return ResultUtils.success(userService.getOnlineTerminals(userIds));
}
@GetMapping("/self")
@Operation(summary = "获取当前用户信息", description = "获取当前用户信息")
public Result<UserVO> findSelfInfo() {
UserSession session = SessionContext.getSession();
User user = userService.getById(session.getUserId());
UserVO userVO = BeanUtils.copyProperties(user, UserVO.class);
return ResultUtils.success(userVO);
}
@GetMapping("/find/{id}")
@Operation(summary = "查找用户", description = "根据id查找用户")
public Result<UserVO> findById(@NotNull @PathVariable("id") Long id) {
return ResultUtils.success(userService.findUserById(id));
}
@PutMapping("/update")
@Operation(summary = "修改用户信息", description = "修改用户信息,仅允许修改登录用户信息")
public Result update(@Valid @RequestBody UserVO vo) {
userService.update(vo);
return ResultUtils.success();
}
@GetMapping("/findByName")
@Operation(summary = "查找用户", description = "根据用户名或昵称查找用户")
public Result<List<UserVO>> findByName(@RequestParam String name) {
return ResultUtils.success(userService.findUserByName(name));
}
}
@@ -0,0 +1,76 @@
package com.bx.implatform.controller;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.WebrtcPrivateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "单人通话")
@RestController
@RequestMapping("/webrtc/private")
@RequiredArgsConstructor
public class WebrtcPrivateController {
private final WebrtcPrivateService webrtcPrivateService;
@Operation(summary = "呼叫视频通话")
@PostMapping("/call")
public Result call(@RequestParam Long uid, @RequestParam(defaultValue = "video") String mode,
@RequestBody String offer) {
webrtcPrivateService.call(uid, mode, offer);
return ResultUtils.success();
}
@Operation(summary = "接受视频通话")
@PostMapping("/accept")
public Result accept(@RequestParam Long uid, @RequestBody String answer) {
webrtcPrivateService.accept(uid, answer);
return ResultUtils.success();
}
@Operation(summary = "拒绝视频通话")
@PostMapping("/reject")
public Result reject(@RequestParam Long uid) {
webrtcPrivateService.reject(uid);
return ResultUtils.success();
}
@Operation(summary = "取消呼叫")
@PostMapping("/cancel")
public Result cancel(@RequestParam Long uid) {
webrtcPrivateService.cancel(uid);
return ResultUtils.success();
}
@Operation(summary = "呼叫失败")
@PostMapping("/failed")
public Result failed(@RequestParam Long uid, @RequestParam String reason) {
webrtcPrivateService.failed(uid, reason);
return ResultUtils.success();
}
@Operation(summary = "挂断")
@PostMapping("/handup")
public Result handup(@RequestParam Long uid) {
webrtcPrivateService.handup(uid);
return ResultUtils.success();
}
@PostMapping("/candidate")
@Operation(summary = "同步candidate")
public Result candidate(@RequestParam Long uid, @RequestBody String candidate) {
webrtcPrivateService.candidate(uid, candidate);
return ResultUtils.success();
}
@Operation(summary = "心跳")
@PostMapping("/heartbeat")
public Result heartbeat(@RequestParam Long uid) {
webrtcPrivateService.heartbeat(uid);
return ResultUtils.success();
}
}
@@ -0,0 +1,23 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author Blue
* @version 1.0
*/
@Data
@Schema(description = "好友免打扰")
public class FriendDndDTO {
@NotNull(message = "好友id不可为空")
@Schema(description = "好友用户id")
private Long friendId;
@NotNull(message = "消息免打扰状态不可为空")
@Schema(description = "消息免打扰状态")
private Boolean isDnd;
}
@@ -0,0 +1,21 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-07-14
* @version: 1.0
*/
@Data
@Schema(description = "群组封禁")
public class GroupBanDTO {
@Schema(description = "群组id")
private Long id;
@Schema(description = "封禁原因")
private String reason;
}
@@ -0,0 +1,23 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author Blue
* @version 1.0
* @date 2025-02-23
*/
@Data
@Schema(description = "群聊免打扰")
public class GroupDndDTO {
@NotNull(message = "群id不可为空")
@Schema(description = "群组id")
private Long groupId;
@NotNull(message = "免打扰状态不可为空")
@Schema(description = "免打扰状态")
private Boolean isDnd;
}
@@ -0,0 +1,23 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Data
@Schema(description = "邀请好友进群请求DTO")
public class GroupInviteDTO {
@NotNull(message = "群id不可为空")
@Schema(description = "群id")
private Long groupId;
@Size(max = 50, message = "一次最多只能邀请50位用户")
@NotEmpty(message = "群id不可为空")
@Schema(description = "好友id列表不可为空")
private List<Long> friendIds;
}
@@ -0,0 +1,28 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
/**
* @author Blue
* @version 1.0
* @date 2025-02-23
*/
@Data
@Schema(description = "移除群聊成员")
public class GroupMemberRemoveDTO {
@NotNull(message = "群id不可为空")
@Schema(description = "群组id")
private Long groupId;
@Size(max = 50, message = "一次最多只能选择50位用户")
@NotEmpty(message = "成员用户id不可为空")
@Schema(description = "成员用户id")
private List<Long> userIds;
}
@@ -0,0 +1,38 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.util.List;
@Data
@Schema(description = "群聊消息DTO")
public class GroupMessageDTO {
@Schema(description = "临时id")
private String tmpId;
@NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id")
private Long groupId;
@Length(max = 1024, message = "发送内容长度不得大于1024")
@NotEmpty(message = "发送内容不可为空")
@Schema(description = "发送内容")
private String content;
@NotNull(message = "消息类型不可为空")
@Schema(description = "消息类型 0:文字 1:图片 2:文件 3:语音 4:视频")
private Integer type;
@Schema(description = "是否回执消息")
private Boolean receipt = false;
@Size(max = 20, message = "一次最多只能@20个小伙伴哦")
@Schema(description = "被@用户列表")
private List<Long> atUserIds;
}
@@ -0,0 +1,18 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-07-14
* @version: 1.0
*/
@Data
@Schema(description = "群组解锁")
public class GroupUnbanDTO {
@Schema(description = "群组id")
private Long id;
}
@@ -0,0 +1,28 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
@Schema(description = "用户登录DTO")
public class LoginDTO {
@Max(value = 2, message = "登录终端类型取值范围:0,2")
@Min(value = 0, message = "登录终端类型取值范围:0,2")
@NotNull(message = "登录终端类型不可为空")
@Schema(description = "登录终端 0:web 1:app 2:pc")
private Integer terminal;
@NotEmpty(message = "用户名不可为空")
@Schema(description = "用户名")
private String userName;
@NotEmpty(message = "用户密码不可为空")
@Schema(description = "用户密码")
private String password;
}
@@ -0,0 +1,19 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
@Data
@Schema(description = "修改密码DTO")
public class ModifyPwdDTO {
@NotEmpty(message = "旧用户密码不可为空")
@Schema(description = "旧用户密码")
private String oldPassword;
@NotEmpty(message = "新用户密码不可为空")
@Schema(description = "新用户密码")
private String newPassword;
}
@@ -0,0 +1,29 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@Data
@Schema(description = "私聊消息DTO")
public class PrivateMessageDTO {
@Schema(description = "临时id")
private String tmpId;
@NotNull(message = "接收用户id不可为空")
@Schema(description = "接收用户id")
private Long recvId;
@Length(max = 1024, message = "内容长度不得大于1024")
@NotEmpty(message = "发送内容不可为空")
@Schema(description = "发送内容")
private String content;
@NotNull(message = "消息类型不可为空")
@Schema(description = "消息类型 0:文字 1:图片 2:文件 3:语音 4:视频")
private Integer type;
}
@@ -0,0 +1,27 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@Data
@Schema(description = "用户注册DTO")
public class RegisterDTO {
@Length(max = 20, message = "用户名不能大于20字符")
@NotEmpty(message = "用户名不可为空")
@Schema(description = "用户名")
private String userName;
@Length(min = 5, max = 20, message = "密码长度必须在5-20个字符之间")
@NotEmpty(message = "用户密码不可为空")
@Schema(description = "用户密码")
private String password;
@Length(max = 20, message = "昵称不能大于20字符")
@Schema(description = "用户昵称")
private String nickName;
}
@@ -0,0 +1,21 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-07-14
* @version: 1.0
*/
@Data
@Schema(description = "用户锁定DTO")
public class UserBanDTO {
@Schema(description = "用户id")
private Long id;
@Schema(description = "锁定原因")
private String reason;
}
@@ -0,0 +1,29 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-06-01
* @version: 1.0
*/
@Data
@Schema(description = "回复用户连接请求DTO")
public class WebrtcGroupAnswerDTO {
@NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id")
private Long groupId;
@NotNull(message = "用户id不可为空")
@Schema(description = "用户id,代表回复谁的连接请求")
private Long userId;
@NotEmpty(message = "anwer不可为空")
@Schema(description = "用户本地anwer信息")
private String answer;
}
@@ -0,0 +1,29 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-06-01
* @version: 1.0
*/
@Data
@Schema(description = "发起群视频通话DTO")
public class WebrtcGroupCandidateDTO {
@NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id")
private Long groupId;
@NotNull(message = "用户id不可为空")
@Schema(description = "用户id")
private Long userId;
@NotEmpty(message = "candidate信息不可为空")
@Schema(description = "candidate信息")
private String candidate;
}
@@ -0,0 +1,26 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-06-01
* @version: 1.0
*/
@Data
@Schema(description = "用户设备操作DTO")
public class WebrtcGroupDeviceDTO {
@NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id")
private Long groupId;
@Schema(description = "是否开启摄像头")
private Boolean isCamera;
@Schema(description = "是否开启麦克风")
private Boolean isMicroPhone;
}
@@ -0,0 +1,23 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-06-01
* @version: 1.0
*/
@Data
@Schema(description = "用户通话失败DTO")
public class WebrtcGroupFailedDTO {
@NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id")
private Long groupId;
@Schema(description = "失败原因")
private String reason;
}
@@ -0,0 +1,28 @@
package com.bx.implatform.dto;
import com.bx.implatform.session.WebrtcUserInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* @author: Blue
* @date: 2024-06-01
* @version: 1.0
*/
@Data
@Schema(description = "邀请用户进入群视频通话DTO")
public class WebrtcGroupInviteDTO {
@NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id")
private Long groupId;
@NotEmpty(message = "参与用户信息不可为空")
@Schema(description = "参与用户信息")
private List<WebrtcUserInfo> userInfos;
}
@@ -0,0 +1,20 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-06-01
* @version: 1.0
*/
@Data
@Schema(description = "进入群视频通话DTO")
public class WebrtcGroupJoinDTO {
@NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id")
private Long groupId;
}
@@ -0,0 +1,29 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-06-01
* @version: 1.0
*/
@Data
@Schema(description = "回复用户连接请求DTO")
public class WebrtcGroupOfferDTO {
@NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id")
private Long groupId;
@NotNull(message = "用户id不可为空")
@Schema(description = "用户id,代表回复谁的连接请求")
private Long userId;
@NotEmpty(message = "offer不可为空")
@Schema(description = "用户offer信息")
private String offer;
}
@@ -0,0 +1,28 @@
package com.bx.implatform.dto;
import com.bx.implatform.session.WebrtcUserInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* @author: Blue
* @date: 2024-06-01
* @version: 1.0
*/
@Data
@Schema(description = "发起群视频通话DTO")
public class WebrtcGroupSetupDTO {
@NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id")
private Long groupId;
@NotEmpty(message = "参与用户信息不可为空")
@Schema(description = "参与用户信息")
private List<WebrtcUserInfo> userInfos;
}
@@ -0,0 +1,67 @@
package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* @author Blue
* @version 1.0
*/
@Data
@TableName("im_file_info")
public class FileInfo {
/**
* 文件ID
*/
@TableId
private Long id;
/**
* 文件名
*/
private String fileName;
/**
* 原始文件存储路径
*/
private String filePath;
/**
* 压缩文件存储路径
*/
private String compressedPath;
/**
* 封面文件路径
*/
private String coverPath;
/**
* 原始文件大小(字节)
*/
private Long fileSize;
/**
* 上传时间
*/
private Date uploadTime;
/**
* 文件类型,枚举: FileType
*/
private Integer fileType;
/**
* 是否永久存储
*/
private Boolean isPermanent;
/**
* 文件MD5哈希值
*/
private String md5;
}
@@ -0,0 +1,63 @@
package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* <p>
* 好友
* </p>
*
* @author blue
* @since 2022-10-22
*/
@Data
@TableName("im_friend")
public class Friend{
/**
* id
*/
@TableId
private Long id;
/**
* 用户id
*/
private Long userId;
/**
* 好友id
*/
private Long friendId;
/**
* 用户昵称
*/
private String friendNickName;
/**
* 用户头像
*/
private String friendHeadImage;
/**
* 是否开启免打扰
*/
private Boolean isDnd;
/**
* 是否已删除
*/
private Boolean deleted;
/**
* 创建时间
*/
private Date createdTime;
}
@@ -0,0 +1,72 @@
package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* 群
*
* @author blue
* @since 2022-10-31
*/
@Data
@TableName("im_group")
public class Group {
/**
* id
*/
@TableId
private Long id;
/**
* 群名字
*/
private String name;
/**
* 群主id
*/
private Long ownerId;
/**
* 群头像
*/
private String headImage;
/**
* 群头像缩略图
*/
private String headImageThumb;
/**
* 群公告
*/
private String notice;
/**
* 是否被封禁
*/
private Boolean isBanned;
/**
* 被封禁原因
*/
private String reason;
/**
* 创建时间
*/
private Date createdTime;
/**
* 是否已删除
*/
private Boolean dissolve;
}
@@ -0,0 +1,86 @@
package com.bx.implatform.entity;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* <p>
* 群成员
* </p>
*
* @author blue
* @since 2022-10-31
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("im_group_member")
public class GroupMember extends Model<GroupMember> {
/**
* id
*/
@TableId
private Long id;
/**
* 群id
*/
private Long groupId;
/**
* 用户id
*/
private Long userId;
/**
* 用户昵称
*/
private String userNickName;
/**
* 显示昵称备注
*/
private String remarkNickName;
/**
* 用户头像
*/
private String headImage;
/**
* 显示群名备注
*/
private String remarkGroupName;
/**
* 是否免打扰
*/
private Boolean isDnd;
/**
* 是否已退出
*/
private Boolean quit;
/**
* 创建时间
*/
private Date createdTime;
/**
* 退出时间
*/
private Date quitTime;
public String getShowNickName() {
return StrUtil.blankToDefault(remarkNickName, userNickName);
}
}
@@ -0,0 +1,88 @@
package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* <p>
* 群消息
* </p>
*
* @author blue
* @since 2022-10-31
*/
@Data
@TableName("im_group_message")
public class GroupMessage {
/**
* id
*/
@TableId
private Long id;
/**
* 临时id,由前端生成
* 作用:如果用户正在发送消息时掉线了,可以通过此字段获取该消息的实际发送状态
*/
private String tmpId;
/**
* 群id
*/
private Long groupId;
/**
* 发送用户id
*/
private Long sendId;
/**
* 发送用户昵称
*/
private String sendNickName;
/**
* 接受用户id,为空表示全体发送
*/
private String recvIds;
/**
* @用户列表
*/
private String atUserIds;
/**
* 发送内容
*/
private String content;
/**
* 消息类型 MessageType
*/
private Integer type;
/**
* 是否回执消息
*/
private Boolean receipt;
/**
* 回执消息是否完成
*/
private Boolean receiptOk;
/**
* 状态 MessageStatus
*/
private Integer status;
/**
* 发送时间
*/
private Date sendTime;
}
@@ -0,0 +1,63 @@
package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* <p>
*
* </p>
*
* @author blue
* @since 2022-10-01
*/
@Data
@TableName("im_private_message")
public class PrivateMessage {
/**
* id
*/
private Long id;
/**
* 临时id,由前端生成
* 作用:如果用户正在发送消息时掉线了,可以通过此字段获取该消息的实际发送状态
*/
private String tmpId;
/**
* 发送用户id
*/
private Long sendId;
/**
* 接收用户id
*/
private Long recvId;
/**
* 发送内容
*/
private String content;
/**
* 消息类型 MessageType
*/
private Integer type;
/**
* 状态
*/
private Integer status;
/**
* 发送时间
*/
private Date sendTime;
}
@@ -0,0 +1,50 @@
package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 敏感词
*
* @author Blue
* @since 1.0.0 2024-07-20
*/
@Data
@TableName("im_sensitive_word")
public class SensitiveWord {
/**
* id
*/
@TableId
private Long id;
/**
* 敏感词内容
*/
private String content;
/**
* 是否启用
*/
private Boolean enabled;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long creator;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
@@ -0,0 +1,87 @@
package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* <p>
* 用户
* </p>
*
* @author blue
* @since 2022-10-01
*/
@Data
@TableName("im_user")
public class User {
/**
* id
*/
@TableId
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户头像
*/
private String headImage;
/**
* 用户头像缩略图
*/
private String headImageThumb;
/**
* 密码(明文)
*/
private String password;
/**
* 性别 0:男 1::女
*/
private Integer sex;
/**
* 个性签名
*/
private String signature;
/**
* 账号是否被封禁
*/
private Boolean isBanned;
/**
* 账号被封禁原因
*/
private String reason;
/**
* 最后登录时间
*/
private Date lastLoginTime;
/**
* 创建时间(注册时间)
*/
private Date createdTime;
/**
* 账号类型 1:普通用户 2:wx小程序审核账户
*/
private Integer type;
}
@@ -0,0 +1,32 @@
package com.bx.implatform.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum FileType {
/**
* 文件
*/
FILE(0, "文件"),
/**
* 图片
*/
IMAGE(1, "图片"),
/**
* 视频
*/
VIDEO(2, "视频");
private final Integer code;
private final String desc;
public Integer code() {
return this.code;
}
}
Loaded 100 of 1273 files, more files were not shown because too many files have changed in this diff. Show more