144 Commits

Author SHA1 Message Date
ageerle
37f832c31c Merge pull request #144 from MuSan-Li/feature_20250721_generate_code
替换代码生成
2025-07-28 09:32:19 +08:00
l90215
25cf1f9744 feat: 更新数据库密码... 2025-07-27 22:47:55 +08:00
l90215
a48178685c feat: 更新代码生成功能-优化逻辑 2025-07-27 22:37:26 +08:00
l90215
7e60cb357f feat: 更新代码生成功能-优化逻辑 2025-07-27 00:33:33 +08:00
l90215
915a393427 feat: 更新代码生成功能-优化取数逻辑 2025-07-24 12:36:26 +08:00
l90215
de323d8c45 feat: 更新代码生成功能-添加数据模型批量添加字段数据 2025-07-23 10:46:29 +08:00
l90215
ffe4867d40 feat: 更新代码生成功能-二阶段 2025-07-22 18:50:37 +08:00
l90215
8b3c0b4134 feat: 更新代码生成功能-一阶段 2025-07-21 16:03:08 +08:00
ageerle
63ec00cd71 Merge pull request #136 from Administratos-User/main
修复token不扣费和扣费异常的问题
2025-07-16 15:53:56 +08:00
ageerle
7eebd87cc8 Merge pull request #141 from lixiang-cell/lixiang
存储向量库同时存储元数据的fid和docId,提供根据fid docId删除
2025-07-16 15:52:19 +08:00
lixiang
285aa2ae62 存储向量库同时存储元数据的fid和docId,提供根据fid docId删除 2025-07-14 17:15:29 +08:00
Administratos-User
99114d3301 修复token不扣费和扣费异常的问题
1.本次提交的token数+未付费token数 判断大于100token的时候就进行扣费,当然这里还可以改成更多,我觉得100合适。
2.不需要进行扣费的地方屏蔽了相关代码。
3.SessionId传递异常 建议前端传递uuid,也就是每次会话的id。
2025-07-11 11:59:20 +08:00
ageerle
138fa5f0e9 Merge pull request #133 from MuSan-Li/feature_20250516_add_dept_error
feat: 修复打开岗位管理爆错
2025-07-11 09:49:55 +08:00
l90215
241c6dc57a feat: 修复打开岗位管理爆粗 2025-07-10 23:53:17 +08:00
ageerle
6005339ec8 upadate md 2025-07-10 23:00:04 +08:00
ageerle
db892d35fb feat: 更新项目说明 2025-07-10 17:12:20 +08:00
ageerle
e117ec8e27 feat: 更新项目说明 2025-07-09 14:27:55 +08:00
ageerle
5e8db861ea feat: 更新项目说明 2025-07-08 15:30:25 +08:00
ageerle
fa2791e2e3 feat: 更新项目说明 2025-07-08 14:31:01 +08:00
ageerle
f1f7cb1084 feat: 更新项目说明 2025-07-08 12:26:24 +08:00
ageerle
acb1f27c37 feat: 更新项目说明 2025-07-08 12:10:03 +08:00
ageerle
e46245d97d feat: 更新项目说明 2025-07-07 13:44:55 +08:00
ageerle
0eff37fa51 ai编程助手 2025-07-07 12:36:53 +08:00
ageerle
c1a178c0be Merge remote-tracking branch 'origin/main' 2025-07-07 12:33:21 +08:00
ageerle
94d4446321 feat: 编程助手 2025-07-07 12:33:06 +08:00
lindaxia
d7930ad713 修复导包问题 2025-07-06 08:12:38 +08:00
lindaxia
48270add01 Merge branch 'main' of https://github.com/ageerle/ruoyi-ai 2025-07-06 07:52:25 +08:00
lindaxia
3786644a25 新增小程序接口 2025-07-06 07:51:38 +08:00
ageerle
53532681d3 Merge pull request #131 from Cyclones-Y/main
[fix][feat](chat): 修复“自动注入警告 => 用户未登录”; 添加会话中SaToken-token值传递和使用;
2025-07-05 21:29:24 +08:00
Yzm
daa7b7315b [fix][feat](chat): 添加会话中SaToken-token值传递和使用;修复“自动注入警告 => 用户未登录”
- 新增 BaseContext 类,用于保存和获取当前登录用户的 token
- 在 OpenAIServiceImpl 和 imageServiceImpl 中获取当前会话 token
- 将 token 作为参数传递给 SSEEventSourceListener
- 在 SSEEventSourceListener 中使用 token 进行用户身份验证和权限控制
- 修改 LoginHelper,增加根据 token 获取登录用户信息的方法
- 更新 InjectionMetaObjectHandler,使用 BaseContext 获取当前 token
- 修复对话时出先”自动注入警告 => 用户未登录“
2025-07-05 19:12:33 +08:00
ageerle
df3b687be4 Merge pull request #129 from MuSan-Li/feature_20250703_add_model_category_select
feat: 模型管理增加模型分类下拉框
2025-07-03 10:48:43 +08:00
l90215
ecb5ef32fc feat: 模型管理增加模型分类下拉框 2025-07-03 10:29:25 +08:00
evo
e5116472ed Merge branch 'ageerle:main' into main 2025-07-03 09:50:59 +08:00
lindaxia
e58aeb5361 更新README.md 2025-07-02 11:04:35 +08:00
lindaxia
b4306289f0 fix weaviate向量库根据数据类进行删除 2025-07-01 18:50:12 +08:00
lindaxia
d9c47bd983 修复删除知识库清空相关表 2025-07-01 18:06:49 +08:00
evo
8ddbb43dde Merge branch 'ageerle:main' into main 2025-07-01 10:02:32 +08:00
ageerle
dfe8c7dc85 Merge pull request #127 from Cyclones-Y/main
feat(chat): 集成 FastGPT 聊天模型
2025-06-30 23:21:31 +08:00
Yzm
fd94a1772f feat(chat): 集成 FastGPT 聊天模型- 在 ChatModeType 枚举中添加 FASTGPT 选项- 新增 FastGPT 相关的实体类和请求响应类
- 实现 FastGPT聊天服务接口
- 添加 FastGPT SSE 事件监听器
2025-06-30 22:03:31 +08:00
ageerle
c105d47d99 Merge pull request #125 from MuSan-Li/feature_20250626_fix_update_role
修复修改角色时候报错
2025-06-27 10:45:32 +08:00
l90215
4e2ec2dc82 feat: 修复修改角色时候报错&优化一些代码风格 2025-06-26 10:19:08 +08:00
evo
614280d8ea Merge branch 'ageerle:main' into main 2025-06-25 12:57:49 +08:00
ageerle
2fae8d0ad0 feat: 更新任务规划演示 2025-06-24 14:21:43 +08:00
ageerle
d7c2d1bcf3 feat: 更新任务规划演示 2025-06-24 14:16:40 +08:00
ageerle
122f63dfbd Merge remote-tracking branch 'origin/main' 2025-06-24 13:52:01 +08:00
ageerle
719e968192 feat: 更新任务规划演示 2025-06-24 13:51:51 +08:00
evo
bf790ceb51 Merge branch 'ageerle:main' into main 2025-06-24 11:43:08 +08:00
ageerle
de5488bd8c Merge pull request #123 from abin0515/one-step-script
one-step-script: 修改MacOS上运行快速启动脚本遇到的bug
2025-06-24 10:30:49 +08:00
GH Action - Upstream Sync
c77a245a4d Merge branch 'main' of https://github.com/ageerle/ruoyi-ai 2025-06-24 01:57:42 +00:00
Bin Xiao
6dcd8823cd one-step-script: 修改MacOS上运行快速启动脚本遇到的bug 2025-06-23 18:21:01 -04:00
ageerle
8be480e06c Update README.md 2025-06-23 16:49:13 +08:00
ageerle
11286de676 Merge pull request #118 from MuSan-Li/feature_20250610_add_prompt_template
Feature 20250610 add prompt template
2025-06-23 16:36:40 +08:00
MuSan-Li
5aaf0a672c feat: 删除fork文件 2025-06-12 17:55:19 +08:00
MuSan-Li
0089706336 添加提示词模板字典相关SQL 2025-06-12 17:37:04 +08:00
MuSan-Li
cc129801b9 添加提示词模板 2025-06-12 17:06:06 +08:00
GH Action - Upstream Sync
e1dc22348c Merge branch 'main' of https://github.com/ageerle/ruoyi-ai 2025-06-07 01:53:26 +00:00
ageerle
f37e4da669 feat: 更新二维码 2025-06-06 10:13:02 +08:00
GH Action - Upstream Sync
3e097d9a68 Merge branch 'main' of https://github.com/ageerle/ruoyi-ai 2025-06-06 01:54:02 +00:00
ageerle
97ae5a46cd feat: 调整sql脚本 2025-06-05 16:16:32 +08:00
ageerle
baa664ac4f feat: 图片识别功能优化 2025-06-05 16:00:06 +08:00
ageerle
353fbf26b8 Merge pull request #115 from Code-Mr-Jiu/main
上传图片支持使用后台image分类下通义千问模型
2025-06-05 13:53:20 +08:00
ageerle
f79b4ec012 Merge pull request #114 from Code-Mr-Jiu/jiuyi-dev
上传图片支持使用后台image分类模型
2025-06-05 13:53:09 +08:00
酒亦
0a73cb4e17 上传图片支持使用后台image分类下通义千问模型 2025-06-05 12:05:28 +08:00
酒亦
d635e30b4a 上传图片支持使用后台image分类模型 2025-06-02 08:11:22 +08:00
evo
ca50d1ddfb Create main.yml 2025-05-30 16:05:04 +08:00
ageerle
bd94a3eae8 Merge remote-tracking branch 'origin/main' 2025-05-27 17:55:44 +08:00
ageerle
fcdcf534f1 fix: 修复脱敏后的apikey也被更新了 2025-05-27 17:55:34 +08:00
lindaxia
deefb6cc0b Merge branch 'main' of https://github.com/ageerle/ruoyi-ai 2025-05-26 22:45:18 +08:00
lindaxia
fa96c3d12f feat: 更新deploy脚本 2025-05-26 22:43:54 +08:00
ageerle
77069fdecd feat: 移除聊天、会话角色校验 2025-05-26 22:41:55 +08:00
ageerle
db4a264a52 Merge remote-tracking branch 'origin/main' 2025-05-26 22:40:17 +08:00
ageerle
70ae7ea8f1 feat: 向量库查询删除逻辑调整 2025-05-26 22:40:01 +08:00
lindaxia
6a93856d90 feat: 更新deploy脚本 2025-05-26 22:24:17 +08:00
lindaxia
71cae94815 Revert "lindaxia"
This reverts commit a2905d08f9.
2025-05-26 22:20:51 +08:00
lindaxia
a2905d08f9 lindaxia
feat: 更新deploy
2025-05-26 22:16:23 +08:00
ageerle
463ad6c583 Merge remote-tracking branch 'origin/main' 2025-05-26 12:56:45 +08:00
ageerle
abcde9e36e refactor: 1. 移除图片附件模块 2. 优化import 2025-05-26 12:56:28 +08:00
ageerle
8e723f5b78 Update README.md 2025-05-26 10:21:09 +08:00
ageerle
2021fb5071 feat: 修改项目介绍 2025-05-26 10:15:19 +08:00
ageerle
ecbab7df7b Update README.md 2025-05-26 10:10:00 +08:00
ageerle
d6fcaa0a11 Update README.md 2025-05-26 10:09:28 +08:00
ageerle
58faded4ff Update README.md 2025-05-26 10:09:02 +08:00
ageerle
7cc7af70f0 Update README.md 2025-05-26 10:07:47 +08:00
ageerle
4312a729a2 feat: 会话管理返回创建时间 2025-05-26 09:32:53 +08:00
ageer
5d972d66f4 fix: 代码优化 2025-05-25 14:42:22 +08:00
ageerle
1bff2791a6 Merge pull request #108 from liudalian/20250520
集成国产大模型 ds、zhipu、qianwen
2025-05-25 14:36:23 +08:00
菩提取一叶
a76769e540 add:集成国产大模型 ds、zhipu、qianwen 2025-05-25 14:30:05 +08:00
ageer
bedffffd86 fix: 修复对话时无法上传文件 2025-05-24 23:59:40 +08:00
ageer
2884c37599 feat: 移除微信支付 2025-05-24 23:07:59 +08:00
ageer
c25749c9ca feat: 更新sql脚本 2025-05-24 23:02:59 +08:00
ageer
147b3fd8f5 feat: 查询ppt apikey 2025-05-24 20:06:19 +08:00
ageerle
373424bd01 feat(更新日志):
更新日志

1. 移除个人微信模块
2. 移除直播模块
3. 移除gpts模块
4. 移除应用商店模块
5. 移除套餐管理模块
6. 移除兑换管理模块

## 微信相关
小程序相关功能迁移至企业版
微信公众号/微信机器人迁移至企业版
微信支付迁移至企业版

## 功能模块
智能体模块迁移至企业版
插件管理改为MCP应用并迁移至企业版

