init
This commit is contained in:
commit
5a041ecaf5
1273 files changed
+158507
No files matched your search
+14
@@ -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
|
||||
@@ -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.
|
||||
@@ -0,0 +1,145 @@
|
||||
|
||||
### **盒子IM**
|
||||

|
||||
[](https://gitee.com/bluexsx/box-im)
|
||||
[](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/ ,或扫码:
|
||||
|
||||

|
||||
|
||||
测试账号:张三/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时,消息是无法直接推送的,所以我们设计出了能够支持跨节点推送的方案:
|
||||

|
||||
|
||||
- 利用了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
|
||||
|
||||
|
||||
#### 界面截图
|
||||
私聊:
|
||||

|
||||
|
||||
群聊:
|
||||

|
||||
|
||||
群通话(商业版):
|
||||

|
||||
|
||||
好友列表:
|
||||

|
||||
|
||||
群列表:
|
||||

|
||||
|
||||
移动端APP:
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
#### 加入交流群
|
||||
群1: 741174521(已满)
|
||||
群2: 937470451(已满)
|
||||
群3: 1012017031(已满)
|
||||
群4: 765849133
|
||||
|
||||
欢迎进群与小伙们一起交流, **申请加群前请务必先star哦**
|
||||
|
||||
|
||||
#### 点下star吧
|
||||
如果项目对您有帮助,请点亮右上方的star,支持一下作者吧!
|
||||
|
||||
|
||||
#### 说明几点
|
||||
|
||||
1. 本系统允许用于商业用途,且不收费,**但切记不要用于任何非法用途** ,本软件作者不会为此承担任何责任
|
||||
1. 基于本系统二次开发后再次开源的项目,请注明引用出处,以避免引发不必要的误会
|
||||
1. 为方便管理,要pr的同学请将代码提交到v_3.0.0分支,作者会在功能上线时合并到master分支
|
||||
1. 允许商用不代表可以随意倒卖开源代码,尤其是代码、名字、截图一点都不改就拿去卖钱的,我劝你们善良一点!
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
com.bx.imclient.IMAutoConfiguration
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user