知识库:
excel解析迁移至企业版
pdf图片解析迁移至企业版
milvus qdrant扩展 迁移至企业版
2025-05-24 16:18:18 +08:00
ageerle
287a0b3d70 Update README.md 2025-05-23 10:28:42 +08:00
ageerle
cd7e07bea6 Update README.md 2025-05-23 10:26:48 +08:00
ageerle
7edfae09bc Update README.md 2025-05-23 10:24:32 +08:00
ageer
a1759ddf5a perf(wechat,live): 移除个人/企业微信,直播模块
1. 个人微信移除原因:功能不稳定,且有封号风险 2. 直播模块移除原因:使用频率低
2025-05-22 18:36:35 +08:00
ageerle
5517a5d19b feat: 新增聊天时返回消息id 2025-05-22 17:24:56 +08:00
ageerle
8fb72ba4fe feat: 修改配置 2025-05-22 17:19:00 +08:00
ageerle
24511fac8f refactor: 移除个人微信模块,移除直播模 2025-05-22 17:13:49 +08:00
ageerle
ce9f2a82da feat: 新增会话返回会话id 2025-05-22 17:11:39 +08:00
ageerle
b81fb7cb3f feat: 修改项目介绍 2025-05-22 11:37:45 +08:00
ageerle
676a63c0d6 feat: 修改项目介绍 2025-05-22 11:30:34 +08:00
ageerle
427736271c feat: 修改项目介绍 2025-05-22 11:28:32 +08:00
ageerle
c39dd7a917 feat: 修改项目介绍 2025-05-22 11:24:17 +08:00
ageerle
c491a98dc7 Merge pull request #95 from MuSan-Li/feature_20250520_fix_email_config
修复用户端注册发送邮件报错
2025-05-21 09:40:11 +08:00
MuSan-Li
2a4978c668 修复用户端注册发送邮件报错 2025-05-20 23:10:27 +08:00
lh
5b4eeee756 更新readme.md 2025-05-20 11:00:21 +08:00
ageerle
93ddcd53b1 feat: 对话时记录对话角色 2025-05-19 16:41:55 +08:00
ageerle
0ee47a5c00 feat: 对话时记录对话角色 2025-05-19 16:35:21 +08:00
ageerle
3ceef41ab0 feat: 知识库上传逻辑调整 2025-05-19 16:11:37 +08:00
ageerle
57693059a1 Merge pull request #91 from xingjisen/main
fix: 自动上传向量时间改为三分钟;修复根据条件删除向量数据报错问题;
2025-05-19 09:21:35 +08:00
邢继森
d18e307e10 fix: 自动上传向量时间改为三分钟;修复根据条件删除向量数据报错问题; 2025-05-19 00:45:32 +08:00
ageerle
eed708c6d3 Merge pull request #90 from alanpeng/main
Add one-step auto-deployment script
2025-05-18 22:11:01 +08:00
root
1093888889 fix the deploy-en.sh by dos2unix command 2025-05-18 15:58:34 +08:00
Alan Peng
e58a420f9f Update README.md 2025-05-18 21:48:36 +08:00
root
4a0c3d132a Update README.md 2025-05-18 15:48:47 +08:00
root
0d81aa640e Add one-step auto-deployment script 2025-05-18 15:48:20 +08:00
ageer
951524bff7 Merge remote-tracking branch 'origin/main' 2025-05-17 12:51:54 +08:00
ageer
c7d0e9337a fix: 修复聊天记录保存异常 2025-05-17 12:51:29 +08:00
ageerle
6955f3c7dc Merge pull request #87 from alanpeng/main
Add weaviate database service
2025-05-17 11:20:10 +08:00
root
a52b017e1a Add weaviate database service 2025-05-17 10:11:16 +08:00
ageer
158a0190b5 feat: 优化会话管理查询逻辑 2025-05-16 23:52:43 +08:00
ageerle
096fc11313 Merge pull request #86 from alanpeng/main
Containerized service package/imagese build and deploy
2025-05-16 22:36:21 +08:00
root
e9ac795d23 Add manual 2025-05-16 22:31:14 +08:00
root
32f1a6bab1 Containerized service package/imagese build and deploy 2025-05-16 22:29:36 +08:00
ageerle
052069a2df Merge pull request #85 from MuSan-Li/feature_20250516_fix_auth
修复未登录也可以访问接口
2025-05-16 22:11:41 +08:00
MuSan-Li
e1c997883f 修复未登录也可以访问接口 2025-05-16 21:01:37 +08:00
ageerle
031b7da198 Merge pull request #84 from janzhou123/main
feat:pdf文件解析图片和分析图片,上传向量数据库都修改成 成异步处理
2025-05-16 16:29:21 +08:00
zhouweiyi
931af963b1 feat:pdf文件解析图片和分析图片,上传向量数据库都修改成 成异步处理 2025-05-16 13:50:12 +08:00
Albert
f5fa7d5da8 Merge branch 'ageerle:main' into main 2025-05-15 17:49:09 +08:00
zhouweiyi
c6ffbcb3cf feat:pdf文件解析图片和分析图片,上传向量数据库都修改成 成异步处理 2025-05-15 17:46:38 +08:00
ageerle
05ae200ff5 Merge pull request #82 from chenwei1015/fix-20250514-knowledge_info
fix- 20250514.sql, alter单词错误
2025-05-14 17:42:21 +08:00
weizhangfz
e745f772ef fix- 20250514.sql, alter单词错误 2025-05-14 17:23:34 +08:00
zhouweiyi
584212c569 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java
2025-05-14 16:13:07 +08:00
zhouweiyi
dc9bf3e25d pdf文件解析成异步处理 2025-05-14 15:41:57 +08:00
ageerle
da84a26c47 feat: 更新sql脚本 2025-05-14 13:56:09 +08:00
ageerle
7d3282c347 feat: 修复知识库上传失败 2025-05-14 13:53:49 +08:00
ageerle
4454be44c3 Update README.md 2025-05-13 14:40:27 +08:00
zhouweiyi
52e0feda01 修改方法描述 2025-05-13 13:44:31 +08:00
ageerle
c89f5d07fb Merge pull request #80 from janzhou123/main
Extract the image from the PDF and call the large model to identify the image content and return it
2025-05-13 11:08:20 +08:00
zhouweiyi
778a7bc21b 提取PDF中的图片并调用大模型,识别图片内容并返回 2025-05-13 11:01:33 +08:00
zhouweiyi
50f5f38996 提取PDF中的图片并调用大模型,识别图片内容并返回 2025-05-13 10:56:05 +08:00
zhouweiyi
32da85daab 提取PDF中的图片并调用大模型,识别图片内容并返回 2025-05-13 10:55:39 +08:00
Albert
3666157d14 Merge pull request #1 from ageerle/main
new
2025-05-13 09:59:36 +08:00
995 changed files with 26650 additions and 173048 deletions

393
README.md
View File

@@ -1,10 +1,6 @@
# RuoYi AI
<!-- PROJECT SHIELDS -->
<div align="center">
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
@@ -12,246 +8,149 @@
[![Issues][issues-shield]][issues-url]
[![MIT License][license-shield]][license-url]
<img src="image/00.png" alt="RuoYi AI Logo" width="120" height="120">
<!-- PROJECT LOGO -->
<br />
### 企业级AI助手平台
*开箱即用的智能AI平台深度集成 FastGPT、扣子(Coze)、DIFY 等主流AI平台提供先进的RAG技术和多模型支持*
<img style="text-align: center;" src="image/00.png" alt="Logo" width="150" height="150">
**[🇺🇸 English](README_EN.md)** | **[📖 使用文档](https://doc.pandarobot.chat)** | **[🚀 在线体验](https://web.pandarobot.chat)** | **[🐛 问题反馈](https://github.com/ageerle/ruoyi-ai/issues)** | **[💡 功能建议](https://github.com/ageerle/ruoyi-ai/issues)**
<h3 style="text-align: center;">快速搭建属于自己的 AI 助手平台</h3>
<p style="text-align: center;">
全新升级,开箱即用,简单高效
<br />
<a href="https://doc.pandarobot.chat"><strong>探索本项目的文档 »</strong></a>
<br />
<br />
<a href="https://web.pandarobot.chat">项目预览</a>
·
<a href="https://github.com/ageerle/ruoyi-ai/issues">报告Bug</a>
·
<a href="https://github.com/ageerle/ruoyi-ai/issues">提出新特性</a>
</p>
## 目录
- [系统体验](#系统体验)
- [源码地址](#源码地址)
- [配套文档](#项目文档)
- [核心功能](#核心功能)
- [项目演示](#项目演示)
- [管理端](#管理端)
- [用户端](#用户端)
- [小程序端](#小程序端)
- [开发环境](#开发环境)
- [项目结构](#项目结构)
- [ruoyi-ai](#ruoyi-ai)
- [注意事项](#注意事项)
- [vben模板](#vben模板)
- [贡献者](#贡献者)
- [如何参与开源项目](#如何参与开源项目)
- [版本控制](#版本控制)
- [作者](#作者)
- [鸣谢](#鸣谢)
- [技术讨论群](#技术讨论群)
### 系统体验
- 用户端https://web.pandarobot.chat
- 管理端https://admin.pandarobot.chat
- 用户名: admin 密码admin123
### 源码地址
[1]github
- 前端服务-用户端: https://github.com/ageerle/ruoyi-web
- 前端服务-管理端: https://github.com/ageerle/ruoyi-admin
- 前端服务-小程序端: https://github.com/ageerle/ruoyi-uniapp
- 后端服务https://github.com/ageerle/ruoyi-ai
[2]gitcode
- 前端服务-用户端https://gitcode.com/ageerle/ruoyi-web
- 前端服务-管理端: https://gitcode.com/ageerle/ruoyi-admin
- 前端服务-小程序端: https://gitcode.com/ageerle/ruoyi-uniapp
- 后端服务https://gitcode.com/ageerle/ruoyi-ai
### 配套文档
- 配套文档: https://doc.pandarobot.chat
- 项目部署文档https://doc.pandarobot.chat/guide/introduction/
### 核心功能与技术亮点
#### 1. 全栈式开源系统
- 全套开源系统提供完整的前端应用、后台管理以及小程序应用基于MIT协议开箱即用。
#### 2. 本地化 RAG 方案
- 基于 **Langchain4j** 框架,支持 Milvus/Weaviate/Qdrant 向量库,结合 BGE-large-zh-v1.5 本地向量化模型 实现高效文档检索与知识库构建。
- 支持 本地 LLM 接入,结合私有知识库实现安全可控的问答系统,避免依赖云端服务的隐私风险。
#### 3. 多模态 AI 引擎与工具集成
- 智能对话:支持 OpenAI GPT-4、Azure、ChatGLM 等主流模型,内置 SSE/WebSocket 协议实现低延迟交互,兼容 **扣子**、**DIFY** 等平台 API 调用。
- **Spring AI MCP** 支持:通过注解快速定义本地工具,支持调用 MCP 广场 的海量 MCP Server 服务,扩展模型能力边界。
#### 4. 企业级扩展与商业化支持
- 即时通讯集成:支持对接个人微信、企业微信及微信公众号,实现消息自动回复、用户管理与智能客服。
- 支付系统集成易支付、微信支付、Stripe 国际信用卡支付,满足商业化场景需求。
#### 5. 多媒体处理与创新功能
- AI 绘画:集成 DALL·E-3、MidJourney、Stable Diffusion支持文生图、图生图及风格化创作适用于营销素材生成与创意设计。
- PPT 制作:根据文本输入自动生成结构化幻灯片,支持自定义模板(需要使用三方平台 如:文多多)。
### 项目演示
#### mcp支持
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/mcp-01.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/mcp-02.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/mcp-03.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/mcp-04.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
#### 管理端
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/02.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/03.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/04.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/05.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
## ✨ 核心亮点
### 🤖 智能AI引擎
- **多模型接入**:支持 OpenAI GPT-4、Azure、ChatGLM、通义千问、智谱AI 等主流模型
- **AI平台集成**:深度集成 **FastGPT**、**扣子(Coze)**、**DIFY** 等主流AI应用平台
- **Spring AI MCP 集成**基于模型上下文协议打造可扩展的AI工具生态系统
- **实时流式对话**:采用 SSE/WebSocket 技术,提供丝滑的对话体验
- **AI 编程助手**:内置智能代码分析和项目脚手架生成能力
### 🌟 AI平台生态集成
- **FastGPT 深度集成**:原生支持 FastGPT API包括知识库检索、工作流编排和上下文管理
- **扣子(Coze) 官方SDK**集成字节跳动扣子平台官方SDK支持Bot对话和流式响应
- **DIFY 完整兼容**:使用 DIFY Java Client支持应用编排、工作流和知识库管理
- **统一聊天接口**:提供统一的聊天服务接口,支持多平台无缝切换和负载均衡
### 🧠 本地化RAG方案
- **私有知识库**:基于 Langchain4j 框架 + BGE-large-zh-v1.5 中文向量模型
- **多种向量库**:支持 Milvus、Weaviate、Qdrant 等主流向量数据库
- **数据安全可控**:支持完全本地部署,保护企业数据隐私
- **灵活模型部署**:兼容 Ollama、vLLM 等本地推理框架
### 🎨 AI创作工具
- **AI 绘画创作**:深度集成 DALL·E-3、MidJourney、Stable Diffusion
- **智能PPT生成**:一键将文本内容转换为精美演示文稿
- **多模态理解**:支持文本、图片、文档等多种格式的智能处理
## 🚀 快速体验
### 在线演示
- **用户端体验**[web.pandarobot.chat](https://web.pandarobot.chat) (账号demo 密码demo123)
- **管理后台**[admin.pandarobot.chat](https://admin.pandarobot.chat) (账号admin 密码admin123)
### 项目源码
| 项目模块 | GitHub 仓库 | Gitee 仓库 | GitCode 仓库 |
|---------|------------|-----------|-------------|
| 🔧 后端服务 | [ruoyi-ai](https://github.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitee.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitcode.com/ageerle/ruoyi-ai) |
| 🎨 用户前端 | [ruoyi-web](https://github.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitee.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitcode.com/ageerle/ruoyi-web) |
| 🛠️ 管理后台 | [ruoyi-admin](https://github.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitee.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitcode.com/ageerle/ruoyi-admin) |
### 合作项目
| 项目介绍 | GitHub 仓库 | Gitee 仓库 |
|:--------:|:----------:|:----------:|
| 前端简化版 | [ruoyi-element-ai](https://github.com/element-plus-x/ruoyi-element-ai) | [ruoyi-element-ai](https://gitee.com/he-jiayue/ruoyi-element-ai) |
## 🛠️ 技术架构
### 🏗️ 核心框架
- **后端架构**Spring Boot 3.4 + Spring AI + Langchain4j
- **数据存储**MySQL 8.0 + Redis + 向量数据库Milvus/Weaviate/Qdrant
- **前端技术**Vue 3 + Vben Admin + Naive UI
- **安全认证**Sa-Token + JWT 双重保障
### 🔧 系统组件
- **文档处理**PDF、Word、Excel 解析,图像智能分析
- **实时通信**WebSocket 实时通信SSE 流式响应
- **系统监控**:完善的日志体系、性能监控、服务健康检查
## 📚 使用文档
想要深入了解安装部署、功能配置和二次开发?
**👉 [完整使用文档](https://doc.pandarobot.chat)**
## 🤝 参与贡献
我们热烈欢迎社区贡献!无论您是资深开发者还是初学者,都可以为项目贡献力量 💪
### 贡献方式
1. **Fork** 项目到您的账户
2. **创建分支** (`git checkout -b feature/新功能名称`)
3. **提交代码** (`git commit -m '添加某某功能'`)
4. **推送分支** (`git push origin feature/新功能名称`)
5. **发起 Pull Request**
> 💡 **小贴士**:建议将 PR 提交到 GitHub我们会自动同步到其他代码托管平台
## 📄 开源协议
本项目采用 **MIT 开源协议**,详情请查看 [LICENSE](LICENSE) 文件。
## 🙏 特别鸣谢
感谢以下优秀的开源项目为本项目提供支持:
- [Spring AI Alibaba Copilot](https://github.com/springaialibaba/spring-ai-alibaba-copilot) - 基于spring-ai-alibaba 的智能编码助手
- [Spring AI](https://spring.io/projects/spring-ai) - Spring 官方 AI 集成框架
- [Langchain4j](https://github.com/langchain4j/langchain4j) - 强大的 Java LLM 开发框架
- [RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus) - 成熟的企业级快速开发框架
- [Vben Admin](https://github.com/vbenjs/vue-vben-admin) - 现代化的 Vue 后台管理模板
- [chatgpt-java](https://github.com/Grt1228/chatgpt-java) - 优秀的 ChatGPT Java SDK
## 🌐 生态伙伴
- [PPIO 派欧云](https://ppinfra.com/user/register?invited_by=P8QTUY&utm_source=github_ruoyi-ai) - 提供高性价比的 GPU 算力和模型 API 服务
- [优云智算](https://www.compshare.cn/?ytag=GPU_YY-gh_ruoyi) - 万卡RTX40系GPU+海内外主流模型API服务秒级响应按量计费新客免费用。
## 💬 社区交流
<div align="center">
<table>
<tr>
<td align="center">
<img src="image/wx.png" alt="微信二维码" width="200" height="200"><br>
<strong>扫码添加作者微信</strong><br>
<em>邀请进群学习</em>
</td>
<td align="center">
<img src="image/qq.png" alt="QQ群二维码" width="200" height="200"><br>
<strong>QQ技术交流群</strong><br>
<em>技术讨论</em>
</td>
</tr>
</table>
</div>
#### 用户端
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/08.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/09.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/10.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/11.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
---
<div align="center">
**[⭐ 点个Star支持一下](https://github.com/ageerle/ruoyi-ai)** • **[🍴 Fork 开始贡献](https://github.com/ageerle/ruoyi-ai/fork)** • **[📚 English](README_EN.md)** • **[📖 查看完整文档](https://doc.pandarobot.chat)**
*用 ❤️ 打造,由 RuoYi AI 开源社区维护*
</div>
#### 小程序端
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: flex-start;">
<img src="image/06.png" alt="drawing" style="width: 320px; height: 600px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/07.png" alt="drawing" style="width: 320px; height: 600px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
### 开发环境
1. jdk 17
2. mysql 5.7、8.0
3. redis 版本必须 >= 5.X
4. maven 3.8+
5. nodejs 20+ & pnpm
- 附-部署配套视频https://www.bilibili.com/video/BV1jDXkYWEba
<div>
<img src="image/教程搭建.png" alt="drawing" width="600px" height="300px"/>
</div>
### 项目结构
- RuoYi-AI
```
├─ ruoyi-admin // 管理模块
│ └─ RuoYiApplication // 启动类
│ └─ RuoYiServletInitializer // 容器部署初始化类
│ └─ resources // 资源文件
│ └─ i18n/messages.properties // 国际化配置文件
│ └─ application.yml // 框架总配置文件
│ └─ application-dev.yml // 开发环境配置文件
│ └─ application-prod.yml // 生产环境配置文件
│ └─ banner.txt // 框架启动图标
│ └─ logback-plus.xml // 日志配置文件
│ └─ ip2region.xdb // IP区域地址库
├─ ruoyi-common // 通用模块
│ └─ ruoyi-common-bom // common依赖包管理
└─ ruoyi-common-chat // 聊天模块
│ └─ ruoyi-common-core // 核心模块
│ └─ ruoyi-common-doc // 系统接口模块
│ └─ ruoyi-common-encrypt // 数据加解密模块
│ └─ ruoyi-common-excel // excel模块
│ └─ ruoyi-common-idempotent // 幂等功能模块
│ └─ ruoyi-common-json // 序列化模块
│ └─ ruoyi-common-log // 日志模块
│ └─ ruoyi-common-mail // 邮件模块
│ └─ ruoyi-common-mybatis // 数据库模块
│ └─ ruoyi-common-oss // oss服务模块
│ └─ ruoyi-common-pay // 支付模块
│ └─ ruoyi-common-ratelimiter // 限流功能模块
│ └─ ruoyi-common-redis // 缓存服务模块
│ └─ ruoyi-common-satoken // satoken模块
│ └─ ruoyi-common-security // 安全模块
│ └─ ruoyi-common-sensitive // 脱敏模块
│ └─ ruoyi-common-sms // 短信模块
│ └─ ruoyi-common-tenant // 租户模块
│ └─ ruoyi-common-translation // 通用翻译模块
│ └─ ruoyi-common-web // web模块
├─ ruoyi-modules // 模块组
│ └─ ruoyi-demo // 演示模块
│ └─ ruoyi-system // 业务模块
├─ .run // 执行脚本文件
├─ .editorconfig // 编辑器编码格式配置
├─ LICENSE // 开源协议
├─ pom.xml // 公共依赖
├─ README.md // 框架说明文件
```
### 注意事项
- vben模板
Qvben5 的模板默认是没有的吗?
Avben模板是收费的 请联系vben-vue-plus作者获取。
### 版本控制
该项目使用Git进行版本管理。您可以在repository参看当前可用版本。
### 版权说明
该项目使用了MIT授权许可详情请参阅 [LICENSE.txt](https://github.com/ageerle/ruoyi-ai/blob/main/LICENSE)
### 项目现状
目前项目还处于早期阶段距离成熟还有很长的路要走。由于个人精力有限项目的发展速度受到了一定的限制。为了加快项目的进度我真诚地希望更多人能够参与到项目中来。无论是经验丰富的开发者还是刚刚入门的小白我都热烈欢迎你们提交Pull RequestPR👏👏👏。即使代码修改得很少或者存在一些错误都没有关系。我会认真审核每一位贡献者的代码并和大家一起完善项目⛽
### 开发计划
| 主题 | 方向 | 时间节点 |
| --- |-----------------------------------|--------|
| 前端简化版 | 与element-plus-x框架合作推出基于该框架的前端简化版 | 2025.5 |
| agent2agent | Agent2Agent协议支持 | 2025.6 |
| 流程编排 | 通过可视化界面和灵活的配置方式快速构建AI应用 | 2025.7 |
- 感谢
最后我要感谢RuoYi-Vue-Plus、chatgpt-java、chatgpt-web-midjourney-proxy等优秀框架。正是因为这些项目的开源和共享我才能够在这个基础上进行开发使我们的项目能够取得今天的成果。再次感谢这些项目及其背后的开发者们
希望更多志同道合的朋友能够加入我们共同推动这个项目的发展。让我们一起努力将这个项目打造成一个真正成熟、实用的AI平台
#### 如何参与开源项目
贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献,我们都非常感谢!🙏
1. Fork 这个项目
2. 创建你的功能分支 (`git checkout -b feature/dev`)
3. 提交你的更改 (`git commit -m 'Add some dev'`)
4. 推送到分支 (`git push origin feature/dev`)
5. 打开拉取请求
6. pr请提交到GitHub上会定时同步到gitee
#### 项目文档
1. 项目文档基于vitepress构建
2. 按照[如何参与开源项目](#如何参与开源项目)拉取https://github.com/ageerle/ruoyi-doc
3. 安装依赖npm install
4. 启动项目npm run docs:dev
5. 主页路径docs/guide/introduction/index.md
### 鸣谢
- [chatgpt-java](https://github.com/Grt1228/chatgpt-java)
- [RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus)
- [chatgpt-web-midjourney-proxy](https://github.com/Dooy/chatgpt-web-midjourney-proxy)
- [Vben Admin](https://github.com/vbenjs/vue-vben-admin)
- [Naive UI](https://www.naiveui.com)
<!-- links -->
[your-project-path]:https://github.com/ageerle/ruoyi-ai
<!-- Badge Links -->
[contributors-shield]: https://img.shields.io/github/contributors/ageerle/ruoyi-ai.svg?style=flat-square
[contributors-url]: https://github.com/ageerle/ruoyi-ai/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/ageerle/ruoyi-ai.svg?style=flat-square
@@ -259,26 +158,6 @@
[stars-shield]: https://img.shields.io/github/stars/ageerle/ruoyi-ai.svg?style=flat-square
[stars-url]: https://github.com/ageerle/ruoyi-ai/stargazers
[issues-shield]: https://img.shields.io/github/issues/ageerle/ruoyi-ai.svg?style=flat-square
[issues-url]: https://img.shields.io/github/issues/ageerle/ruoyi-ai.svg
[issues-url]: https://github.com/ageerle/ruoyi-ai/issues
[license-shield]: https://img.shields.io/github/license/ageerle/ruoyi-ai.svg?style=flat-square
[license-url]: https://github.com/ageerle/ruoyi-ai/blob/master/LICENSE.txt
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
## 🌿 第三方生态
- [PPIO 派欧云:一键调用高性价比的开源模型 API 和 GPU 容器](https://ppinfra.com/user/register?invited_by=P8QTUY&utm_source=github_ruoyi-ai)
### 附:技术讨论群
#### 进群学习
🏠 wxruoyi-ai加人备注ruoyi-ai
<img src="image/小助手wx.png" alt="drawing" style="width: 400px; height: 400px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
🏠 qq1603234088 加人备注ruoyi-ai
👏👏👏 ruoyi-ai官方交流1群qq区1034554687
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/QQ区-官方交流1群.png" alt="drawing" style="width: 400px; height: 400px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
[license-url]: https://github.com/ageerle/ruoyi-ai/blob/main/LICENSE

167
README_EN.md Normal file
View File

@@ -0,0 +1,167 @@
# RuoYi AI
<div align="center">
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
[![Issues][issues-shield]][issues-url]
[![MIT License][license-shield]][license-url]
<img src="image/00.png" alt="RuoYi AI Logo" width="120" height="120">
### Enterprise-Grade AI Assistant Platform
*Production-ready AI platform with deep integration of FastGPT, Coze, DIFY and advanced RAG technology*
**[📖 中文文档](README.md)** | **[📚 Documentation](https://doc.pandarobot.chat)** | **[🚀 Live Demo](https://web.pandarobot.chat)** | **[🐛 Report Bug](https://github.com/ageerle/ruoyi-ai/issues)** | **[💡 Request Feature](https://github.com/ageerle/ruoyi-ai/issues)**
</div>
## ✨ Key Features
### 🤖 Advanced AI Engine
- **Multi-Model Support**: OpenAI GPT-4, Azure, ChatGLM, Qwen, ZhipuAI
- **AI Platform Integration**: Deep integration with **FastGPT**, **Coze**, **DIFY** and other leading AI platforms
- **Spring AI MCP Integration**: Extensible tool ecosystem with Model Context Protocol
- **Streaming Chat**: Real-time SSE/WebSocket communication
- **AI Copilot**: Intelligent code analysis and project scaffolding
### 🌟 AI Platform Ecosystem
- **FastGPT Deep Integration**: Native FastGPT API support with knowledge base retrieval, workflow orchestration and context management
- **Coze Official SDK**: Integration with ByteDance Coze platform official SDK, supporting Bot conversations and streaming responses
- **DIFY Full Compatibility**: Using DIFY Java Client for app orchestration, workflows and knowledge base management
- **Unified Chat Interface**: Standardized chat service interface supporting seamless platform switching and load balancing
### 🧠 Enterprise RAG Solution
- **Local Knowledge Base**: Langchain4j + BGE-large-zh-v1.5 embeddings
- **Vector Database Support**: Milvus, Weaviate, Qdrant
- **Privacy-First**: On-premise deployment with local LLM support
- **Ollama & vLLM Compatible**: Flexible model deployment options
### 🎨 Creative AI Tools
- **AI Art Generation**: DALL·E-3, MidJourney, Stable Diffusion integration
- **PPT Creation**: Automated slide generation from text input
- **Multi-Modal Processing**: Text, image, and document understanding
## 🚀 Quick Start
### Live Demo
- **User Portal**: [web.pandarobot.chat](https://web.pandarobot.chat) (demo/demo123)
- **Admin Panel**: [admin.pandarobot.chat](https://admin.pandarobot.chat) (admin/admin123)
### Source Code
| Component | GitHub | Gitee | GitCode |
|-----------|--------|-------|---------|
| Backend API | [ruoyi-ai](https://github.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitee.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitcode.com/ageerle/ruoyi-ai) |
| User Frontend | [ruoyi-web](https://github.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitee.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitcode.com/ageerle/ruoyi-web) |
| Admin Frontend | [ruoyi-admin](https://github.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitee.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitcode.com/ageerle/ruoyi-admin) |
### Collaborative Projects
| Project Description | GitHub Repository | Gitee Repository |
|:-------------------:|:-----------------:|:----------------:|
| Simplified Frontend | [ruoyi-element-ai](https://github.com/element-plus-x/ruoyi-element-ai) | [ruoyi-element-ai](https://gitee.com/he-jiayue/ruoyi-element-ai) |
## 🛠️ Tech Stack
### Core Framework
- **Backend**: Spring Boot 3.4, Spring AI, Langchain4j
- **Database**: MySQL 8.0, Redis, Vector Databases (Milvus/Weaviate/Qdrant)
- **Frontend**: Vue 3, Vben Admin, Naive UI
- **Authentication**: Sa-Token, JWT
### System Components
- **File Processing**: PDF, Word, Excel parsing, intelligent image analysis
- **Real-time Communication**: WebSocket real-time communication, SSE streaming
- **System Monitoring**: Comprehensive logging, performance monitoring, health checks
## 📚 Documentation
For detailed setup, configuration, and development guides, visit our comprehensive documentation:
**[📖 Official Documentation](https://doc.pandarobot.chat)**
## 🤝 Contributing
We welcome contributions from developers of all skill levels! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated.
### How to Contribute
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
*Please submit PRs to GitHub - they will be synchronized to other platforms automatically.*
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 🙏 Acknowledgments
Special thanks to these amazing open source projects:
- [Spring AI Alibaba Copilot](https://github.com/springaialibaba/spring-ai-alibaba-copilot) - Intelligent coding assistant based on spring-ai-alibaba with MCP protocol integration for project analysis and code generation
- [Spring AI](https://spring.io/projects/spring-ai) - Spring's AI integration framework
- [Langchain4j](https://github.com/langchain4j/langchain4j) - Java LLM framework
- [RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus) - Enterprise development framework
- [Vben Admin](https://github.com/vbenjs/vue-vben-admin) - Vue admin template
- [chatgpt-java](https://github.com/Grt1228/chatgpt-java) - ChatGPT Java SDK
## 🌐 Ecosystem Partners
- [PPIO Cloud](https://ppinfra.com/user/register?invited_by=P8QTUY&utm_source=github_ruoyi-ai) - Cost-effective GPU containers and model APIs
## 💬 Community
<div align="center">
<table>
<tr>
<td align="center">
<img src="image/wx.png" alt="WeChat" width="200" height="200"><br>
<strong>Add Author WeChat</strong><br>
<em>Scan to join learning group</em>
</td>
<td align="center">
<img src="image/qq.png" alt="QQ Group" width="200" height="200"><br>
<strong>QQ Group</strong><br>
<em>Technical discussion</em>
</td>
</tr>
</table>
</div>
---
<div align="center">
**[⭐ Star this repo](https://github.com/ageerle/ruoyi-ai)** • **[🍴 Fork it](https://github.com/ageerle/ruoyi-ai/fork)** • **[📖 中文文档](README.md)** • **[📚 Documentation](https://doc.pandarobot.chat)**
*Built with ❤️ by the RuoYi AI community*
</div>
<!-- Badge Links -->
[contributors-shield]: https://img.shields.io/github/contributors/ageerle/ruoyi-ai.svg?style=flat-square
[contributors-url]: https://github.com/ageerle/ruoyi-ai/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/ageerle/ruoyi-ai.svg?style=flat-square
[forks-url]: https://github.com/ageerle/ruoyi-ai/network/members
[stars-shield]: https://img.shields.io/github/stars/ageerle/ruoyi-ai.svg?style=flat-square
[stars-url]: https://github.com/ageerle/ruoyi-ai/stargazers
[issues-shield]: https://img.shields.io/github/issues/ageerle/ruoyi-ai.svg?style=flat-square
[issues-url]: https://github.com/ageerle/ruoyi-ai/issues
[license-shield]: https://img.shields.io/github/license/ageerle/ruoyi-ai.svg?style=flat-square
[license-url]: https://github.com/ageerle/ruoyi-ai/blob/main/LICENSE

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 KiB

BIN
image/qq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

BIN
image/wx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 KiB

11
pom.xml
View File

@@ -54,10 +54,6 @@
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
<maven-surefire-plugin.version>3.0.0</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<weixin-java-miniapp.version>4.5.0</weixin-java-miniapp.version>
<weixin-java-pay.version>4.6.0</weixin-java-pay.version>
<weixin-java-cp.version>4.6.0</weixin-java-cp.version>
<weixin-java-cp.version>4.6.0</weixin-java-cp.version>
</properties>
<profiles>
@@ -336,13 +332,6 @@
<artifactId>ruoyi-generator</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-wechat</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -57,11 +57,6 @@
<artifactId>ruoyi-generator</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-wechat</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -3,6 +3,8 @@ package org.ruoyi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动程序
@@ -10,6 +12,8 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt
* @author Lion Li
*/
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class RuoYiAIApplication {
public static void main(String[] args) {

View File

@@ -2,21 +2,16 @@ package org.ruoyi.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.collection.CollUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.constant.Constants;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.domain.model.EmailLoginBody;
import org.ruoyi.common.core.domain.model.LoginBody;
import org.ruoyi.common.core.domain.model.RegisterBody;
import org.ruoyi.common.core.domain.model.SmsLoginBody;
import org.ruoyi.common.core.domain.model.VisitorLoginBody;
import org.ruoyi.common.core.domain.model.*;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StreamUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.tenant.helper.TenantHelper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.ruoyi.system.domain.bo.SysTenantBo;
import org.ruoyi.system.domain.vo.LoginTenantVo;
import org.ruoyi.system.domain.vo.LoginVo;

View File

@@ -5,10 +5,13 @@ import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.constant.Constants;
import org.ruoyi.common.core.constant.GlobalConstants;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.common.core.utils.StringUtils;
@@ -22,10 +25,6 @@ import org.ruoyi.common.web.config.properties.CaptchaProperties;
import org.ruoyi.common.web.enums.CaptchaType;
import org.ruoyi.system.domain.request.EmailRequest;
import org.ruoyi.system.domain.vo.CaptchaVo;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -36,7 +35,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
/**
* 验证码操作处理
@@ -67,7 +67,7 @@ public class CaptchaController {
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
// 验证码模板id
String templateId = "";
Map<String, String> map = new HashMap<>(1);
map.put("code", code);
@@ -90,18 +90,6 @@ public class CaptchaController {
String key = GlobalConstants.CAPTCHA_CODE_KEY + emailRequest.getUsername();
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 检验邮箱后缀
String suffix = configService.getConfigValue("mail", "suffix");
String prompt = configService.getConfigValue("mail", "prompt");
if(StringUtils.isNotEmpty(suffix)){
// 动态的域名列表
String[] invalidDomains = suffix.split(",");
for (String domain : invalidDomains) {
if (emailRequest.getUsername().endsWith(domain)) {
throw new ServiceException(prompt);
}
}
}
// 自定义邮箱模板
String model = configService.getConfigValue("mail", "mailModel");
String mailTitle = configService.getConfigValue("mail", "mailTitle");

View File

@@ -1,12 +1,3 @@
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: ruoyi
password: 123456
--- # 数据源配置
spring:
@@ -25,9 +16,9 @@ spring:
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: ry-vue
password: xx
url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: ruoyi-ai
password: ruoyi-ai
hikari:
# 最大连接池数量
@@ -94,3 +85,4 @@ sms:
# 腾讯专用
sdkAppId:

View File

@@ -49,7 +49,7 @@ server:
# 日志配置
logging:
level:
org.dromara: @logging.level@
org.ruoyi: @logging.level@
org.springframework: warn
org.mybatis.spring.mapper: error
org.apache.fury: warn
@@ -120,6 +120,8 @@ sa-token:
security:
# 排除路径
excludes:
# 获取模型信息
- /system/model/modelList
# 支付回调
- /pay/returnUrl
- /pay/notifyUrl
@@ -128,7 +130,12 @@ security:
# 重置密码
- /auth/reset/password
# 聊天接口
- /chat
- /chat/send
# 文件上传
- /chat/upload
# 代码生成调用
- /tool/gen/getByTableName
- /tool/gen/batchGenCode
# 静态资源
- /*.html
- /**/*.html
@@ -181,7 +188,7 @@ mybatis-plus:
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
logImpl: org.apache.ibatis.logging.slf4j.Slf4jImpl
global-config:
# 是否打印 Logo banner
banner: true
@@ -291,26 +298,6 @@ websocket:
# 设置访问源地址
allowedOrigins: '*'
# 微信小程序配置信息
wx:
miniapp:
configs:
- appid: # 你的appid
secret: # 你的secret
token: #微信小程序消息服务器配置的token
aesKey: #微信小程序消息服务器配置的EncodingAESKey
msgDataFormat: JSON
# 企业微信应用
wechat:
cp:
corpId:
appConfigs:
- agentId:
secret: ''
token: ''
aesKey: ''
spring:
ai:
openai:

View File

@@ -39,9 +39,7 @@
<module>ruoyi-common-encrypt</module>
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-chat</module>
<module>ruoyi-common-wechat</module>
<module>ruoyi-common-pay</module>
<module>ruoyi-common-live</module>
</modules>
</project>

View File

@@ -0,0 +1,15 @@
package org.ruoyi.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class FastGPTAnswerResponse {
private String id;
private String object;
private long created;
private String model;
private List<FastGPTChatChoice> choices;
}

View File

@@ -1,4 +1,4 @@
package tech.ordinaryroad.live.chat.client.douyu.client;
package org.ruoyi.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -6,15 +6,9 @@ import lombok.Data;
import java.io.Serializable;
/**
*
*
* @author https:www.unfbx.com
* @since 2023-03-02
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChatChoice implements Serializable {
public class FastGPTChatChoice implements Serializable {
private long index;
/**
* 请求参数stream为true返回是delta
@@ -28,4 +22,4 @@ public class ChatChoice implements Serializable {
private Message message;
@JsonProperty("finish_reason")
private String finishReason;
}
}

View File

@@ -0,0 +1,41 @@
package org.ruoyi.common.chat.entity.chat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.io.Serializable;
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class FastGPTChatCompletion extends ChatCompletion implements Serializable {
/**
* 是否使用FastGPT提供的上下文
*/
private String chatId;
/**
* 是否返回详细信息;stream模式下会通过event进行区分非stream模式结果保存在responseData中.
*/
private boolean detail;
/**
* 运行时变量
* 模块变量一个对象会替换模块中输入fastgpt框内容里的{{key}}
*/
private Variables variables;
/**
* responseChatItemId: string | undefined 。
* 如果传入,则会将该值作为本次对话的响应消息的 ID
* FastGPT 会自动将该 ID 存入数据库。请确保,
* 在当前chatId下responseChatItemId是唯一的。
*/
private String responseChatItemId;
}

View File

@@ -0,0 +1,20 @@
package org.ruoyi.common.chat.entity.chat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Variables implements Serializable {
private String uid;
private String name;
}

View File

@@ -7,9 +7,7 @@ import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.ruoyi.common.chat.constant.OpenAIConst;
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
import org.ruoyi.common.chat.entity.chat.Message;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
@@ -63,7 +61,7 @@ public class WebSocketEventListener extends EventSourceListener {
delta = mapper.writeValueAsString(completionResponse.getChoices().get(0).getDelta());
}catch (Exception e){
log.error("转换失败{}",e.getMessage());
}
}
session.sendMessage(new TextMessage(delta));
}

View File

@@ -10,6 +10,7 @@ import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import org.jetbrains.annotations.NotNull;
import org.ruoyi.common.chat.constant.OpenAIConst;
import org.ruoyi.common.chat.entity.billing.BillingUsage;
import org.ruoyi.common.chat.entity.billing.Subscription;
@@ -45,7 +46,6 @@ import org.ruoyi.common.chat.openai.interceptor.OpenAiAuthInterceptor;
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
import org.ruoyi.common.chat.openai.plugin.PluginParam;
import org.ruoyi.common.core.exception.base.BaseException;
import org.jetbrains.annotations.NotNull;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.jackson.JacksonConverterFactory;

View File

@@ -13,6 +13,7 @@ import okhttp3.*;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import okhttp3.sse.EventSources;
import org.jetbrains.annotations.NotNull;
import org.ruoyi.common.chat.constant.OpenAIConst;
import org.ruoyi.common.chat.entity.Tts.TextToSpeech;
import org.ruoyi.common.chat.entity.billing.BillingUsage;
@@ -27,7 +28,6 @@ import org.ruoyi.common.chat.entity.images.ImageResponse;
import org.ruoyi.common.chat.entity.models.Model;
import org.ruoyi.common.chat.entity.models.ModelResponse;
import org.ruoyi.common.chat.entity.whisper.Transcriptions;
import org.ruoyi.common.chat.entity.whisper.Translations;
import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
import org.ruoyi.common.chat.openai.exception.CommonError;
import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
@@ -40,7 +40,6 @@ import org.ruoyi.common.chat.openai.plugin.PluginParam;
import org.ruoyi.common.chat.sse.DefaultPluginListener;
import org.ruoyi.common.chat.sse.PluginListener;
import org.ruoyi.common.core.exception.base.BaseException;
import org.jetbrains.annotations.NotNull;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
@@ -145,13 +144,15 @@ public class OpenAiStreamClient {
.build();
}
okHttpClient = builder.okHttpClient;
if (apiHost.endsWith("/")) {
this.openAiApi = new Retrofit.Builder()
.baseUrl(apiHost)
.client(okHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(JacksonConverterFactory.create())
.build().create(OpenAiApi.class);
}
// this.openAiApi = new Retrofit.Builder()
// .baseUrl(apiHost)
// .client(okHttpClient)
// .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// .addConverterFactory(JacksonConverterFactory.create())
// .build().create(OpenAiApi.class);
}
/**

View File

@@ -36,16 +36,6 @@ public class ChatRequest {
*/
private Boolean stream = Boolean.TRUE;
/**
* 是否开启联网搜索(0关闭 1开启)
*/
private Boolean search = Boolean.FALSE;
/**
* 是否开启mcp
*/
private Boolean isMcp = Boolean.FALSE;
/**
* 知识库id
*/
@@ -56,7 +46,6 @@ public class ChatRequest {
*/
private Long userId;
/**
* 会话id
*/
@@ -68,14 +57,14 @@ public class ChatRequest {
private String appId;
/**
* 上下文的条数
* 对话角色
*/
private Integer contentNumber = 10;
private String role;
/**
* 是否携带上下文
* 对话id(每个聊天窗口都不一样)
*/
private Boolean usingContext = Boolean.TRUE;
private Long uuid;
}

View File

@@ -1,7 +1,6 @@
package org.ruoyi.common.chat.sse;
import lombok.extern.slf4j.Slf4j;
import okhttp3.sse.EventSourceListener;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;

View File

@@ -7,10 +7,10 @@ import com.knuddels.jtokkit.api.EncodingRegistry;
import com.knuddels.jtokkit.api.EncodingType;
import com.knuddels.jtokkit.api.ModelType;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
import org.ruoyi.common.chat.entity.chat.FunctionCall;
import org.ruoyi.common.chat.entity.chat.Message;
import org.jetbrains.annotations.NotNull;
import java.util.*;

View File

@@ -82,24 +82,6 @@
<artifactId>ip2region</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${weixin-java-cp.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>${weixin-java-miniapp.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>${weixin-java-pay.version}</version>
</dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>

View File

@@ -3,7 +3,6 @@ package org.ruoyi.common.core.factory;
import cn.hutool.core.lang.PatternPool;
import org.ruoyi.common.core.constant.RegexConstants;
import java.util.regex.Pattern;
/**

View File

@@ -0,0 +1,25 @@
package org.ruoyi.common.core.service;
/**
* @description: 基于ThreadLocal封装工具类用户保存和获取当前登录用户Sa-Token token值
* @author: yzm
**/
public class BaseContext {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
/**
* @description: 设置值
* @author: yzm
* @param: [token] 线程token
**/
public static void setCurrentToken(String token){
threadLocal.set(token);
}
/**
* @description: 获取值
* @author: yzm
**/
public static String getCurrentToken(){
return threadLocal.get();
}
}

View File

@@ -4,9 +4,9 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.utils.file.FileUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.File;

View File

@@ -1,49 +0,0 @@
<!--
~ MIT License
~
~ Copyright (c) 2023 OrdinaryRoad
~
~ 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.
-->
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-commons</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>live-chat-client-commons-base</artifactId>
<name>ordinaryroad-live-chat-client-commons-base</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,32 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.constant;
/**
* @author mjz
* @date 2023/8/26
*/
public class Constants {
}

View File

@@ -1,51 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.exception;
/**
* @author mjz
* @date 2023/9/5
*/
public class BaseException extends RuntimeException {
public BaseException() {
}
public BaseException(String message) {
super(message);
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(Throwable cause) {
super(cause);
}
public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,60 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 连接回调
*
* @author mjz
* @date 2023/8/26
*/
public interface IBaseConnectionListener<T> {
/**
* 连接建立成功
*/
default void onConnected(T t) {
// ignore
}
/**
* 连接建立失败
*
* @param t
*/
default void onConnectFailed(T t) {
// ignore
}
/**
* 连接断开
*
* @param t
*/
default void onDisconnected(T t) {
// ignore
}
}

View File

@@ -1,149 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseCmdMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
/**
* Base消息回调
*
* @author mjz
* @date 2023/8/26
*/
public interface IBaseMsgListener<T, CmdEnum extends Enum<CmdEnum>> {
/**
* 收到消息(所有消息)
*
* @param msg IMsg
*/
default void onMsg(T t, IMsg msg) {
this.onMsg(msg);
}
default void onMsg(IMsg msg) {
// ignore
}
/**
* 收到cmd消息所有cmd
*
* @param cmd CmdEnum
* @param cmdMsg BaseCmdMsg
*/
default void onCmdMsg(T t, CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
this.onCmdMsg(cmd, cmdMsg);
}
default void onCmdMsg(CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
// ignore
}
/**
* 收到其他cmd消息存在Enum但Listener没有对应的回调
*
* @param cmd CmdEnum
* @param cmdMsg BaseCmdMsg
*/
default void onOtherCmdMsg(T t, CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
this.onOtherCmdMsg(cmd, cmdMsg);
}
default void onOtherCmdMsg(CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
// ignore
}
/**
* 收到未知cmd消息
*
* @param cmdString 实际收到的cmd字符串
* @param msg BaseMsg
*/
default void onUnknownCmd(T t, String cmdString, IMsg msg) {
this.onUnknownCmd(cmdString, msg);
}
default void onUnknownCmd(String cmdString, IMsg msg) {
// ignore
}
/**
* 收到cmd消息所有cmd
*
* @param cmd CmdEnum
* @param cmdMsg BaseCmdMsg
* @deprecated use {@link #onCmdMsg(T, Enum, ICmdMsg)}
*/
default void onCmdMsg(T t, CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
this.onCmdMsg(cmd, cmdMsg);
}
/**
* @deprecated use {@link #onCmdMsg(Enum, ICmdMsg)}
*/
default void onCmdMsg(CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
// ignore
}
/**
* 收到其他cmd消息存在Enum但Listener没有对应的回调
*
* @param cmd CmdEnum
* @param cmdMsg BaseCmdMsg
* @deprecated use {@link #onOtherCmdMsg(T, Enum, ICmdMsg)}
*/
default void onOtherCmdMsg(T t, CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
this.onOtherCmdMsg(cmd, cmdMsg);
}
/**
* @deprecated use {@link #onOtherCmdMsg(Enum, ICmdMsg)}
*/
default void onOtherCmdMsg(CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
// ignore
}
/**
* 收到未知cmd消息
*
* @param cmdString 实际收到的cmd字符串
* @param msg BaseMsg
* @deprecated use {@link #onUnknownCmd(T, String, IMsg)}
*/
default void onUnknownCmd(T t, String cmdString, BaseMsg msg) {
this.onUnknownCmd(cmdString, msg);
}
/**
* @deprecated use {@link #onUnknownCmd(String, IMsg)}
*/
default void onUnknownCmd(String cmdString, BaseMsg msg) {
// ignore
}
}

View File

@@ -1,46 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 弹幕消息回调
*
* @author mjz
* @since 0.0.6
*/
public interface IDanmuMsgListener<T, DanmuMsg> {
/**
* 收到弹幕
*/
default void onDanmuMsg(T t, DanmuMsg msg) {
this.onDanmuMsg(msg);
}
default void onDanmuMsg(DanmuMsg msg) {
// ignore
}
}

View File

@@ -1,47 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 进入房间消息回调
*
* @author mjz
* @date 2023/12/14
* @since 0.0.16
*/
public interface IEnterRoomMsgListener<T, EnterRoomMsg> {
/**
* 用户进入房间
*/
default void onEnterRoomMsg(T t, EnterRoomMsg msg) {
this.onEnterRoomMsg(msg);
}
default void onEnterRoomMsg(EnterRoomMsg msg) {
// ignore
}
}

View File

@@ -1,47 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 礼物消息回调
*
* @author mjz
* @since 0.0.8
*/
public interface IGiftMsgListener<T, GiftMsg> {
/**
* 收到礼物
*/
default void onGiftMsg(T t, GiftMsg msg) {
this.onGiftMsg(msg);
}
default void onGiftMsg(GiftMsg msg) {
// ignore
}
}

View File

@@ -1,46 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 点赞消息回调
*
* @author mjz
* @since 0.2.0
*/
public interface ILikeMsgListener<T, LikeMsg> {
/**
* 收到点赞
*/
default void onLikeMsg(T t, LikeMsg msg) {
this.onLikeMsg(msg);
}
default void onLikeMsg(LikeMsg msg) {
// ignore
}
}

View File

@@ -1,47 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.listener;
/**
* 醒目留言消息回调
*
* @author mjz
* @date 2023/9/24
* @since 0.0.11
*/
public interface ISuperChatMsgListener<T, SuperChatMsg> {
/**
* 收到醒目留言
*/
default void onSuperChatMsg(T t, SuperChatMsg msg) {
this.onSuperChatMsg(msg);
}
default void onSuperChatMsg(SuperChatMsg msg) {
// ignore
}
}

View File

@@ -1,33 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2023/8/26
*/
public abstract class BaseCmdMsg<CmdEnum extends Enum<CmdEnum>> extends BaseMsg
implements ICmdMsg<CmdEnum> {
}

View File

@@ -1,74 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import java.util.HashMap;
import java.util.Map;
/**
* @author mjz
* @date 2023/8/26
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class BaseMsg implements IMsg {
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
/**
* 未知属性都放在这
*/
private final Map<String, JsonNode> unknownProperties = new HashMap<>();
@JsonAnyGetter
public Map<String, JsonNode> getUnknownProperties() {
return unknownProperties;
}
@JsonAnySetter
public void setOther(String key, JsonNode value) {
this.unknownProperties.put(key, value);
}
@Override
public String toString() {
try {
return OBJECT_MAPPER.writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new BaseException(e);
}
}
}

View File

@@ -1,38 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2023/10/2
*/
public interface ICmdMsg<CmdEnum extends Enum<CmdEnum>> extends IMsg {
String getCmd();
void setCmd(String cmd);
CmdEnum getCmdEnum();
}

View File

@@ -1,67 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2023/9/8
*/
public interface IDanmuMsg extends IMsg {
/**
* 粉丝牌名称
*/
String getBadgeName();
/**
* 粉丝牌等级
*/
byte getBadgeLevel();
/**
* 弹幕发送者id
*/
String getUid();
/**
* 弹幕发送者用户名
*/
String getUsername();
/**
* 弹幕发送者头像地址
*
* @since 0.0.11
*/
default String getUserAvatar() {
return null;
}
/**
* 弹幕内容
*/
String getContent();
}

View File

@@ -1,62 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* 入房消息
*
* @author mjz
* @date 2023/12/26
* @since 0.0.16
*/
public interface IEnterRoomMsg extends IMsg {
/**
* 粉丝牌名称
*/
String getBadgeName();
/**
* 粉丝牌等级
*/
byte getBadgeLevel();
/**
* 用户id
*/
String getUid();
/**
* 用户名
*/
String getUsername();
/**
* 头像地址
*/
default String getUserAvatar() {
return null;
}
}

View File

@@ -1,100 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2023/9/8
*/
public interface IGiftMsg extends IMsg {
/**
* 粉丝牌名称
*/
default String getBadgeName() {
return "";
}
/**
* 粉丝牌等级
*/
default byte getBadgeLevel() {
return 0;
}
/**
* 发送方id
*/
String getUid();
/**
* 发送方用户名
*/
String getUsername();
/**
* 发送方头像地址
*
* @since 0.0.11
*/
default String getUserAvatar() {
return null;
}
/**
* 礼物名称
*/
String getGiftName();
/**
* 礼物图像地址
*/
String getGiftImg();
/**
* 礼物id
*/
String getGiftId();
/**
* 礼物数量
*/
int getGiftCount();
/**
* 单个礼物价格
*/
int getGiftPrice();
/**
* 接收方id
*/
String getReceiveUid();
/**
* 接收方用户名
*/
String getReceiveUsername();
}

View File

@@ -1,71 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* @author mjz
* @date 2024/1/31
* @since 0.2.0
*/
public interface ILikeMsg extends IMsg {
/**
* 粉丝牌名称
*/
default String getBadgeName(){
return "";
}
/**
* 粉丝牌等级
*/
default byte getBadgeLevel(){
return 0;
}
/**
* 点赞者id
*/
String getUid();
/**
* 点赞者用户名
*/
String getUsername();
/**
* 点赞者头像地址
*/
default String getUserAvatar() {
return null;
}
/**
* 点赞数
*/
default int getClickCount() {
return 1;
}
}

View File

@@ -1,34 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
import java.io.Serializable;
/**
* @author mjz
* @date 2023/8/26
*/
public interface IMsg extends Serializable {
}

View File

@@ -1,49 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.base.msg;
/**
* 醒目留言
*
* @author mjz
* @date 2023/9/22
*/
public interface ISuperChatMsg extends IDanmuMsg {
/**
* 醒目留言持续时间,单位秒
*/
int getDuration();
@Override
default String getBadgeName() {
return "";
}
@Override
default byte getBadgeLevel() {
return 0;
}
}

View File

@@ -1,55 +0,0 @@
<!--
~ MIT License
~
~ Copyright (c) 2023 OrdinaryRoad
~
~ 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.
-->
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-commons</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<artifactId>live-chat-client-commons-client</artifactId>
<name>live-chat-client-commons-client</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-commons-base</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-commons-util</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,200 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.client;
import lombok.Getter;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
import tech.ordinaryroad.live.chat.client.commons.client.config.BaseLiveChatClientConfig;
import tech.ordinaryroad.live.chat.client.commons.client.enums.ClientStatusEnums;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
/**
* @author mjz
* @date 2023/8/26
*/
public abstract class BaseLiveChatClient<
Config extends BaseLiveChatClientConfig,
MsgListener extends IBaseMsgListener<?, ?>
> implements IBaseLiveChatClient<MsgListener> {
private final Config config;
@Getter
private volatile ClientStatusEnums status = ClientStatusEnums.NEW;
protected PropertyChangeSupport statusChangeSupport = new PropertyChangeSupport(status);
protected volatile boolean cancelReconnect = false;
protected final List<MsgListener> msgListeners = Collections.synchronizedList(new ArrayList<>());
protected BaseLiveChatClient(Config config) {
this.config = config;
}
public Config getConfig() {
return config;
}
@Override
public void connect(Runnable success) {
this.connect(success, null);
}
@Override
public void connect() {
this.connect(null, null);
}
@Override
public void disconnect(boolean cancelReconnect) {
this.cancelReconnect = cancelReconnect;
this.disconnect();
}
@Override
public void send(Object msg) {
this.send(msg, null, null);
}
@Override
public void send(Object msg, Runnable success) {
this.send(msg, success, null);
}
@Override
public void send(Object msg, Consumer<Throwable> failed) {
this.send(msg, null, failed);
}
@Override
public void sendDanmu(Object danmu) {
this.sendDanmu(danmu, null, null);
}
@Override
public void sendDanmu(Object danmu, Runnable success) {
this.sendDanmu(danmu, success, null);
}
@Override
public void sendDanmu(Object danmu, Consumer<Throwable> failed) {
this.sendDanmu(danmu, null, failed);
}
@Override
public void clickLike(int count) {
this.clickLike(count, null, null);
}
@Override
public void clickLike(int count, Runnable success) {
this.clickLike(count, success, null);
}
@Override
public void clickLike(int count, Consumer<Throwable> failed) {
this.clickLike(count, null, failed);
}
protected abstract void tryReconnect();
protected abstract String getWebSocketUriString();
/**
* 判断是否处于某个状态,或者处于后续状态
*
* @param status {@link ClientStatusEnums}
* @return false: 还没有到达该状态
*/
protected boolean checkStatus(ClientStatusEnums status) {
return this.status.getCode() >= Objects.requireNonNull(status).getCode();
}
protected void setStatus(ClientStatusEnums status) {
ClientStatusEnums oldStatus = this.status;
if (oldStatus != status) {
this.status = status;
this.statusChangeSupport.firePropertyChange("status", oldStatus, status);
}
}
public void addStatusChangeListener(PropertyChangeListener listener) {
this.statusChangeSupport.addPropertyChangeListener(listener);
}
public void removeStatusChangeListener(PropertyChangeListener listener) {
this.statusChangeSupport.removePropertyChangeListener(listener);
}
@Override
public void destroy() {
for (PropertyChangeListener propertyChangeListener : this.statusChangeSupport.getPropertyChangeListeners()) {
this.statusChangeSupport.removePropertyChangeListener(propertyChangeListener);
}
this.msgListeners.clear();
}
@Override
public boolean addMsgListener(MsgListener msgListener) {
if (msgListener == null) {
return false;
}
return this.msgListeners.add(msgListener);
}
@Override
public boolean addMsgListeners(List<MsgListener> msgListeners) {
if (msgListeners == null || msgListeners.isEmpty()) {
return false;
}
return this.msgListeners.addAll(msgListeners);
}
@Override
public boolean removeMsgListener(MsgListener msgListener) {
if (msgListener == null) {
return false;
}
return this.msgListeners.remove(msgListener);
}
@Override
public boolean removeMsgListeners(List<MsgListener> msgListeners) {
if (msgListeners == null || msgListeners.isEmpty()) {
return false;
}
return this.msgListeners.removeAll(msgListeners);
}
@Override
public void removeAllMsgListeners() {
this.msgListeners.clear();
}
}

View File

@@ -1,135 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.client;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
import java.util.List;
import java.util.function.Consumer;
/**
* @author mjz
* @date 2023/9/5
*/
public interface IBaseLiveChatClient<MsgListener extends IBaseMsgListener<?, ?>> {
void init();
boolean addMsgListener(MsgListener msgListener);
boolean addMsgListeners(List<MsgListener> msgListeners);
boolean removeMsgListener(MsgListener msgListener);
boolean removeMsgListeners(List<MsgListener> msgListeners);
void removeAllMsgListeners();
void connect(Runnable success, Consumer<Throwable> failed);
void connect(Runnable success);
void connect();
/**
* 手动断开连接
*
* @param cancelReconnect 取消本次的自动重连(如果启用自动重连)
*/
void disconnect(boolean cancelReconnect);
void disconnect();
void destroy();
void send(Object msg);
void send(Object msg, Runnable success, Consumer<Throwable> failed);
void send(Object msg, Runnable success);
void send(Object msg, Consumer<Throwable> failed);
/**
* 发送弹幕
*
* @param danmu 弹幕内容
* @since 0.0.6
*/
void sendDanmu(Object danmu);
/**
* 发送弹幕
*
* @param danmu 弹幕内容
* @since 0.0.6
*/
void sendDanmu(Object danmu, Runnable success, Consumer<Throwable> failed);
/**
* 发送弹幕
*
* @param danmu 弹幕内容
* @since 0.0.6
*/
void sendDanmu(Object danmu, Runnable success);
/**
* 发送弹幕
*
* @param danmu 弹幕内容
* @since 0.0.6
*/
void sendDanmu(Object danmu, Consumer<Throwable> failed);
/**
* 为直播间点赞
*
* @since 0.2.0
*/
void clickLike(int count);
/**
* 为直播间点赞
*
* @since 0.2.0
*/
void clickLike(int count, Runnable success, Consumer<Throwable> failed);
/**
* 为直播间点赞
*
* @since 0.2.0
*/
void clickLike(int count, Runnable success);
/**
* 为直播间点赞
*
* @since 0.2.0
*/
void clickLike(int count, Consumer<Throwable> failed);
}

View File

@@ -1,139 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.client.config;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
/**
* 直播间弹幕客户端配置
*
* @author mjz
* @date 2023/8/26
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder(toBuilder = true)
public abstract class BaseLiveChatClientConfig {
protected PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public static final long DEFAULT_HEARTBEAT_INITIAL_DELAY = 15;
public static final long DEFAULT_HEARTBEAT_PERIOD = 25;
public static final long DEFAULT_MIN_SEND_DANMU_PERIOD = 3000L;
private String websocketUri;
/**
* 浏览器中的Cookie
*/
private String cookie;
/**
* 直播间id
*/
private Object roomId;
/**
* 是否启用自动重连
*/
@Builder.Default
private boolean autoReconnect = Boolean.TRUE;
/**
* 重试延迟时间默认5s后重试
*/
@Builder.Default
private int reconnectDelay = 5;
/**
* 首次发送心跳包的延迟时间(秒)
*/
@Builder.Default
private long heartbeatInitialDelay = DEFAULT_HEARTBEAT_INITIAL_DELAY;
/**
* 心跳包发送周期(秒)
*/
@Builder.Default
private long heartbeatPeriod = DEFAULT_HEARTBEAT_PERIOD;
/**
* 最小发送弹幕时间间隔(毫秒)
*/
@Builder.Default
private long minSendDanmuPeriod = DEFAULT_MIN_SEND_DANMU_PERIOD;
public void setCookie(String cookie) {
String oldValue = this.cookie;
this.cookie = cookie;
this.propertyChangeSupport.firePropertyChange("cookie", oldValue, cookie);
}
public void setRoomId(Object roomId) {
if (!(roomId instanceof Number || roomId instanceof String)) {
throw new BaseException("房间ID仅支持数字或字符串所传参数类型" + roomId.getClass() + "值:" + roomId);
}
Object oldValue = this.roomId;
this.roomId = roomId;
this.propertyChangeSupport.firePropertyChange("roomId", oldValue, roomId);
}
public void setWebsocketUri(String websocketUri) {
String oldValue = this.websocketUri;
this.websocketUri = websocketUri;
this.propertyChangeSupport.firePropertyChange("websocketUri", oldValue, websocketUri);
}
public void setMinSendDanmuPeriod(long minSendDanmuPeriod) {
long oldValue = this.minSendDanmuPeriod;
this.minSendDanmuPeriod = minSendDanmuPeriod;
this.propertyChangeSupport.firePropertyChange("minSendDanmuPeriod", oldValue, minSendDanmuPeriod);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
this.propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
this.propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
this.propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
this.propertyChangeSupport.removePropertyChangeListener(listener);
}
}

View File

@@ -1,83 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.client.enums;
/**
* @author mjz
* @date 2023/8/26
*/
public enum ClientStatusEnums {
/**
* 新创建
*/
NEW(0),
/**
* 已初始化
*/
INITIALIZED(1),
/**
* 连接中
*/
CONNECTING(100),
/**
* 重新连接中
*/
RECONNECTING(101),
/**
* 已连接
*/
CONNECTED(200),
/**
* 连接失败
*/
CONNECT_FAILED(401),
/**
* 已断开连接
*/
DISCONNECTED(400),
/**
* 已销毁
*/
DESTROYED(-1),
;
public int getCode() {
return code;
}
ClientStatusEnums(int order) {
this.code = order;
}
private final int code;
}

View File

@@ -1,25 +0,0 @@
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-commons</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<artifactId>live-chat-client-commons-util</artifactId>
<name>live-chat-client-commons-util</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,80 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import java.net.HttpCookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* @author mjz
* @date 2023/8/27
*/
public class OrLiveChatCookieUtil {
public static String toString(List<HttpCookie> cookies) {
if (CollUtil.isEmpty(cookies)) {
return StrUtil.EMPTY;
}
return cookies.stream().map(httpCookie -> {
httpCookie.setVersion(0);
return httpCookie.toString();
}).collect(Collectors.joining("; "));
}
public static Map<String, String> parseCookieString(String cookies) {
Map<String, String> map = new HashMap<>();
if (StrUtil.isNotBlank(cookies) && !StrUtil.isNullOrUndefined(cookies)) {
try {
String[] split = cookies.split("; ");
for (String s : split) {
String[] split1 = s.split("=");
map.put(split1[0], split1[1]);
}
} catch (Exception e) {
throw new RuntimeException("cookie解析失败 " + cookies, e);
}
}
return map;
}
public static String getCookieByName(Map<String, String> cookieMap, String name, Supplier<String> supplier) {
String str = MapUtil.getStr(cookieMap, name);
return str == null ? supplier.get() : str;
}
public static String getCookieByName(String cookie, String name, Supplier<String> supplier) {
String str = MapUtil.getStr(parseCookieString(cookie), name);
return str == null ? supplier.get() : str;
}
}

View File

@@ -1,40 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.util;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
/**
* @author mjz
* @date 2023/12/2
*/
public class OrLiveChatNumberUtil extends NumberUtil {
public static long parseLong(Object object){
return NumberUtil.parseLong(StrUtil.toStringOrNull(object));
}
}

View File

@@ -1,50 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.util;
import cn.hutool.core.util.ReflectUtil;
import java.lang.reflect.Method;
/**
* @author mjz
* @date 2023/8/28
*/
public class OrLiveChatReflectUtil extends ReflectUtil {
public static Method getGetterMethod(Class<?> objectClass, String key) {
Method method;
if (key.startsWith("is")) {
method = ReflectUtil.getMethodByNameIgnoreCase(objectClass, key);
if (method == null) {
ReflectUtil.getMethodByNameIgnoreCase(objectClass, "get" + key);
}
} else {
method = ReflectUtil.getMethodByNameIgnoreCase(objectClass, "get" + key);
}
return method;
}
}

View File

@@ -1,54 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.commons.util;
import cn.hutool.core.date.LocalDateTimeUtil;
import java.time.ZoneId;
import java.time.ZonedDateTime;
/**
* @author mjz
* @date 2023/9/7
*/
public class OrLocalDateTimeUtil extends LocalDateTimeUtil {
public static ZoneId ZONE_ID_CTT = ZoneId.of(ZoneId.SHORT_IDS.get("CTT"));
/**
* 获取中国标准时间的当前时间戳(毫秒)
*/
public static long zonedCurrentTimeMillis() {
ZonedDateTime now = ZonedDateTime.now(ZONE_ID_CTT);
return now.toEpochSecond() * 1000 + now.getNano() / 1_000_000;
}
/**
* 获取中国标准时间的当前时间戳(秒)
*/
public static long zonedCurrentTimeSecs() {
return ZonedDateTime.now(ZONE_ID_CTT).toEpochSecond();
}
}

View File

@@ -1,44 +0,0 @@
<!--
~ MIT License
~
~ Copyright (c) 2023 OrdinaryRoad
~
~ 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.
-->
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-live</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>pom</packaging>
<artifactId>live-chat-client-commons</artifactId>
<modules>
<module>live-chat-client-commons-base</module>
<module>live-chat-client-commons-util</module>
<module>live-chat-client-commons-client</module>
</modules>
</project>

View File

@@ -1,55 +0,0 @@
<!--
~ MIT License
~
~ Copyright (c) 2023 OrdinaryRoad
~
~ 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.
-->
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-servers</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<artifactId>live-chat-client-servers-netty-client</artifactId>
<name>live-chat-client-servers-netty</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-commons-client</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-servers-netty</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,349 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.servers.netty.client.base;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
import tech.ordinaryroad.live.chat.client.commons.client.BaseLiveChatClient;
import tech.ordinaryroad.live.chat.client.commons.client.enums.ClientStatusEnums;
import tech.ordinaryroad.live.chat.client.servers.netty.client.config.BaseNettyClientConfig;
import tech.ordinaryroad.live.chat.client.servers.netty.handler.base.BaseBinaryFrameHandler;
import tech.ordinaryroad.live.chat.client.servers.netty.handler.base.BaseConnectionHandler;
import javax.net.ssl.SSLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* @author mjz
* @date 2023/8/26
*/
@Slf4j
public abstract class BaseNettyClient
<Config extends BaseNettyClientConfig,
CmdEnum extends Enum<CmdEnum>,
Msg extends IMsg,
MsgListener extends IBaseMsgListener<BinaryFrameHandler, CmdEnum>,
ConnectionHandler extends BaseConnectionHandler<ConnectionHandler>,
BinaryFrameHandler extends BaseBinaryFrameHandler<BinaryFrameHandler, CmdEnum, Msg, MsgListener>
>
extends BaseLiveChatClient<Config, MsgListener> {
@Getter
private final EventLoopGroup workerGroup;
@Getter
private final Bootstrap bootstrap = new Bootstrap();
private BinaryFrameHandler binaryFrameHandler;
private ConnectionHandler connectionHandler;
private IBaseConnectionListener<ConnectionHandler> connectionListener;
private Channel channel;
@Getter
private URI websocketUri;
protected IBaseConnectionListener<ConnectionHandler> clientConnectionListener;
/**
* 控制弹幕发送频率
*/
private volatile long lastSendDanmuTimeInMillis;
public abstract ConnectionHandler initConnectionHandler(IBaseConnectionListener<ConnectionHandler> clientConnectionListener);
public abstract BinaryFrameHandler initBinaryFrameHandler();
protected BaseNettyClient(Config config, EventLoopGroup workerGroup, IBaseConnectionListener<ConnectionHandler> connectionListener) {
super(config);
this.workerGroup = workerGroup;
this.connectionListener = connectionListener;
}
public void onConnected(ConnectionHandler connectionHandler) {
this.setStatus(ClientStatusEnums.CONNECTED);
if (this.connectionListener != null) {
this.connectionListener.onConnected(connectionHandler);
}
}
public void onConnectFailed(ConnectionHandler connectionHandler) {
this.setStatus(ClientStatusEnums.CONNECT_FAILED);
tryReconnect();
if (this.connectionListener != null) {
this.connectionListener.onConnectFailed(connectionHandler);
}
}
public void onDisconnected(ConnectionHandler connectionHandler) {
this.setStatus(ClientStatusEnums.DISCONNECTED);
tryReconnect();
if (this.connectionListener != null) {
this.connectionListener.onDisconnected(connectionHandler);
}
}
@Override
public void init() {
if (checkStatus(ClientStatusEnums.INITIALIZED)) {
return;
}
try {
this.websocketUri = new URI(getWebSocketUriString());
SslContext sslCtx = SslContextBuilder.forClient().build();
this.clientConnectionListener = new IBaseConnectionListener<ConnectionHandler>() {
@Override
public void onConnected(ConnectionHandler connectionHandler) {
BaseNettyClient.this.onConnected(connectionHandler);
}
@Override
public void onConnectFailed(ConnectionHandler connectionHandler) {
BaseNettyClient.this.onConnectFailed(connectionHandler);
}
@Override
public void onDisconnected(ConnectionHandler connectionHandler) {
BaseNettyClient.this.onDisconnected(connectionHandler);
}
};
this.binaryFrameHandler = this.initBinaryFrameHandler();
this.connectionHandler = this.initConnectionHandler(this.clientConnectionListener);
this.bootstrap.group(this.workerGroup)
// 创建Channel
.channel(NioSocketChannel.class)
.remoteAddress(this.websocketUri.getHost(), getInetPort())
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true)
// Channel配置
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
// 责任链
ChannelPipeline pipeline = ch.pipeline();
// 放到第一位 addFirst 支持wss链接服务端
pipeline.addFirst(sslCtx.newHandler(ch.alloc(), BaseNettyClient.this.websocketUri.getHost(), getInetPort()));
// 添加一个http的编解码器
pipeline.addLast(new HttpClientCodec());
// 添加一个用于支持大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
// 添加一个聚合器这个聚合器主要是将HttpMessage聚合成FullHttpRequest/Response
pipeline.addLast(new HttpObjectAggregator(BaseNettyClient.this.getConfig().getAggregatorMaxContentLength()));
// 连接处理器
pipeline.addLast(BaseNettyClient.this.connectionHandler);
// 弹幕处理器
pipeline.addLast(BaseNettyClient.this.binaryFrameHandler);
}
});
this.setStatus(ClientStatusEnums.INITIALIZED);
} catch (URISyntaxException e) {
throw new BaseException(e);
} catch (SSLException e) {
throw new BaseException(e);
}
}
private int getInetPort() {
int port = this.websocketUri.getPort();
return port == -1 ? "wss".equalsIgnoreCase(websocketUri.getScheme()) ? 443 : 80 : port;
}
@Override
public void connect(Runnable success, Consumer<Throwable> failed) {
if (this.cancelReconnect) {
this.cancelReconnect = false;
}
if (!checkStatus(ClientStatusEnums.INITIALIZED)) {
return;
}
if (getStatus() == ClientStatusEnums.CONNECTED) {
return;
}
if (getStatus() != ClientStatusEnums.RECONNECTING) {
this.setStatus(ClientStatusEnums.CONNECTING);
}
this.bootstrap.connect().addListener((ChannelFutureListener) connectFuture -> {
if (connectFuture.isSuccess()) {
if (log.isDebugEnabled()) {
log.debug("连接建立成功!");
}
this.channel = connectFuture.channel();
// 监听是否握手成功
this.connectionHandler.getHandshakeFuture().addListener((ChannelFutureListener) handshakeFuture -> {
try {
connectionHandler.sendAuthRequest(channel);
if (success != null) {
success.run();
}
} catch (Exception e) {
log.error("认证包发送失败,断开连接", e);
this.disconnect();
}
});
} else {
log.error("连接建立失败", connectFuture.cause());
this.onConnectFailed(this.connectionHandler);
if (failed != null) {
failed.accept(connectFuture.cause());
}
}
});
}
@Override
public void disconnect() {
if (this.channel == null) {
return;
}
this.channel.close();
}
@Override
protected void tryReconnect() {
if (this.cancelReconnect) {
this.cancelReconnect = false;
return;
}
if (!getConfig().isAutoReconnect()) {
return;
}
if (log.isWarnEnabled()) {
log.warn("{}s后将重新连接 {}", getConfig().getReconnectDelay(), getConfig().getRoomId());
}
workerGroup.schedule(() -> {
this.setStatus(ClientStatusEnums.RECONNECTING);
this.connect();
}, getConfig().getReconnectDelay(), TimeUnit.SECONDS);
}
@Override
public void send(Object msg, Runnable success, Consumer<Throwable> failed) {
ChannelFuture future = this.channel.writeAndFlush(msg);
if (success != null || failed != null) {
future.addListener((ChannelFutureListener) channelFuture -> {
if (channelFuture.isSuccess()) {
if (success != null) {
success.run();
}
} else {
if (failed != null) {
failed.accept(channelFuture.cause());
}
}
});
}
}
@Override
public void destroy() {
super.destroy();
// 销毁时不需要重连
this.cancelReconnect = true;
workerGroup.shutdownGracefully().addListener(future -> {
if (future.isSuccess()) {
this.setStatus(ClientStatusEnums.DESTROYED);
} else {
throw new BaseException("client销毁失败", future.cause());
}
});
}
@Override
protected String getWebSocketUriString() {
return getConfig().getWebsocketUri();
}
@Override
protected void setStatus(ClientStatusEnums status) {
if (log.isDebugEnabled()) {
if (getStatus() != status) {
log.debug("{} 状态变化 {} => {}\n", getClass().getSimpleName(), getStatus(), status);
}
}
super.setStatus(status);
}
@Override
public void sendDanmu(Object danmu, Runnable success, Consumer<Throwable> failed) {
throw new BaseException("暂未支持该功能");
}
@Override
public void clickLike(int count, Runnable success, Consumer<Throwable> failed) {
throw new BaseException("暂未支持该功能");
}
/**
* 发送弹幕前判断是否可以发送
*
* @param checkConnected 是否检查Client连接状态
*/
protected boolean checkCanSendDanmu(boolean checkConnected) {
if (checkConnected && getStatus() != ClientStatusEnums.CONNECTED) {
throw new BaseException("连接未建立,无法发送弹幕");
}
if (System.currentTimeMillis() - this.lastSendDanmuTimeInMillis <= getConfig().getMinSendDanmuPeriod()) {
if (log.isWarnEnabled()) {
log.warn("发送弹幕频率过快,忽略该次发送");
}
return false;
}
return true;
}
protected boolean checkCanSendDanmu() {
return checkCanSendDanmu(true);
}
/**
* 发送弹幕后调用该方法
*/
protected void finishSendDanmu() {
this.lastSendDanmuTimeInMillis = System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("弹幕发送完成");
}
}
public void iteratorMsgListeners(Consumer<MsgListener> consumer) {
binaryFrameHandler.iteratorMsgListeners(consumer);
}
}

View File

@@ -1,65 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.servers.netty.client.config;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import tech.ordinaryroad.live.chat.client.commons.client.config.BaseLiveChatClientConfig;
import java.net.URI;
/**
* @author mjz
* @date 2023/8/26
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder(toBuilder = true)
public abstract class BaseNettyClientConfig extends BaseLiveChatClientConfig {
/**
* 聚合器允许的最大消息体长度,默认 64*1024 byte
*
* @see HttpObjectAggregator#HttpObjectAggregator(int)
*/
@Builder.Default
private int aggregatorMaxContentLength = 64 * 1024;
/**
* WebSocketClientHandshaker最大消息体长度默认 64*1024 byte
*
* @see WebSocketClientHandshakerFactory#newHandshaker(URI, WebSocketVersion, String, boolean, HttpHeaders, int)
*/
@Builder.Default
private int maxFramePayloadLength = 64 * 1024;
}

View File

@@ -1,66 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.servers.netty.client.handler;
import lombok.Getter;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
import tech.ordinaryroad.live.chat.client.servers.netty.client.base.BaseNettyClient;
import tech.ordinaryroad.live.chat.client.servers.netty.handler.base.BaseBinaryFrameHandler;
import java.util.List;
/**
* BaseClientBinaryFrameHandler
*
* @author mjz
* @date 2023/8/30
*/
public abstract class BaseNettyClientBinaryFrameHandler<
Client extends BaseNettyClient<?, ?, ?, ?, ?, ?>,
BinaryFrameHandler extends BaseBinaryFrameHandler<BinaryFrameHandler, CmdEnum, Msg, MsgListener>,
CmdEnum extends Enum<CmdEnum>,
Msg extends IMsg,
MsgListener extends IBaseMsgListener<BinaryFrameHandler, CmdEnum>>
extends BaseBinaryFrameHandler<BinaryFrameHandler, CmdEnum, Msg, MsgListener> {
@Getter
protected final Client client;
public BaseNettyClientBinaryFrameHandler(List<MsgListener> msgListeners, Client client, long roomId) {
super(msgListeners, roomId);
this.client = client;
}
public BaseNettyClientBinaryFrameHandler(List<MsgListener> msgListeners, Client client) {
super(msgListeners, client.getConfig().getRoomId());
this.client = client;
}
public BaseNettyClientBinaryFrameHandler(List<MsgListener> msgListeners, long roomId) {
super(msgListeners, roomId);
this.client = null;
}
}

View File

@@ -1,65 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.servers.netty.client.handler;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import lombok.Getter;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
import tech.ordinaryroad.live.chat.client.servers.netty.client.base.BaseNettyClient;
import tech.ordinaryroad.live.chat.client.servers.netty.handler.base.BaseConnectionHandler;
/**
* BaseClientConnectionHandler
*
* @author mjz
* @date 2023/8/27
*/
public abstract class BaseNettyClientConnectionHandler<
Client extends BaseNettyClient<?, ?, ?, ?, ?, ?>,
ConnectionHandler extends BaseConnectionHandler<ConnectionHandler>>
extends BaseConnectionHandler<ConnectionHandler> {
@Getter
protected final Client client;
public BaseNettyClientConnectionHandler(WebSocketClientHandshaker handshaker, Client client, IBaseConnectionListener<ConnectionHandler> listener) {
super(handshaker, listener);
this.client = client;
}
public BaseNettyClientConnectionHandler(WebSocketClientHandshaker handshaker, Client client) {
this(handshaker, client, null);
}
public BaseNettyClientConnectionHandler(WebSocketClientHandshaker handshaker, IBaseConnectionListener<ConnectionHandler> listener) {
super(handshaker, listener);
this.client = null;
}
public BaseNettyClientConnectionHandler(WebSocketClientHandshaker handshaker, long roomId) {
super(handshaker, null);
this.client = null;
}
}

View File

@@ -1,59 +0,0 @@
<!--
~ MIT License
~
~ Copyright (c) 2023 OrdinaryRoad
~
~ 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.
-->
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-servers</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<artifactId>live-chat-client-servers-netty</artifactId>
<name>live-chat-client-servers-netty</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-commons-base</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,39 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.servers.netty.frame.base;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
/**
* @author mjz
* @date 2023/1/5
*/
public abstract class BaseBinaryWebSocketFrame extends BinaryWebSocketFrame {
public BaseBinaryWebSocketFrame(ByteBuf byteBuf) {
super(byteBuf);
}
}

View File

@@ -1,184 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.servers.netty.handler.base;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseCmdMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
import java.util.List;
import java.util.function.Consumer;
/**
* 消息处理器
*
* @author mjz
* @date 2023/1/4
*/
@Slf4j
public abstract class BaseBinaryFrameHandler<
T extends BaseBinaryFrameHandler<?, ?, ?, ?>,
CmdEnum extends Enum<CmdEnum>,
Msg extends IMsg,
MsgListener extends IBaseMsgListener<T, CmdEnum>
> extends SimpleChannelInboundHandler<BinaryWebSocketFrame>
implements IBaseMsgListener<T, CmdEnum> {
@Getter
private final Object roomId;
protected final List<MsgListener> msgListeners;
public BaseBinaryFrameHandler(List<MsgListener> msgListeners, Object roomId) {
this.msgListeners = msgListeners;
this.roomId = roomId;
if (this.msgListeners == null || this.msgListeners.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("listener not set");
}
}
}
/**
* 解码收到的二进制流
*
* @param byteBuf ByteBuf
* @return List<Msg>
*/
protected abstract List<Msg> decode(ByteBuf byteBuf);
@SuppressWarnings("unchecked")
protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame message) {
ByteBuf byteBuf = message.content();
List<Msg> msgList = this.decode(byteBuf);
if (msgList == null || msgList.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("msgList is empty");
}
return;
}
for (Msg msg : msgList) {
this.onMsg((T) BaseBinaryFrameHandler.this, msg);
if (msg instanceof ICmdMsg<?>) {
ICmdMsg<?> cmdMsg = (ICmdMsg<?>) msg;
Enum<?> cmdEnum = cmdMsg.getCmdEnum();
if (cmdEnum == null) {
this.onUnknownCmd((T) BaseBinaryFrameHandler.this, cmdMsg.getCmd(), cmdMsg);
} else {
this.onCmdMsg((T) BaseBinaryFrameHandler.this, (CmdEnum) cmdEnum, (ICmdMsg<CmdEnum>) cmdMsg);
}
}
if (msg instanceof BaseCmdMsg<?>) {
BaseCmdMsg<?> cmdMsg = (BaseCmdMsg<?>) msg;
Enum<?> cmdEnum = cmdMsg.getCmdEnum();
if (cmdEnum == null) {
this.onUnknownCmd((T) BaseBinaryFrameHandler.this, cmdMsg.getCmd(), cmdMsg);
} else {
this.onCmdMsg((T) BaseBinaryFrameHandler.this, (CmdEnum) cmdEnum, (BaseCmdMsg<CmdEnum>) cmdMsg);
}
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause.getCause() instanceof UnrecognizedPropertyException) {
log.error("缺少字段:{}", cause.getMessage());
} else {
super.exceptionCaught(ctx, cause);
}
}
@Override
public void onMsg(T t, IMsg msg) {
IBaseMsgListener.super.onMsg(t, msg);
iteratorMsgListeners(msgListener -> msgListener.onMsg(t, msg));
}
/**
* 重写该方法判断CMD或者调用{@link IBaseMsgListener#onOtherCmdMsg(Object, Enum, ICmdMsg)}
*
* @param t BaseBinaryFrameHandler
* @param cmd CmdEnum
* @param cmdMsg BaseMsg
*/
@Override
public void onCmdMsg(T t, CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
IBaseMsgListener.super.onCmdMsg(t, cmd, cmdMsg);
iteratorMsgListeners(msgListener -> msgListener.onCmdMsg(t, cmd, cmdMsg));
}
@Override
public void onUnknownCmd(T t, String cmdString, IMsg msg) {
IBaseMsgListener.super.onUnknownCmd(t, cmdString, msg);
iteratorMsgListeners(msgListener -> msgListener.onUnknownCmd(t, cmdString, msg));
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void iteratorMsgListeners(Consumer<MsgListener> consumer) {
if (msgListeners.isEmpty()) {
return;
}
for (int i = 0; i < msgListeners.size(); i++) {
consumer.accept(msgListeners.get(i));
}
}
@Override
public void onCmdMsg(T t, CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
IBaseMsgListener.super.onCmdMsg(t, cmd, cmdMsg);
iteratorMsgListeners(msgListener -> msgListener.onCmdMsg(t, cmd, cmdMsg));
}
@Override
public void onUnknownCmd(T t, String cmdString, BaseMsg msg) {
IBaseMsgListener.super.onUnknownCmd(t, cmdString, msg);
iteratorMsgListeners(msgListener -> msgListener.onUnknownCmd(t, cmdString, msg));
}
public String getRoomIdAsString() {
if (this.roomId == null) {
return "";
}
return this.roomId.toString();
}
public long getRoomIdAsLong() {
String roomIdAsString = this.getRoomIdAsString();
if (roomIdAsString.trim().isEmpty()) {
return 0L;
}
return Long.parseLong(roomIdAsString);
}
}

View File

@@ -1,168 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.servers.netty.handler.base;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
import io.netty.handler.ssl.SslCloseCompletionEvent;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.util.concurrent.ScheduledFuture;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
import java.util.concurrent.TimeUnit;
/**
* 连接处理器
*
* @author mjz
* @date 2023/8/21
*/
@Slf4j
public abstract class BaseConnectionHandler<ConnectionHandler extends BaseConnectionHandler<?>> extends SimpleChannelInboundHandler<FullHttpResponse> {
private final WebSocketClientHandshaker handshaker;
@Getter
private ChannelPromise handshakeFuture;
private final IBaseConnectionListener<ConnectionHandler> listener;
/**
* 客户端发送心跳包
*/
private ScheduledFuture<?> scheduledFuture = null;
public BaseConnectionHandler(WebSocketClientHandshaker handshaker, IBaseConnectionListener<ConnectionHandler> listener) {
this.handshaker = handshaker;
this.listener = listener;
}
public BaseConnectionHandler(WebSocketClientHandshaker handshaker) {
this(handshaker, null);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
this.handshakeFuture = ctx.newPromise();
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
this.handshaker.handshake(ctx.channel());
}
protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception {
// 判断是否正确握手
if (this.handshaker.isHandshakeComplete()) {
handshakeSuccessfully(ctx, msg);
} else {
try {
handshakeSuccessfully(ctx, msg);
} catch (WebSocketHandshakeException e) {
handshakeFailed(msg, e);
}
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (log.isDebugEnabled()) {
log.debug("userEventTriggered {}", evt.getClass());
}
if (evt instanceof SslHandshakeCompletionEvent) {
heartbeatCancel();
heartbeatStart(ctx);
if (this.listener != null) {
listener.onConnected((ConnectionHandler) BaseConnectionHandler.this);
}
} else if (evt instanceof SslCloseCompletionEvent) {
heartbeatCancel();
if (this.listener != null) {
listener.onDisconnected((ConnectionHandler) BaseConnectionHandler.this);
}
} else {
log.error("待处理 {}", evt.getClass());
}
super.userEventTriggered(ctx, evt);
}
/**
* 开始发送心跳包
*/
private void heartbeatStart(ChannelHandlerContext ctx) {
scheduledFuture = ctx.executor().scheduleAtFixedRate(() -> {
sendHeartbeat(ctx);
}, getHeartbeatInitialDelay(), getHeartbeatPeriod(), TimeUnit.SECONDS);
}
/**
* 取消发送心跳包
*/
private void heartbeatCancel() {
if (null != scheduledFuture && !scheduledFuture.isCancelled()) {
scheduledFuture.cancel(true);
scheduledFuture = null;
}
}
protected abstract void sendHeartbeat(ChannelHandlerContext ctx);
public abstract void sendAuthRequest(Channel channel);
protected abstract long getHeartbeatPeriod();
protected abstract long getHeartbeatInitialDelay();
private void handshakeSuccessfully(ChannelHandlerContext ctx, FullHttpResponse msg) {
if (log.isDebugEnabled()) {
log.debug("握手完成!");
}
this.handshaker.finishHandshake(ctx.channel(), msg);
this.handshakeFuture.setSuccess();
}
private void handshakeFailed(FullHttpResponse msg, WebSocketHandshakeException e) {
log.error("握手失败status:" + msg.status(), e);
this.handshakeFuture.setFailure(e);
if (listener != null) {
this.listener.onConnectFailed((ConnectionHandler) this);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
log.error("exceptionCaught", cause);
if (!this.handshakeFuture.isDone()) {
this.handshakeFuture.setFailure(cause);
}
ctx.close();
}
}

View File

@@ -1,47 +0,0 @@
<!--
~ MIT License
~
~ Copyright (c) 2023 OrdinaryRoad
~
~ 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.
-->
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-live</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>pom</packaging>
<artifactId>live-chat-client-servers</artifactId>
<name>live-chat-client-servers</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modules>
<module>live-chat-client-servers-netty</module>
<module>live-chat-client-servers-netty-client</module>
</modules>
</project>

View File

@@ -1,71 +0,0 @@
<!--
~ MIT License
~
~ Copyright (c) 2023 OrdinaryRoad
~
~ 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.
-->
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-clients</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<artifactId>live-chat-client-bilibili</artifactId>
<name>live-chat-client-bilibili</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>live-chat-client-servers-netty-client</artifactId>
</dependency>
<dependency>
<groupId>com.aayushatharva.brotli4j</groupId>
<artifactId>brotli4j</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,247 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.bilibili.api;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import tech.ordinaryroad.live.chat.client.bilibili.api.request.BilibiliLikeReportV3Request;
import tech.ordinaryroad.live.chat.client.bilibili.api.request.BilibiliSendMsgRequest;
import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import tech.ordinaryroad.live.chat.client.commons.util.OrLiveChatCookieUtil;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static tech.ordinaryroad.live.chat.client.commons.base.msg.BaseMsg.OBJECT_MAPPER;
/**
* B站API简易版
*
* @author mjz
* @date 2023/5/5
*/
@Slf4j
public class BilibiliApis {
public static final TimedCache<Long, String> GIFT_IMG_CACHE = new TimedCache<>(TimeUnit.DAYS.toMillis(1));
public static final String KEY_COOKIE_CSRF = "bili_jct";
public static final String KEY_UID = "DedeUserID";
@SneakyThrows
public static RoomInitResult roomInit(long roomId, String cookie) {
@Cleanup
HttpResponse response = createGetRequest("https://api.live.bilibili.com/room/v1/Room/room_init?id=" + roomId, cookie).execute();
JsonNode dataJsonNode = responseInterceptor(response.body());
return OBJECT_MAPPER.readValue(dataJsonNode.toString(), RoomInitResult.class);
}
public static JsonNode roomGiftConfig(long roomId, String cookie) {
@Cleanup
HttpResponse response = createGetRequest("https://api.live.bilibili.com/xlive/web-room/v1/giftPanel/roomGiftConfig?platform=pc&source=live&build=0&global_version=0&room_id=" + roomId, cookie).execute();
return responseInterceptor(response.body());
}
/**
* @param roomId
* @param type 直播间用0
* @return <pre>{@code
* {
* "group": "live",
* "business_id": 0,
* "refresh_row_factor": 0.125,
* "refresh_rate": 100,
* "max_delay": 5000,
* "token": "-wm5-Qo4BBAztd1qp5ZJpgyTMRBhCc7yikz5d9rAd63PV46G9BMwl0R10kMM8Ilb-UieZGjLtipPrz4Cvi0DdhGFwOi8PJpFN9K-LoXh6Z_4yjEIwgRerDiMIstHzJ80J3B7wnRisAYkWA==",
* "host_list": [{
* "host": "ali-bj-live-comet-09.chat.bilibili.com",
* "port": 2243,
* "wss_port": 443,
* "ws_port": 2244
* }, {
* "host": "ali-gz-live-comet-02.chat.bilibili.com",
* "port": 2243,
* "wss_port": 443,
* "ws_port": 2244
* }, {
* "host": "broadcastlv.chat.bilibili.com",
* "port": 2243,
* "wss_port": 443,
* "ws_port": 2244
* }]
* }
* }</pre>
*/
public static JsonNode getDanmuInfo(long roomId, int type, String cookie) {
@Cleanup
HttpResponse response = createGetRequest("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=" + roomId + "&type=" + type, cookie).execute();
return responseInterceptor(response.body());
}
public static String getGiftImgById(long giftId, long roomId) {
if (!GIFT_IMG_CACHE.containsKey(giftId)) {
ThreadUtil.execAsync(() -> {
updateGiftImgCache(roomId, null);
});
}
return GIFT_IMG_CACHE.get(giftId);
}
/**
* 更新礼物图片缓存
*/
public static void updateGiftImgCache(long roomId, String cookie) {
JsonNode jsonNode = roomGiftConfig(roomId, cookie);
for (JsonNode node : jsonNode.get("global_gift").get("list")) {
long giftId = node.get("id").asLong();
String giftImgUrl = node.get("webp").asText();
GIFT_IMG_CACHE.put(giftId, giftImgUrl);
}
}
/**
* 发送弹幕
*
* @param request {@link BilibiliSendMsgRequest}
* @param cookie Cookie
*/
public static void sendMsg(BilibiliSendMsgRequest request, String cookie) {
if (StrUtil.isBlank(cookie)) {
throw new BaseException("发送弹幕接口cookie不能为空");
}
Map<String, Object> stringObjectMap = BeanUtil.beanToMap(request);
@Cleanup HttpResponse execute = HttpUtil.createPost("https://api.live.bilibili.com/msg/send")
.cookie(cookie)
.form(stringObjectMap)
.execute();
responseInterceptor(execute.body());
}
/**
* 发送弹幕
*
* @param msg 内容
* @param realRoomId 真实房间id
* @param cookie Cookie
*/
public static void sendMsg(String msg, long realRoomId, String cookie) {
String biliJct = OrLiveChatCookieUtil.getCookieByName(cookie, KEY_COOKIE_CSRF, () -> {
throw new BaseException("cookie中缺少参数" + KEY_COOKIE_CSRF);
});
BilibiliSendMsgRequest request = new BilibiliSendMsgRequest(msg, StrUtil.toString(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).toEpochSecond()), realRoomId, biliJct, biliJct);
sendMsg(request, cookie);
}
/**
* 为主播点赞
*
* @param request {@link BilibiliLikeReportV3Request}
* @param cookie Cookie
*/
public static void likeReportV3(BilibiliLikeReportV3Request request, String cookie) {
if (StrUtil.isBlank(cookie)) {
throw new BaseException("为主播点赞接口cookie不能为空");
}
Map<String, Object> stringObjectMap = BeanUtil.beanToMap(request);
@Cleanup HttpResponse execute = HttpUtil.createPost("https://api.live.bilibili.com/xlive/app-ucenter/v1/like_info_v3/like/likeReportV3")
.cookie(cookie)
.form(stringObjectMap)
.execute();
responseInterceptor(execute.body());
}
/**
* 为主播点赞
*
* @param anchor_id 主播Uid {@link RoomInitResult#uid}
* @param realRoomId 真实房间Id {@link RoomInitResult#room_id}
* @param cookie Cookie
*/
public static void likeReportV3(long anchor_id, long realRoomId, String cookie) {
String uid = OrLiveChatCookieUtil.getCookieByName(cookie, KEY_UID, () -> {
throw new BaseException("cookie中缺少参数" + KEY_UID);
});
String biliJct = OrLiveChatCookieUtil.getCookieByName(cookie, KEY_COOKIE_CSRF, () -> {
throw new BaseException("cookie中缺少参数" + KEY_COOKIE_CSRF);
});
BilibiliLikeReportV3Request request = new BilibiliLikeReportV3Request(realRoomId, uid, anchor_id, biliJct, biliJct);
likeReportV3(request, cookie);
}
public static HttpRequest createGetRequest(String url, String cookies) {
return HttpUtil.createGet(url)
.cookie(cookies);
}
private static JsonNode responseInterceptor(String responseString) {
try {
JsonNode jsonNode = OBJECT_MAPPER.readTree(responseString);
int code = jsonNode.get("code").asInt();
if (code == 0) {
// 成功
return jsonNode.get("data");
} else {
throw new BaseException(jsonNode.get("message").asText());
}
} catch (JsonProcessingException e) {
throw new BaseException(e);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class RoomInitResult {
private long room_id;
private int short_id;
private long uid;
private int need_p2p;
private boolean is_hidden;
private boolean is_locked;
private boolean is_portrait;
private int live_status;
private int hidden_till;
private int lock_till;
private boolean encrypted;
private boolean pwd_verified;
private long live_time;
private int room_shield;
private int is_sp;
private int special_type;
}
}

View File

@@ -1,74 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.bilibili.api.request;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author mjz
* @date 2024/1/31
*/
@Data
@NoArgsConstructor
public class BilibiliLikeReportV3Request {
/**
* 本次点赞次数
*/
private int click_time = 1;
/**
* 房间真实ID
*/
private long room_id;
/**
* Cookie中的DedeUserID
*/
private String uid;
/**
* RoomInitResult中的uid
*/
private long anchor_id;
/**
* Cookie中的bili_jct
*/
private String csrf;
/**
* Cookie中的bili_jct
*/
private String csrf_token;
/**
* 暂时留空
*/
private String visit_id = StrUtil.EMPTY;
public BilibiliLikeReportV3Request(long room_id, String uid, long anchor_id, String csrf, String csrf_token) {
this.room_id = room_id;
this.uid = uid;
this.anchor_id = anchor_id;
this.csrf = csrf;
this.csrf_token = csrf_token;
}
}

View File

@@ -1,77 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.bilibili.api.request;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author mjz
* @date 2023/9/7
*/
@Data
@NoArgsConstructor
public class BilibiliSendMsgRequest {
private String bubble = "0";
/**
* 弹幕内容
*/
private String msg;
/**
* 弹幕颜色
*/
private String color = "16777215";
private String mode = "1";
private String room_type = "0";
private String jumpfrom = "0";
/**
* 字体大小
*/
private String fontsize = "25";
/**
* 时间戳(秒)
*/
private String rnd;
/**
* 房间真实ID
*/
private long roomid;
/**
* Cookie中的bili_jct
*/
private String csrf;
/**
* Cookie中的bili_jct
*/
private String csrf_token;
public BilibiliSendMsgRequest(String msg, String rnd, long roomid, String csrf, String csrf_token) {
this.msg = msg;
this.rnd = rnd;
this.roomid = roomid;
this.csrf = csrf;
this.csrf_token = csrf_token;
}
}

View File

@@ -1,184 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.bilibili.client;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import lombok.extern.slf4j.Slf4j;
import tech.ordinaryroad.live.chat.client.bilibili.api.BilibiliApis;
import tech.ordinaryroad.live.chat.client.bilibili.config.BilibiliLiveChatClientConfig;
import tech.ordinaryroad.live.chat.client.bilibili.constant.BilibiliCmdEnum;
import tech.ordinaryroad.live.chat.client.bilibili.listener.IBilibiliConnectionListener;
import tech.ordinaryroad.live.chat.client.bilibili.listener.IBilibiliMsgListener;
import tech.ordinaryroad.live.chat.client.bilibili.msg.base.IBilibiliMsg;
import tech.ordinaryroad.live.chat.client.bilibili.netty.handler.BilibiliBinaryFrameHandler;
import tech.ordinaryroad.live.chat.client.bilibili.netty.handler.BilibiliConnectionHandler;
import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
import tech.ordinaryroad.live.chat.client.servers.netty.client.base.BaseNettyClient;
import java.util.List;
import java.util.function.Consumer;
/**
* B站直播间弹幕客户端
*
* @author mjz
* @date 2023/8/20
*/
@Slf4j
public class BilibiliLiveChatClient extends BaseNettyClient<
BilibiliLiveChatClientConfig,
BilibiliCmdEnum,
IBilibiliMsg,
IBilibiliMsgListener,
BilibiliConnectionHandler,
BilibiliBinaryFrameHandler
> {
private BilibiliApis.RoomInitResult roomInitResult = new BilibiliApis.RoomInitResult();
public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config, List<IBilibiliMsgListener> msgListeners, IBilibiliConnectionListener connectionListener, EventLoopGroup workerGroup) {
super(config, workerGroup, connectionListener);
addMsgListeners(msgListeners);
// 初始化
this.init();
}
public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config, IBilibiliMsgListener msgListener, IBilibiliConnectionListener connectionListener, EventLoopGroup workerGroup) {
super(config, workerGroup, connectionListener);
addMsgListener(msgListener);
// 初始化
this.init();
}
public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config, IBilibiliMsgListener msgListener, IBilibiliConnectionListener connectionListener) {
this(config, msgListener, connectionListener, new NioEventLoopGroup());
}
public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config, IBilibiliMsgListener msgListener) {
this(config, msgListener, null, new NioEventLoopGroup());
}
public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config) {
this(config, null);
}
@Override
public void init() {
roomInitResult = BilibiliApis.roomInit(getConfig().getRoomId(), getConfig().getCookie());
super.init();
}
@Override
public BilibiliConnectionHandler initConnectionHandler(IBaseConnectionListener<BilibiliConnectionHandler> clientConnectionListener) {
return new BilibiliConnectionHandler(
WebSocketClientHandshakerFactory.newHandshaker(getWebsocketUri(), WebSocketVersion.V13, null, true, new DefaultHttpHeaders(), getConfig().getMaxFramePayloadLength()),
BilibiliLiveChatClient.this, clientConnectionListener
);
}
@Override
public BilibiliBinaryFrameHandler initBinaryFrameHandler() {
return new BilibiliBinaryFrameHandler(super.msgListeners, BilibiliLiveChatClient.this);
}
@Override
public void sendDanmu(Object danmu, Runnable success, Consumer<Throwable> failed) {
if (!checkCanSendDanmu(false)) {
return;
}
if (danmu instanceof String) {
String msg = (String) danmu;
try {
if (log.isDebugEnabled()) {
log.debug("{} bilibili发送弹幕 {}", getConfig().getRoomId(), danmu);
}
boolean sendSuccess = false;
try {
BilibiliApis.sendMsg(msg, roomInitResult.getRoom_id(), getConfig().getCookie());
sendSuccess = true;
} catch (Exception e) {
log.error("bilibili弹幕发送失败", e);
if (failed != null) {
failed.accept(e);
}
}
if (!sendSuccess) {
return;
}
if (log.isDebugEnabled()) {
log.debug("bilibili弹幕发送成功 {}", danmu);
}
if (success != null) {
success.run();
}
finishSendDanmu();
} catch (Exception e) {
log.error("bilibili弹幕发送失败", e);
if (failed != null) {
failed.accept(e);
}
}
} else {
super.sendDanmu(danmu, success, failed);
}
}
@Override
public void clickLike(int count, Runnable success, Consumer<Throwable> failed) {
if (count <= 0) {
throw new BaseException("点赞次数必须大于0");
}
boolean successfullyClicked = false;
try {
BilibiliApis.likeReportV3(roomInitResult.getUid(), roomInitResult.getRoom_id(), getConfig().getCookie());
successfullyClicked = true;
} catch (Exception e) {
log.error("Bilibili为直播间点赞失败", e);
if (failed != null) {
failed.accept(e);
}
}
if (!successfullyClicked) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Bilibili为直播间点赞成功");
}
if (success != null) {
success.run();
}
}
}

View File

@@ -1,67 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.bilibili.config;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
import tech.ordinaryroad.live.chat.client.commons.util.OrLiveChatNumberUtil;
import tech.ordinaryroad.live.chat.client.servers.netty.client.config.BaseNettyClientConfig;
/**
* B站直播间弹幕客户端配置
*
* @author mjz
* @date 2023/8/21
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder(toBuilder = true)
public class BilibiliLiveChatClientConfig extends BaseNettyClientConfig {
/**
* @see ProtoverEnum
*/
@Builder.Default
private ProtoverEnum protover = ProtoverEnum.NORMAL_ZLIB;
@Builder.Default
private String websocketUri = "wss://broadcastlv.chat.bilibili.com:443/sub";
@Override
public Long getRoomId() {
return OrLiveChatNumberUtil.parseLong(super.getRoomId());
}
public void setProtover(ProtoverEnum protover) {
ProtoverEnum oldValue = this.protover;
this.protover = protover;
super.propertyChangeSupport.firePropertyChange("protover", oldValue, protover);
}
}

View File

@@ -1,143 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.bilibili.constant;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author mjz
* @date 2023/1/6
*/
@Getter
@RequiredArgsConstructor
public enum BilibiliCmdEnum {
/**
* 游客状态下5分钟后会出现登录提示弹幕中的用户名、用户id等信息将不再可见
*/
LOG_IN_NOTICE,
/**
* 收到弹幕
*/
DANMU_MSG,
/**
* 收到礼物
*/
SEND_GIFT,
/**
* 有人上舰
*/
GUARD_BUY,
/**
* 欢迎舰长
*/
WELCOME_GUARD,
WELCOME,
/**
* 礼物连击
*/
COMBO_SEND,
/**
* 欢迎高能用户、(舰长?待验证)特殊消息
*/
ENTRY_EFFECT,
HOT_RANK_CHANGED,
HOT_RANK_CHANGED_V2,
INTERACT_WORD,
/**
* 开始直播
*/
LIVE,
LIVE_INTERACTIVE_GAME,
NOTICE_MSG,
/**
* 高能榜数量更新
*/
ONLINE_RANK_COUNT,
ONLINE_RANK_TOP3,
ONLINE_RANK_V2,
PK_BATTLE_END,
PK_BATTLE_FINAL_PROCESS,
PK_BATTLE_PROCESS,
PK_BATTLE_PROCESS_NEW,
PK_BATTLE_SETTLE,
PK_BATTLE_SETTLE_USER,
PK_BATTLE_SETTLE_V2,
/**
* 主播准备中
*/
PREPARING,
ROOM_REAL_TIME_MESSAGE_UPDATE,
/**
* 停止直播的房间ID列表
*/
STOP_LIVE_ROOM_LIST,
/**
* 醒目留言
*/
SUPER_CHAT_MESSAGE,
SUPER_CHAT_MESSAGE_JPN,
/**
* 删除醒目留言
*/
SUPER_CHAT_MESSAGE_DELETE,
WIDGET_BANNER,
/**
* 点赞数更新
*/
LIKE_INFO_V3_UPDATE,
/**
* 为主播点赞
*/
LIKE_INFO_V3_CLICK,
HOT_ROOM_NOTIFY,
/**
* 观看人数变化
*/
WATCHED_CHANGE,
POPULAR_RANK_CHANGED,
COMMON_NOTICE_DANMAKU,
LIVE_MULTI_VIEW_CHANGE,
RECOMMEND_CARD,
PK_BATTLE_START_NEW,
PK_BATTLE_ENTRANCE,
AREA_RANK_CHANGED,
ROOM_BLOCK_MSG,
USER_TOAST_MSG,
PK_BATTLE_PRE_NEW,
PK_BATTLE_RANK_CHANGE,
PK_BATTLE_START,
PK_BATTLE_PRE,
PLAY_TAG,
;
public static BilibiliCmdEnum getByString(String cmd) {
try {
return BilibiliCmdEnum.valueOf(cmd);
} catch (Exception e) {
return null;
}
}
}

View File

@@ -1,86 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.bilibili.constant;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author mjz
* @date 2023/1/5
*/
@Getter
@RequiredArgsConstructor
public enum OperationEnum {
HANDSHAKE(0),
HANDSHAKE_REPLY(1),
/**
* 心跳包
*/
HEARTBEAT(2),
/**
* 心跳包回复(人气值)
*/
HEARTBEAT_REPLY(3),
SEND_MSG(4),
/**
* 普通包(命令)
*/
SEND_SMS_REPLY(5),
DISCONNECT_REPLY(6),
/**
* 认证包
*/
AUTH(7),
/**
* 认证包回复
*/
AUTH_REPLY(8),
RAW(9),
PROTO_READY(10),
PROTO_FINISH(11),
CHANGE_ROOM(12),
CHANGE_ROOM_REPLY(13),
REGISTER(14),
REGISTER_REPLY(15),
UNREGISTER(16),
UNREGISTER_REPLY(17),
;
private final int code;
public static OperationEnum getByCode(int code) {
for (OperationEnum value : OperationEnum.values()) {
if (value.code == code) {
return value;
}
}
return null;
}
}

View File

@@ -1,67 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.bilibili.constant;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author mjz
* @date 2023/1/5
*/
@Getter
@RequiredArgsConstructor
public enum ProtoverEnum {
/**
* 普通包正文不使用压缩
*/
NORMAL_NO_COMPRESSION(0),
/**
* 心跳及认证包正文不使用压缩
*/
HEARTBEAT_AUTH_NO_COMPRESSION(1),
/**
* 普通包正文使用zlib压缩
*/
NORMAL_ZLIB(2),
/**
* 普通包正文使用brotli压缩,解压为一个带头部的协议0普通包
*/
NORMAL_BROTLI(3),
;
private final int code;
public static ProtoverEnum getByCode(int code) {
for (ProtoverEnum value : ProtoverEnum.values()) {
if (value.code == code) {
return value;
}
}
return null;
}
}

View File

@@ -1,38 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2023 OrdinaryRoad
*
* 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.
*/
package tech.ordinaryroad.live.chat.client.bilibili.listener;
import tech.ordinaryroad.live.chat.client.bilibili.netty.handler.BilibiliConnectionHandler;
import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
/**
* 连接回调
*
* @author mjz
* @date 2023/8/21
*/
public interface IBilibiliConnectionListener extends IBaseConnectionListener<BilibiliConnectionHandler> {
}

Some files were not shown because too many files have changed in this diff Show More