mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 23:53:55 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9430094072 | ||
|
|
50490b70b7 | ||
|
|
fac3a0acd7 | ||
|
|
73c4d6a604 | ||
|
|
6ba63db47f | ||
|
|
fdd16ad3c7 | ||
|
|
9178faef6d | ||
|
|
e2da300e09 | ||
|
|
e11a37ffec | ||
|
|
9ba836b814 | ||
|
|
a345d41e9e | ||
|
|
d36ea8a906 | ||
|
|
28f8533d0a | ||
|
|
587b9e8800 | ||
|
|
c3a1b0f609 | ||
|
|
1b25ed5903 | ||
|
|
e87c96300b | ||
|
|
ba5bf33b5a | ||
|
|
94e1c09647 | ||
|
|
6bd7ad9413 | ||
|
|
d75032e853 | ||
|
|
261ecfea4d | ||
|
|
6f9cd94e66 | ||
|
|
ee2e1ed5fc | ||
|
|
92c1317d4c | ||
|
|
cfb97a087b | ||
|
|
76ba99aa6c | ||
|
|
330d7ed95c | ||
|
|
878e73524b | ||
|
|
13b0aa1a7b | ||
|
|
326342b834 | ||
|
|
8bbd610f12 | ||
|
|
549c2af988 | ||
|
|
d02c56a0e0 | ||
|
|
1f3654cc01 | ||
|
|
b6b6b0310f | ||
|
|
4c91bffcc7 | ||
|
|
d730a6cd06 | ||
|
|
67050f972e | ||
|
|
ffafd32693 | ||
|
|
ab4ec5e468 | ||
|
|
a07277feb8 | ||
|
|
a3cd4b6e66 | ||
|
|
25b7e76407 | ||
|
|
630cb13066 | ||
|
|
81188a0129 | ||
|
|
cf1abd90a0 | ||
|
|
6865a8be45 | ||
|
|
b63fcc80a1 | ||
|
|
a19ba6d48b | ||
|
|
70b6efe922 | ||
|
|
0b07269346 | ||
|
|
336954eeeb | ||
|
|
2988e5eef2 | ||
|
|
fe87889ccd | ||
|
|
0598ab4400 | ||
|
|
7d090f8ecc | ||
|
|
088ee47b3c | ||
|
|
a320b4c001 | ||
|
|
3adce95340 | ||
|
|
2a6bfd792a | ||
|
|
0816f1f589 | ||
|
|
497def8055 | ||
|
|
62be26ff47 | ||
|
|
6a81d80778 | ||
|
|
8914beae26 | ||
|
|
60c38a8dea | ||
|
|
4e0934debf | ||
|
|
17126303fa | ||
|
|
98fa95190c | ||
|
|
3a2261107c | ||
|
|
b1b64abed9 | ||
|
|
05356b6652 | ||
|
|
cbd0c6615d | ||
|
|
90e389efa2 | ||
|
|
00a0b26797 | ||
|
|
69b5ffe3bf | ||
|
|
31539294eb | ||
|
|
d1a2307134 | ||
|
|
97b777f1b2 | ||
|
|
c565bfc6bc | ||
|
|
a63137abc9 | ||
|
|
3ca06f8d50 | ||
|
|
e5b0e202be | ||
|
|
41db6aafd9 | ||
|
|
c66385d1f7 | ||
|
|
b0304fd875 | ||
|
|
5bfc3efc9c | ||
|
|
62845cfcf1 | ||
|
|
5c536e72df | ||
|
|
7cfece1592 | ||
|
|
f393b2c9ba | ||
|
|
164f2e5472 | ||
|
|
1434ecbc2e | ||
|
|
c2e443d4ac | ||
|
|
c079125458 | ||
|
|
4c1d7b34b9 | ||
|
|
53772b3fe3 | ||
|
|
28d3c40835 | ||
|
|
a01faecd0c | ||
|
|
a1b071e38e | ||
|
|
571622a0f4 | ||
|
|
4ab84d7209 | ||
|
|
4b90e70aad | ||
|
|
30a938e348 | ||
|
|
f6f4384e42 | ||
|
|
911b11211e | ||
|
|
ee0da9fc3a | ||
|
|
8bb596c74f | ||
|
|
d0fdeb33e0 | ||
|
|
ae7f41032e | ||
|
|
f30cb6badb | ||
|
|
97a9c1e08a | ||
|
|
1e699c027d | ||
|
|
9bd731729d | ||
|
|
dc17eccf76 | ||
|
|
3649a33afb | ||
|
|
d76bcc1366 | ||
|
|
2987f1c680 | ||
|
|
ab93ee0406 | ||
|
|
69dd933a7d | ||
|
|
5440de0453 | ||
|
|
f2e130c70e | ||
|
|
4c845c26f7 | ||
|
|
a43acca375 | ||
|
|
635520aa85 | ||
|
|
fce9759e93 | ||
|
|
18243cd4b0 | ||
|
|
75a6e51e68 | ||
|
|
312edd4b15 | ||
|
|
be3e67b2c1 | ||
|
|
a5765f81fe | ||
|
|
4bee92abb0 | ||
|
|
d9dcb90745 | ||
|
|
fbd25353d5 | ||
|
|
44c3e73577 | ||
|
|
9cbad1a448 | ||
|
|
3a3ddbc2bc | ||
|
|
63a3c33df9 | ||
|
|
32d7400f7d | ||
|
|
38191f976b | ||
|
|
c380e0e110 | ||
|
|
2199b8eca7 | ||
|
|
88455604a2 | ||
|
|
63eb69091f | ||
|
|
fc668810bd | ||
|
|
2773e3a48a | ||
|
|
18a03f6ccc | ||
|
|
e9348f5512 | ||
|
|
ce65226ea4 | ||
|
|
a3b3079b63 | ||
|
|
22de4bb13b | ||
|
|
eef7bb0cad | ||
|
|
8f1fbbdeef | ||
|
|
f3562e091f | ||
|
|
2001258ef4 | ||
|
|
52a66ea304 | ||
|
|
21ddb1541f | ||
|
|
a3ec6d79b0 | ||
|
|
bb8c315ffc | ||
|
|
f2ae398d3f | ||
|
|
02e33f68b4 | ||
|
|
8c4c9d88da | ||
|
|
49cb98a606 | ||
|
|
bdd18b384b | ||
|
|
32ab716f0d | ||
|
|
fe24529fce | ||
|
|
b59c097bca | ||
|
|
6d6414eb62 | ||
|
|
5b1c20d625 | ||
|
|
af6c8ab935 | ||
|
|
4acb5f924a | ||
|
|
b1b99f1066 | ||
|
|
004ee6f143 | ||
|
|
19455f89d3 | ||
|
|
18d98e3bd0 | ||
|
|
af038b7fc8 | ||
|
|
4a9297dc65 | ||
|
|
49fefb41a6 | ||
|
|
2fa64dfdd0 | ||
|
|
e0f777b9b4 | ||
|
|
dfe3de14d8 | ||
|
|
abe234c7ce | ||
|
|
d7bf67fd0e | ||
|
|
8218996f04 | ||
|
|
ed91e5d607 | ||
|
|
0bf0b0d381 | ||
|
|
d88559f5de | ||
|
|
f398115df3 | ||
|
|
82413fecab | ||
|
|
66352de1eb | ||
|
|
a7730f6e93 | ||
|
|
d5a5b24223 | ||
|
|
472dba2665 | ||
|
|
a5444b11b3 | ||
|
|
5a05a68524 | ||
|
|
36be61bbe1 | ||
|
|
ec6ab983f7 | ||
|
|
69630c2be0 | ||
|
|
8e605dd1d4 | ||
|
|
8bb0cf02ad | ||
|
|
60c054f457 | ||
|
|
5648ee974a | ||
|
|
dc4968f961 | ||
|
|
36d1370720 | ||
|
|
7ead5537f6 | ||
|
|
7fe797d44d | ||
|
|
a1e959420d | ||
|
|
4c99b40257 | ||
|
|
ead0958d18 | ||
|
|
220af8068e | ||
|
|
a1730b2d52 | ||
|
|
1a1d6c0835 | ||
|
|
06cbffdea4 | ||
|
|
4409f37000 | ||
|
|
c45e1faf42 | ||
|
|
732d42f174 | ||
|
|
524fde99f8 | ||
|
|
649246dc6a | ||
|
|
6c7ac25388 | ||
|
|
cb1d6ddeec | ||
|
|
58313889ee | ||
|
|
5ebc62a084 | ||
|
|
fcba384a15 | ||
|
|
3ea95ffa48 | ||
|
|
898fd36eca | ||
|
|
6cd88fb98f | ||
|
|
396de72c5d | ||
|
|
9932d60dee | ||
|
|
1ad93ce1fd | ||
|
|
2041513676 | ||
|
|
3ea1a2bf2f | ||
|
|
2b81ebca8e | ||
|
|
27df97d710 | ||
|
|
a25b7b9b3f | ||
|
|
de9809ca5a | ||
|
|
8504c181c0 | ||
|
|
8a005ffb00 | ||
|
|
80ffa7a577 | ||
|
|
ca8f9a7e4a | ||
|
|
8d19d78297 | ||
|
|
483e5eb85a | ||
|
|
a4b4f491d2 | ||
|
|
08093f509a | ||
|
|
9a96223d12 | ||
|
|
0c2b194444 | ||
|
|
a06055168f | ||
|
|
e04546a9ae | ||
|
|
f6da1a045d | ||
|
|
acd386bb09 | ||
|
|
45225c0ce1 | ||
|
|
0bd884d309 | ||
|
|
aea2ae8d79 | ||
|
|
99ec5ab161 | ||
|
|
4431e7470f | ||
|
|
ed775405c6 | ||
|
|
4aefbe5aa8 | ||
|
|
786c467464 | ||
|
|
50182ef940 | ||
|
|
bf3aa2df09 | ||
|
|
0b744a2599 | ||
|
|
a75b8b40e2 | ||
|
|
2e23c08608 | ||
|
|
178f45ee26 | ||
|
|
7b16edc99c | ||
|
|
f3738a1630 | ||
|
|
34fa65132d | ||
|
|
19fe2d2f03 | ||
|
|
c796af7d07 | ||
|
|
b7251e2be2 | ||
|
|
6efede79c7 | ||
|
|
e58da80f8e | ||
|
|
bd556c33a0 | ||
|
|
878809ecb4 | ||
|
|
ea4966f494 | ||
|
|
f5d9b478d5 | ||
|
|
3d97fab16d | ||
|
|
af2ae11eb1 | ||
|
|
2c1fc30bfb | ||
|
|
5aa4199dfb | ||
|
|
773790c8ec | ||
|
|
c7dec8045e | ||
|
|
5d7d12415c | ||
|
|
8a066163fe | ||
|
|
08e57936fc | ||
|
|
7d474e17fa | ||
|
|
6eac25425c | ||
|
|
c0f1981d6f | ||
|
|
d3571942d7 | ||
|
|
8376b6f0f2 | ||
|
|
490a66d3fe | ||
|
|
66f5f8ab12 | ||
|
|
74ce9f7a86 | ||
|
|
5b2957e7eb | ||
|
|
40eb4ea4ec | ||
|
|
0f41039b99 | ||
|
|
8f76db9e41 | ||
|
|
fc922e996c | ||
|
|
6142d325c8 | ||
|
|
aa21aa686d | ||
|
|
ec2719a572 | ||
|
|
a466126d63 | ||
|
|
568f322a25 | ||
|
|
1b64959e2c |
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Bug 报告(Bug Report)
|
||||
about: 反馈项目中的缺陷或问题
|
||||
title: "[Bug] 简短描述问题"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 🐛 问题描述
|
||||
<!-- 清晰简洁地描述问题是什么 -->
|
||||
|
||||
## 🔍 复现步骤
|
||||
<!-- 详细描述复现问题的步骤 -->
|
||||
1. 打开 '...'
|
||||
2. 点击 '...'
|
||||
3. 滚动到 '...'
|
||||
4. 看到错误
|
||||
|
||||
## 🤔 预期行为
|
||||
<!-- 简要描述预期的正确行为 -->
|
||||
|
||||
## 😯 截图
|
||||
<!-- 如果适用,添加问题的截图 -->
|
||||
|
||||
## 🖥️ 环境信息
|
||||
- 操作系统: [例如 Windows 10]
|
||||
- 浏览器: [例如 Chrome 89]
|
||||
- 项目版本: [例如 1.0.0]
|
||||
- Python版本: [例如 3.9.13]
|
||||
- Jdk版本:[例如 java 21.0.5 2024-10-15 LTS]
|
||||
- Nodejs版本:[例如 v20.14.0]
|
||||
|
||||
## 📋 其他信息
|
||||
<!-- 在此添加关于此问题的任何其他上下文信息 -->
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: 代码优化建议(Code Improvement)
|
||||
about: 提出对现有代码的优化或改进建议
|
||||
title: "[Improvement] 简短描述改进内容"
|
||||
labels: refactor
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 💡 改进描述
|
||||
<!-- 描述需要改进的代码部分以及存在的问题 -->
|
||||
|
||||
## 🌟 改进建议
|
||||
<!-- 提出具体的改进方案或思路 -->
|
||||
|
||||
## 🛠️ 相关代码
|
||||
<!-- 如果可能,提供相关代码片段或链接 -->
|
||||
|
||||
## 📋 其他信息
|
||||
<!-- 在此添加任何其他相关信息 -->
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: 文档改进建议(Documentation Improvement)
|
||||
about: 提出对项目文档的改进或补充建议
|
||||
title: "[Docs] 简短描述改进内容"
|
||||
labels: documentation
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 📚 改进描述
|
||||
<!-- 描述需要改进的文档部分以及存在的问题 -->
|
||||
|
||||
## ✨ 改进建议
|
||||
<!-- 提出具体的改进方案或内容 -->
|
||||
|
||||
## 📋 其他信息
|
||||
<!-- 在此添加任何其他相关信息 -->
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: 功能请求(Feature Request)
|
||||
about: 提出新的功能或改进建议
|
||||
title: "[Feature] 简短描述功能"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 🚀 需求描述
|
||||
<!-- 清晰简洁地描述你希望发生什么 -->
|
||||
|
||||
## 🎯 解决方案
|
||||
<!-- 描述你认为可行的解决方案或实现方式 -->
|
||||
|
||||
## 📝 备选方案
|
||||
<!-- 描述你考虑过的替代方案或功能 -->
|
||||
|
||||
## 📋 其他信息
|
||||
<!-- 在此添加关于需求的任何其他上下文信息 -->
|
||||
+7
-1
@@ -141,13 +141,19 @@ music/
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
*.iml
|
||||
model.pt
|
||||
tmp
|
||||
.history
|
||||
.DS_Store
|
||||
main/xiaozhi-server/data
|
||||
main/xiaozhi-server/config/assets/wakeup_words.*
|
||||
main/manager-web/node_modules
|
||||
.config.yaml
|
||||
.secrets.yaml
|
||||
.private_config.yaml
|
||||
.env.development
|
||||
|
||||
# model files
|
||||
main/xiaozhi-server/models/SenseVoiceSmall/model.pt
|
||||
main/xiaozhi-server/models/sherpa-onnx*
|
||||
my_wakeup_words.mp3
|
||||
main/manager-api/.vscode
|
||||
|
||||
+3
-3
@@ -5,15 +5,15 @@ WORKDIR /app
|
||||
|
||||
COPY main/xiaozhi-server/requirements.txt .
|
||||
|
||||
# 优化apt安装
|
||||
# 安装Python依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 第三阶段:生产镜像
|
||||
# 第二阶段:生产镜像
|
||||
FROM python:3.10-slim
|
||||
|
||||
WORKDIR /opt/xiaozhi-esp32-server
|
||||
|
||||
# 优化apt安装
|
||||
# 安装系统依赖
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends libopus0 ffmpeg && \
|
||||
apt-get clean && \
|
||||
|
||||
@@ -51,12 +51,19 @@
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://www.bilibili.com/video/av114036381327149" target="_blank">
|
||||
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
||||
<picture>
|
||||
<img alt="控制家电开关" src="docs/images/demo5.png" />
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://www.bilibili.com/video/BV1kgA2eYEQ9" target="_blank">
|
||||
<picture>
|
||||
<img alt="成本最低配置" src="docs/images/demo4.png" />
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
@@ -74,13 +81,25 @@
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://www.bilibili.com/video/BV1kgA2eYEQ9" target="_blank">
|
||||
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
|
||||
<picture>
|
||||
<img alt="成本最低配置" src="docs/images/demo4.png" />
|
||||
<img alt="天气插件" src="docs/images/demo8.png" />
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://www.bilibili.com/video/BV178XuYfEpi" target="_blank">
|
||||
<picture>
|
||||
<img alt="IOT指令控制设备" src="docs/images/demo9.png" />
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
|
||||
<picture>
|
||||
<img alt="播报新闻" src="docs/images/demo0.png" />
|
||||
</picture>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -92,9 +111,11 @@
|
||||
- **硬件**:一套兼容 `xiaozhi-esp32`
|
||||
的硬件设备(具体型号请参考 [此处](https://rcnv1t9vps13.feishu.cn/wiki/DdgIw4BUgivWDPkhMj1cGIYCnRf))。
|
||||
|
||||
- **电脑或服务器**:至少 4 核 CPU、8G 内存的电脑。
|
||||
- **电脑或服务器**:建议 4 核 CPU、8G 内存的电脑。如果开启ASR也使用API,可运行在2核CPU、2G内存的服务器中。
|
||||
- **固件编译**:请将本后端服务的接口地址更新至 `xiaozhi-esp32` 项目中,再重新编译`xiaozhi-esp32`固件并烧录到设备上。
|
||||
|
||||

|
||||
|
||||
如果你没有esp32相关的硬件设备,但是非常想体验该项目,可以使用以下的项目让你的电脑、手机模拟成esp32设备。
|
||||
|
||||
- [小智安卓端](https://github.com/TOM88812/xiaozhi-android-client)
|
||||
@@ -141,12 +162,13 @@ server:
|
||||
支持 EdgeTTS(默认)、火山引擎豆包 TTS 等多种 TTS 接口,满足语音合成需求。
|
||||
- **记忆功能**
|
||||
支持超长记忆、本地总结记忆、无记忆三种模式,满足不同场景需求。
|
||||
- **IOT功能**
|
||||
支持管理注册设备IOT功能,支持基于对话上下文语境下的智能物联网控制。
|
||||
|
||||
### 正在开发 🚧
|
||||
|
||||
- 多种心情模式
|
||||
- 智控台webui
|
||||
- iot功能
|
||||
|
||||
想了解具体开发进度,[请点击这里](https://github.com/users/xinnan-tech/projects/3)
|
||||
|
||||
@@ -168,7 +190,6 @@ server:
|
||||
| LLM | FastgptLLM | fastgpt 接口调用 | 免费/消耗 token | 本地化部署,注意配置提示词需在 Fastgpt 控制台设置 |
|
||||
| LLM | GeminiLLM | gemini 接口调用 | 免费 | [点击申请密钥](https://aistudio.google.com/apikey) |
|
||||
| LLM | CozeLLM | coze 接口调用 | 消耗 token | 需提供 bot_id、user_id 及个人令牌 |
|
||||
| LLM | Home Assistant | homeassistant语音助手接口调用 | 免费 | 需提供home assistant令牌 |
|
||||
|
||||
实际上,任何支持 openai 接口调用的 LLM 均可接入使用。
|
||||
|
||||
@@ -184,6 +205,7 @@ server:
|
||||
| TTS | CosyVoiceSiliconflow | 接口调用 | 消耗 token | 需申请硅基流动 API 密钥;输出格式为 wav |
|
||||
| TTS | TTS302AI | 接口调用 | 消耗 token | [点击创建密钥](https://dash.302.ai/apis/list) |
|
||||
| TTS | CozeCnTTS | 接口调用 | 消耗 token | 需提供 Coze API key;输出格式为 wav |
|
||||
| TTS | GizwitsTTS | 接口调用 | 消耗 token | [点击创建密钥](https://agentrouter.gizwitsapi.com) |
|
||||
| TTS | ACGNTTS | 接口调用 | 消耗 token | [联系网站管理员购买密钥](www.ttson.cn) |
|
||||
| TTS | OpenAITTS | 接口调用 | 消耗 token | 境外使用,境外购买 |
|
||||
| TTS | FishSpeech | 接口调用 | 免费/自定义 | 本地启动 TTS 服务;启动方法见配置文件内说明 |
|
||||
@@ -206,16 +228,18 @@ server:
|
||||
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 |
|
||||
|:---:|:---------:|:----:|:----:|:--:|
|
||||
| ASR | FunASR | 本地使用 | 免费 | |
|
||||
| ASR | SherpaASR | 本地使用 | 免费 | |
|
||||
| ASR | DoubaoASR | 接口调用 | 收费 | |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Memory 记忆存储
|
||||
|
||||
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 |
|
||||
|:------:|:---------------:|:----:|:--------:|:--:|
|
||||
| Memory | mem0ai | 接口调用 | 100次/月额度 | |
|
||||
| Memory | mem_local_short | 本地总结 | 免费 | |
|
||||
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 |
|
||||
|:------:|:---------------:|:----:|:---------:|:--:|
|
||||
| Memory | mem0ai | 接口调用 | 1000次/月额度 | |
|
||||
| Memory | mem_local_short | 本地总结 | 免费 | |
|
||||
|
||||
---
|
||||
|
||||
@@ -350,7 +374,41 @@ VAD:
|
||||
|
||||
### 6、我想通过小智控制电灯、空调、远程开关机等操作 💡
|
||||
|
||||
建议:在配置文件中将 `LLM` 设置为 `HomeAssistant`,通过 调用`HomeAssistant`接口实现相关控制。
|
||||
本项目,支持以工具调用的方式控制HomeAssistant设备
|
||||
|
||||
1、首先选择一款支持function call支持的LLM,例如`ChatGLMLLM`。
|
||||
|
||||
2、在配置文件中,将 `selected_module.Intent` 设置为 `function_call`。
|
||||
|
||||
3、登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。
|
||||
|
||||
在配置文件中,配置好你的home assistant的`devices`(被控制的设备)和`api_key`和`base_url`等信息。例如:
|
||||
|
||||
``` yaml
|
||||
plugins
|
||||
home_assistant:
|
||||
devices:
|
||||
- 客厅,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1
|
||||
- 卧室,台灯,switch.iot_cn_831898993_socn1_on_p_2_1
|
||||
base_url: http://你的homeassistant地址:8123
|
||||
api_key: 你的home assistant api访问令牌
|
||||
```
|
||||
|
||||
最后,允许function_call 插件在配置文件中启用`hass_get_state`(必须)、`hass_set_state`(必须)、`hass_play_music`(不想用ha听音乐可以不启动),例如:
|
||||
|
||||
``` yaml
|
||||
Intent:
|
||||
...
|
||||
function_call:
|
||||
type: nointent
|
||||
functions:
|
||||
- change_role
|
||||
- get_weather
|
||||
- get_news
|
||||
- hass_get_state
|
||||
- hass_set_state
|
||||
- hass_play_music
|
||||
```
|
||||
|
||||
### 7、更多问题,可联系我们反馈 💬
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@ In fact, any LLM that supports OpenAI API calls can be integrated.
|
||||
| Type | Platform Name | Usage Method | Pricing Model | Remarks |
|
||||
|:----:|:-------------------:|:------------:|:-------------:|:-------:|
|
||||
| ASR | FunASR | Local | Free | |
|
||||
| ASR | SherpaASR | Local | Free | |
|
||||
| ASR | DoubaoASR | API call | Paid | |
|
||||
|
||||
---
|
||||
|
||||
+4
-2
@@ -1,3 +1,5 @@
|
||||
# 部署方案参考
|
||||

|
||||
# 方式一:docker快速部署
|
||||
|
||||
docker镜像已支持x86架构、arm64架构的CPU,支持在国产操作系统上运行。
|
||||
@@ -239,7 +241,7 @@ pip install -r requirements.txt
|
||||
|
||||
## 4.配置项目文件
|
||||
|
||||
接下里,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
|
||||
接下来,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
|
||||
[跳转到配置项目文件](#配置项目)
|
||||
|
||||
## 5.运行项目
|
||||
@@ -336,4 +338,4 @@ LLM:
|
||||
|
||||
[5、我说话很慢,停顿时小智老是抢话](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
||||
|
||||
[6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
||||
[6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
||||
|
||||
@@ -35,7 +35,7 @@ John,2025.3.11,广州
|
||||
|
||||
### 1、成为普通贡献者
|
||||
|
||||
Fork 项目,,提交 PR,由开发者审核后合入主分支。
|
||||
Fork 项目,提交 PR,由开发者审核后合入主分支。
|
||||
|
||||
### 2、成为开发者
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 289 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 338 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 298 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 576 KiB |
+1
-3
@@ -18,6 +18,4 @@ xiaozhi-esp32-server
|
||||
|
||||
# manager-web 、manager-api接口协议
|
||||
|
||||
[manager前后端接口协议](https://app.apifox.com/invite/project?token=H_8qhgfjUeaAL0wybghgU)
|
||||
|
||||
[前端页面设计图](https://codesign.qq.com/app/s/526108506410828)
|
||||
https://2662r3426b.vicp.fun/xiaozhi-esp32-api/api/v1/doc.html
|
||||
|
||||
@@ -5,12 +5,11 @@ manager-api 该项目基于SpringBoot框架开发。
|
||||
|
||||
开发使用代码编辑器,导入项目时,选择`manager-api`文件夹作为项目目录
|
||||
|
||||
参照[manager前后端接口协议](https://app.apifox.com/invite/project?token=H_8qhgfjUeaAL0wybghgU)开发
|
||||
|
||||
# 开发环境
|
||||
JDK 21
|
||||
Maven 3.8+
|
||||
MySQL 8.0+
|
||||
Redis 5.0+
|
||||
Vue 3.x
|
||||
|
||||
# 创建数据库
|
||||
@@ -43,6 +42,30 @@ spring:
|
||||
password: 123456
|
||||
```
|
||||
|
||||
|
||||
# 连接Redis
|
||||
|
||||
如果还没有Redis,你可以通过docker安装redis
|
||||
|
||||
```
|
||||
docker run --name xiaozhi-esp32-server-redis -d -p 6379:6379 redis
|
||||
```
|
||||
|
||||
# 确认项目Redis连接信息
|
||||
|
||||
在`src/main/resources/application-dev.yml`中配置Redis连接信息
|
||||
|
||||
```
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password:
|
||||
database: 0
|
||||
```
|
||||
|
||||
|
||||
# 测试启动
|
||||
|
||||
本项目为SpringBoot项目,启动方式为:
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>21</java.version>
|
||||
<junit.version>5.12.0</junit.version>
|
||||
<junit.version>5.10.1</junit.version>
|
||||
<druid.version>1.2.20</druid.version>
|
||||
<mybatisplus.version>3.5.5</mybatisplus.version>
|
||||
<hutool.version>5.8.24</hutool.version>
|
||||
@@ -27,7 +27,6 @@
|
||||
<shiro.version>2.0.2</shiro.version>
|
||||
<captcha.version>1.6.2</captcha.version>
|
||||
<guava.version>33.0.0-jre</guava.version>
|
||||
<easyexcel.version>3.3.2</easyexcel.version>
|
||||
<liquibase-core.version>4.20.0</liquibase-core.version>
|
||||
</properties>
|
||||
|
||||
@@ -88,6 +87,36 @@
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-launcher</artifactId>
|
||||
<version>1.10.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-commons</artifactId>
|
||||
<version>1.10.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-engine</artifactId>
|
||||
<version>1.10.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package xiaozhi.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 数据过滤注解
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package xiaozhi.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 操作日志注解
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package xiaozhi.common.aspect;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import xiaozhi.common.annotation.DataFilter;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.interceptor.DataScope;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
import xiaozhi.modules.sys.enums.SuperAdminEnum;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据过滤,切面处理类
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
* Website: https://www.renren.io
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class DataFilterAspect {
|
||||
|
||||
@Pointcut("@annotation(xiaozhi.common.annotation.DataFilter)")
|
||||
public void dataFilterCut() {
|
||||
|
||||
}
|
||||
|
||||
@Before("dataFilterCut()")
|
||||
public void dataFilter(JoinPoint point) {
|
||||
Object params = point.getArgs()[0];
|
||||
if (params != null && params instanceof Map) {
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
|
||||
//如果是超级管理员,则不进行数据过滤
|
||||
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//否则进行数据过滤
|
||||
Map map = (Map) params;
|
||||
String sqlFilter = getSqlFilter(user, point);
|
||||
map.put(Constant.SQL_FILTER, new DataScope(sqlFilter));
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new RenException(ErrorCode.DATA_SCOPE_PARAMS_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据过滤的SQL
|
||||
*/
|
||||
private String getSqlFilter(UserDetail user, JoinPoint point) throws Exception {
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
Method method = point.getTarget().getClass().getDeclaredMethod(signature.getName(), signature.getParameterTypes());
|
||||
DataFilter dataFilter = method.getAnnotation(DataFilter.class);
|
||||
|
||||
//获取表的别名
|
||||
String tableAlias = dataFilter.tableAlias();
|
||||
if (StringUtils.isNotBlank(tableAlias)) {
|
||||
tableAlias += ".";
|
||||
}
|
||||
|
||||
StringBuilder sqlFilter = new StringBuilder();
|
||||
sqlFilter.append(" (");
|
||||
|
||||
//部门ID列表
|
||||
List<Long> deptIdList = user.getDeptIdList();
|
||||
if (CollUtil.isNotEmpty(deptIdList)) {
|
||||
sqlFilter.append(tableAlias).append(dataFilter.deptId());
|
||||
|
||||
sqlFilter.append(" in(").append(StringUtils.join(deptIdList, ",")).append(")");
|
||||
}
|
||||
|
||||
//查询本人数据
|
||||
if (CollUtil.isNotEmpty(deptIdList)) {
|
||||
sqlFilter.append(" or ");
|
||||
}
|
||||
sqlFilter.append(tableAlias).append(dataFilter.userId()).append("=").append(user.getId());
|
||||
|
||||
sqlFilter.append(")");
|
||||
|
||||
return sqlFilter.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
package xiaozhi.common.aspect;
|
||||
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
|
||||
/**
|
||||
* Redis切面处理类
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
@@ -19,7 +20,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class RedisAspect {
|
||||
/**
|
||||
* 是否开启redis缓存 true开启 false关闭
|
||||
* 是否开启redis缓存 true开启 false关闭
|
||||
*/
|
||||
@Value("${renren.redis.open}")
|
||||
private boolean open;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package xiaozhi.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
|
||||
import xiaozhi.common.interceptor.DataFilterInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* mybatis-plus配置
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package xiaozhi.common.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
|
||||
/**
|
||||
* Swagger配置
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
@@ -14,11 +15,60 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi deviceApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("device")
|
||||
.pathsToMatch("/device/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi agentApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("agent")
|
||||
.pathsToMatch("/agent/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi modelApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("models")
|
||||
.pathsToMatch("/models/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi oatApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("ota")
|
||||
.pathsToMatch("/ota/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi timbreApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("timbre")
|
||||
.pathsToMatch("/timbre/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi sysApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("admin")
|
||||
.pathsToMatch("/admin/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi userApi() {
|
||||
String[] paths = {"/**"};
|
||||
return GroupedOpenApi.builder().group("xiaozhi")
|
||||
.pathsToMatch(paths).build();
|
||||
return GroupedOpenApi.builder()
|
||||
.group("user")
|
||||
.pathsToMatch("/user/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -73,10 +73,6 @@ public interface Constant {
|
||||
* 排序方式
|
||||
*/
|
||||
String ORDER = "order";
|
||||
/**
|
||||
* token header
|
||||
*/
|
||||
String TOKEN_HEADER = "token";
|
||||
|
||||
String AUTHORIZATION = "Authorization";
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package xiaozhi.common.convert;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 日期转换
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package xiaozhi.common.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 基础实体类,所有实体都需要继承
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package xiaozhi.common.exception;
|
||||
|
||||
|
||||
import xiaozhi.common.utils.MessageUtils;
|
||||
|
||||
/**
|
||||
@@ -10,7 +9,6 @@ import xiaozhi.common.utils.MessageUtils;
|
||||
*/
|
||||
public class RenException extends RuntimeException {
|
||||
|
||||
|
||||
private int code;
|
||||
private String msg;
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package xiaozhi.common.exception;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import xiaozhi.common.utils.Result;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xiaozhi.common.utils.Result;
|
||||
|
||||
/**
|
||||
* 异常处理器
|
||||
@@ -23,35 +23,34 @@ public class RenExceptionHandler {
|
||||
* 处理自定义异常
|
||||
*/
|
||||
@ExceptionHandler(RenException.class)
|
||||
public Result handleRenException(RenException ex) {
|
||||
Result result = new Result();
|
||||
public Result<Void> handleRenException(RenException ex) {
|
||||
Result<Void> result = new Result<>();
|
||||
result.error(ex.getCode(), ex.getMsg());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ExceptionHandler(DuplicateKeyException.class)
|
||||
public Result handleDuplicateKeyException(DuplicateKeyException ex) {
|
||||
Result result = new Result();
|
||||
public Result<Void> handleDuplicateKeyException(DuplicateKeyException ex) {
|
||||
Result<Void> result = new Result<>();
|
||||
result.error(ErrorCode.DB_RECORD_EXISTS);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ExceptionHandler(UnauthorizedException.class)
|
||||
public Result handleUnauthorizedException(UnauthorizedException ex) {
|
||||
Result result = new Result();
|
||||
public Result<Void> handleUnauthorizedException(UnauthorizedException ex) {
|
||||
Result<Void> result = new Result<>();
|
||||
result.error(ErrorCode.FORBIDDEN);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result handleException(Exception ex) {
|
||||
public Result<Void> handleException(Exception ex) {
|
||||
log.error(ex.getMessage(), ex);
|
||||
|
||||
return new Result().error();
|
||||
return new Result<Void>().error();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
package xiaozhi.common.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
|
||||
/**
|
||||
* 公共字段,自动填充值
|
||||
@@ -22,38 +24,34 @@ public class FieldMetaObjectHandler implements MetaObjectHandler {
|
||||
private final static String UPDATER = "updater";
|
||||
|
||||
private final static String DATA_OPERATION = "dataOperation";
|
||||
private final static String DEPT_ID = "deptId";
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
Date date = new Date();
|
||||
|
||||
//创建者
|
||||
// 创建者
|
||||
strictInsertFill(metaObject, CREATOR, Long.class, user.getId());
|
||||
//创建时间
|
||||
// 创建时间
|
||||
strictInsertFill(metaObject, CREATE_DATE, Date.class, date);
|
||||
|
||||
//创建者所属部门
|
||||
strictInsertFill(metaObject, DEPT_ID, Long.class, user.getDeptId());
|
||||
|
||||
//更新者
|
||||
// 更新者
|
||||
strictInsertFill(metaObject, UPDATER, Long.class, user.getId());
|
||||
//更新时间
|
||||
// 更新时间
|
||||
strictInsertFill(metaObject, UPDATE_DATE, Date.class, date);
|
||||
|
||||
//数据标识
|
||||
// 数据标识
|
||||
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.INSERT.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
//更新者
|
||||
// 更新者
|
||||
strictUpdateFill(metaObject, UPDATER, Long.class, SecurityUser.getUserId());
|
||||
//更新时间
|
||||
// 更新时间
|
||||
strictUpdateFill(metaObject, UPDATE_DATE, Date.class, new Date());
|
||||
|
||||
//数据标识
|
||||
// 数据标识
|
||||
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.UPDATE.getValue());
|
||||
}
|
||||
}
|
||||
+14
-10
@@ -1,8 +1,17 @@
|
||||
package xiaozhi.common.interceptor;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.StringValue;
|
||||
@@ -10,13 +19,6 @@ import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.Select;
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据过滤
|
||||
@@ -25,8 +27,10 @@ import java.util.Map;
|
||||
*/
|
||||
public class DataFilterInterceptor implements InnerInterceptor {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
|
||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
|
||||
ResultHandler resultHandler, BoundSql boundSql) {
|
||||
DataScope scope = getDataScope(parameter);
|
||||
// 不进行数据过滤
|
||||
if (scope == null || StrUtil.isBlank(scope.getSqlFilter())) {
|
||||
@@ -48,7 +52,7 @@ public class DataFilterInterceptor implements InnerInterceptor {
|
||||
// 判断参数里是否有DataScope对象
|
||||
if (parameter instanceof Map) {
|
||||
Map<?, ?> parameterMap = (Map<?, ?>) parameter;
|
||||
for (Map.Entry entry : parameterMap.entrySet()) {
|
||||
for (Map.Entry<?, ?> entry : parameterMap.entrySet()) {
|
||||
if (entry.getValue() != null && entry.getValue() instanceof DataScope) {
|
||||
return (DataScope) entry.getValue();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package xiaozhi.common.page;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分页工具类
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package xiaozhi.common.page;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 令牌信息
|
||||
*
|
||||
@@ -14,7 +14,6 @@ import java.io.Serializable;
|
||||
@Schema(description = "令牌信息")
|
||||
public class TokenDTO implements Serializable {
|
||||
|
||||
|
||||
@Schema(description = "密码")
|
||||
private String token;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package xiaozhi.common.redis;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
@@ -8,6 +7,8 @@ import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
/**
|
||||
* Redis配置
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package xiaozhi.common.redis;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.HashOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.core.HashOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
/**
|
||||
* Redis工具类
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package xiaozhi.common.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
|
||||
/**
|
||||
* 基础服务接口,所有Service接口都要继承
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
@@ -56,7 +56,8 @@ public interface BaseService<T> {
|
||||
* </p>
|
||||
*
|
||||
* @param entity 实体对象
|
||||
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
|
||||
* @param updateWrapper 实体对象封装操作类
|
||||
* {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
|
||||
*/
|
||||
boolean update(T entity, Wrapper<T> updateWrapper);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package xiaozhi.common.service;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
|
||||
/**
|
||||
* CRUD基础服务接口
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
package xiaozhi.common.service.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.apache.ibatis.binding.MapperMethod;
|
||||
import org.apache.ibatis.logging.Log;
|
||||
import org.apache.ibatis.logging.LogFactory;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
@@ -10,22 +23,11 @@ import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
|
||||
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.BaseService;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import org.apache.ibatis.binding.MapperMethod;
|
||||
import org.apache.ibatis.logging.Log;
|
||||
import org.apache.ibatis.logging.LogFactory;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* 基础服务类,所有Service都要继承
|
||||
@@ -45,7 +47,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
* @param isAsc 排序方式
|
||||
*/
|
||||
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
|
||||
//分页参数
|
||||
// 分页参数
|
||||
long curPage = 1;
|
||||
long limit = 10;
|
||||
|
||||
@@ -56,17 +58,17 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
limit = Long.parseLong((String) params.get(Constant.LIMIT));
|
||||
}
|
||||
|
||||
//分页对象
|
||||
// 分页对象
|
||||
Page<T> page = new Page<>(curPage, limit);
|
||||
|
||||
//分页参数
|
||||
// 分页参数
|
||||
params.put(Constant.PAGE, page);
|
||||
|
||||
//排序字段
|
||||
// 排序字段
|
||||
String orderField = (String) params.get(Constant.ORDER_FIELD);
|
||||
String order = (String) params.get(Constant.ORDER);
|
||||
|
||||
//前端字段排序
|
||||
// 前端字段排序
|
||||
if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) {
|
||||
if (Constant.ASC.equalsIgnoreCase(order)) {
|
||||
return page.addOrder(OrderItem.asc(orderField));
|
||||
@@ -75,12 +77,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
}
|
||||
}
|
||||
|
||||
//没有排序字段,则不排序
|
||||
// 没有排序字段,则不排序
|
||||
if (StringUtils.isBlank(defaultOrderField)) {
|
||||
return page;
|
||||
}
|
||||
|
||||
//默认排序
|
||||
// 默认排序
|
||||
if (isAsc) {
|
||||
page.addOrder(OrderItem.asc(defaultOrderField));
|
||||
} else {
|
||||
@@ -90,13 +92,13 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
return page;
|
||||
}
|
||||
|
||||
protected <T> PageData<T> getPageData(List<?> list, long total, Class<T> target) {
|
||||
List<T> targetList = ConvertUtils.sourceToTarget(list, target);
|
||||
protected <D> PageData<D> getPageData(List<?> list, long total, Class<D> target) {
|
||||
List<D> targetList = ConvertUtils.sourceToTarget(list, target);
|
||||
|
||||
return new PageData<>(targetList, total);
|
||||
}
|
||||
|
||||
protected <T> PageData<T> getPageData(IPage page, Class<T> target) {
|
||||
protected <D> PageData<D> getPageData(IPage<?> page, Class<D> target) {
|
||||
return getPageData(page.getRecords(), page.getTotal(), target);
|
||||
}
|
||||
|
||||
@@ -126,10 +128,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
return SqlHelper.retBool(result);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<M> currentMapperClass() {
|
||||
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Class<T> currentModelClass() {
|
||||
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
|
||||
@@ -164,11 +168,11 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
/**
|
||||
* 执行批量操作
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
|
||||
return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateById(T entity) {
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
package xiaozhi.common.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.CrudService;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.CrudService;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
|
||||
/**
|
||||
* CRUD基础服务类
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
* Website: https://www.renren.io
|
||||
*/
|
||||
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T> implements CrudService<T, D> {
|
||||
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T>
|
||||
implements CrudService<T, D> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<D> currentDtoClass() {
|
||||
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
|
||||
}
|
||||
@@ -29,8 +33,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
|
||||
public PageData<D> page(Map<String, Object> params) {
|
||||
IPage<T> page = baseDao.selectPage(
|
||||
getPage(params, null, false),
|
||||
getWrapper(params)
|
||||
);
|
||||
getWrapper(params));
|
||||
|
||||
return getPageData(page, currentDtoClass());
|
||||
}
|
||||
@@ -56,7 +59,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
|
||||
T entity = ConvertUtils.sourceToTarget(dto, currentModelClass());
|
||||
insert(entity);
|
||||
|
||||
//copy主键值到dto
|
||||
// copy主键值到dto
|
||||
BeanUtils.copyProperties(entity, dto);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package xiaozhi.common.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
@@ -14,19 +13,7 @@ import java.util.List;
|
||||
public class UserDetail implements Serializable {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String realName;
|
||||
private String headUrl;
|
||||
private Integer gender;
|
||||
private String email;
|
||||
private String mobile;
|
||||
private Long deptId;
|
||||
private String password;
|
||||
private Integer status;
|
||||
private Integer superAdmin;
|
||||
private String token;
|
||||
/**
|
||||
* 部门数据权限
|
||||
*/
|
||||
private List<Long> deptIdList;
|
||||
|
||||
private Integer status;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 转换工具类
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
@@ -20,7 +21,7 @@ public class ConvertUtils {
|
||||
}
|
||||
T targetObject = null;
|
||||
try {
|
||||
targetObject = target.newInstance();
|
||||
targetObject = target.getDeclaredConstructor().newInstance();
|
||||
BeanUtils.copyProperties(source, targetObject);
|
||||
} catch (Exception e) {
|
||||
log.error("convert error ", e);
|
||||
@@ -34,10 +35,10 @@ public class ConvertUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
List targetList = new ArrayList<>(sourceList.size());
|
||||
List<T> targetList = new ArrayList<>(sourceList.size());
|
||||
try {
|
||||
for (Object source : sourceList) {
|
||||
T targetObject = target.newInstance();
|
||||
T targetObject = target.getDeclaredConstructor().newInstance();
|
||||
BeanUtils.copyProperties(source, targetObject);
|
||||
targetList.add(targetObject);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.DigestUtils;
|
||||
@@ -9,10 +12,9 @@ import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
|
||||
/**
|
||||
* Http
|
||||
@@ -30,13 +32,14 @@ public class HttpContextUtils {
|
||||
return ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
}
|
||||
|
||||
public static String getToken() {
|
||||
HttpServletRequest httpRequest = getHttpServletRequest();
|
||||
String token = httpRequest.getHeader(Constant.TOKEN_HEADER);
|
||||
|
||||
//如果header中不存在token,则从参数中获取token
|
||||
public static String getToken(String authorization) {
|
||||
String token;
|
||||
if (StringUtils.isBlank(authorization) && authorization.contains("Bearer ")) {
|
||||
throw new RenException(ErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
token = authorization.replace("Bearer ", "");
|
||||
if (StringUtils.isBlank(token)) {
|
||||
token = httpRequest.getParameter(Constant.TOKEN_HEADER);
|
||||
throw new RenException(ErrorCode.TOKEN_NOT_EMPTY);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
@@ -68,15 +71,15 @@ public class HttpContextUtils {
|
||||
}
|
||||
|
||||
public static String getLanguage() {
|
||||
//默认语言
|
||||
// 默认语言
|
||||
String defaultLanguage = "zh-CN";
|
||||
//request
|
||||
// request
|
||||
HttpServletRequest request = getHttpServletRequest();
|
||||
if (request == null) {
|
||||
return defaultLanguage;
|
||||
}
|
||||
|
||||
//请求语言
|
||||
// 请求语言
|
||||
defaultLanguage = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE);
|
||||
|
||||
return defaultLanguage;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* IP地址
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* JSON 工具类
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
@@ -59,7 +60,8 @@ public class JsonUtils {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
return objectMapper.readValue(text,
|
||||
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
@@ -42,10 +36,6 @@ public class Result<T> implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean success() {
|
||||
return code == 0;
|
||||
}
|
||||
|
||||
public Result<T> error() {
|
||||
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
|
||||
this.msg = MessageUtils.getMessage(this.code);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 树节点,所有需要实现树节点的,都需要继承该类
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import xiaozhi.common.validator.AssertUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import xiaozhi.common.validator.AssertUtils;
|
||||
|
||||
/**
|
||||
* 树形结构工具类,如:菜单、部门等
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
@@ -17,8 +17,8 @@ public class TreeUtils {
|
||||
/**
|
||||
* 根据pid,构建树节点
|
||||
*/
|
||||
public static <T extends TreeNode> List<T> build(List<T> treeNodes, Long pid) {
|
||||
//pid不能为空
|
||||
public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes, Long pid) {
|
||||
// pid不能为空
|
||||
AssertUtils.isNull(pid, "pid");
|
||||
|
||||
List<T> treeList = new ArrayList<>();
|
||||
@@ -34,7 +34,7 @@ public class TreeUtils {
|
||||
/**
|
||||
* 查找子节点
|
||||
*/
|
||||
private static <T extends TreeNode> T findChildren(List<T> treeNodes, T rootNode) {
|
||||
private static <T extends TreeNode<T>> T findChildren(List<T> treeNodes, T rootNode) {
|
||||
for (T treeNode : treeNodes) {
|
||||
if (rootNode.getId().equals(treeNode.getPid())) {
|
||||
rootNode.getChildren().add(findChildren(treeNodes, treeNode));
|
||||
@@ -46,10 +46,10 @@ public class TreeUtils {
|
||||
/**
|
||||
* 构建树节点
|
||||
*/
|
||||
public static <T extends TreeNode> List<T> build(List<T> treeNodes) {
|
||||
public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes) {
|
||||
List<T> result = new ArrayList<>();
|
||||
|
||||
//list转map
|
||||
// list转map
|
||||
Map<Long, T> nodeMap = new LinkedHashMap<>(treeNodes.size());
|
||||
for (T treeNode : treeNodes) {
|
||||
nodeMap.put(treeNode.getId(), treeNode);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package xiaozhi.common.validator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 校验工具类
|
||||
@@ -73,11 +74,11 @@ public class AssertUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void isMapEmpty(Map map, String... params) {
|
||||
public static void isMapEmpty(Map<?, ?> map, String... params) {
|
||||
isMapEmpty(map, ErrorCode.NOT_NULL, params);
|
||||
}
|
||||
|
||||
public static void isMapEmpty(Map map, Integer code, String... params) {
|
||||
public static void isMapEmpty(Map<?, ?> map, Integer code, String... params) {
|
||||
if (code == null) {
|
||||
throw new RenException(ErrorCode.NOT_NULL, "code");
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package xiaozhi.common.validator;
|
||||
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
|
||||
/**
|
||||
* hibernate-validator校验工具类
|
||||
@@ -35,7 +36,7 @@ public class ValidatorUtils {
|
||||
throws RenException {
|
||||
Locale.setDefault(LocaleContextHolder.getLocale());
|
||||
Validator validator = Validation.byDefaultProvider().configure().messageInterpolator(
|
||||
new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource())))
|
||||
new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource())))
|
||||
.buildValidatorFactory().getValidator();
|
||||
|
||||
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package xiaozhi.common.xss;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* SQL过滤
|
||||
@@ -20,19 +21,20 @@ public class SqlFilter {
|
||||
if (StringUtils.isBlank(str)) {
|
||||
return null;
|
||||
}
|
||||
//去掉'|"|;|\字符
|
||||
// 去掉'|"|;|\字符
|
||||
str = StringUtils.replace(str, "'", "");
|
||||
str = StringUtils.replace(str, "\"", "");
|
||||
str = StringUtils.replace(str, ";", "");
|
||||
str = StringUtils.replace(str, "\\", "");
|
||||
|
||||
//转换成小写
|
||||
// 转换成小写
|
||||
str = str.toLowerCase();
|
||||
|
||||
//非法字符
|
||||
String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"};
|
||||
// 非法字符
|
||||
String[] keywords = { "master", "truncate", "insert", "select", "delete", "update", "declare", "alter",
|
||||
"drop" };
|
||||
|
||||
//判断是否包含非法字符
|
||||
// 判断是否包含非法字符
|
||||
for (String keyword : keywords) {
|
||||
if (str.contains(keyword)) {
|
||||
throw new RenException(ErrorCode.INVALID_SYMBOL);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package xiaozhi.common.xss;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
@@ -8,6 +7,8 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.PathMatcher;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
|
||||
/**
|
||||
* XSS 配置文件
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
package xiaozhi.common.xss;
|
||||
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.util.PathMatcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
/**
|
||||
* XSS过滤
|
||||
@@ -38,7 +44,8 @@ public class XssFilter implements Filter {
|
||||
|
||||
private boolean shouldNotFilter(HttpServletRequest request) {
|
||||
// 放行不过滤的URL
|
||||
return properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath()));
|
||||
return properties.getExcludeUrls().stream()
|
||||
.anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+12
-12
@@ -1,20 +1,20 @@
|
||||
package xiaozhi.common.xss;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
/**
|
||||
* XSS过滤处理
|
||||
@@ -29,18 +29,18 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
//非json类型,直接返回
|
||||
// 非json类型,直接返回
|
||||
if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) {
|
||||
return super.getInputStream();
|
||||
}
|
||||
|
||||
//为空,直接返回
|
||||
// 为空,直接返回
|
||||
String json = IoUtil.readUtf8(super.getInputStream());
|
||||
if (StringUtils.isBlank(json)) {
|
||||
return super.getInputStream();
|
||||
}
|
||||
|
||||
//xss过滤
|
||||
// xss过滤
|
||||
json = xssEncode(json);
|
||||
final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
|
||||
return new ServletInputStream() {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package xiaozhi.common.xss;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* XSS 配置项
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
@@ -22,13 +22,15 @@ public class XssUtils extends Safelist {
|
||||
*/
|
||||
private static Safelist xssWhitelist() {
|
||||
return new Safelist()
|
||||
//支持的标签
|
||||
// 支持的标签
|
||||
.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl",
|
||||
"dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q", "small",
|
||||
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul",
|
||||
"dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q",
|
||||
"small",
|
||||
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u",
|
||||
"ul",
|
||||
"embed", "object", "param", "span")
|
||||
|
||||
//支持的标签属性
|
||||
// 支持的标签属性
|
||||
.addAttributes("a", "href", "class", "style", "target", "rel", "nofollow")
|
||||
.addAttributes("blockquote", "cite")
|
||||
.addAttributes("code", "class", "style")
|
||||
@@ -44,13 +46,16 @@ public class XssUtils extends Safelist {
|
||||
.addAttributes("ul", "type", "style")
|
||||
.addAttributes("pre", "class", "style")
|
||||
.addAttributes("div", "class", "id", "style")
|
||||
.addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen", "allowfullscreen",
|
||||
"quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess", "allownetworking", "type")
|
||||
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid", "codebase")
|
||||
.addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen",
|
||||
"allowfullscreen",
|
||||
"quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess",
|
||||
"allownetworking", "type")
|
||||
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid",
|
||||
"codebase")
|
||||
.addAttributes("param", "name", "value")
|
||||
.addAttributes("span", "class", "style")
|
||||
|
||||
//标签属性对应的协议
|
||||
// 标签属性对应的协议
|
||||
.addProtocols("a", "href", "ftp", "http", "https", "mailto")
|
||||
.addProtocols("img", "src", "http", "https")
|
||||
.addProtocols("blockquote", "cite", "http", "https")
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package xiaozhi.modules.agent.controller;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||
import xiaozhi.modules.agent.service.AgentService;
|
||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
|
||||
@Tag(name = "智能体管理")
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/agent")
|
||||
public class AgentController {
|
||||
private final AgentService agentService;
|
||||
private final AgentTemplateService agentTemplateService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获取用户智能体列表")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<List<AgentEntity>> getUserAgents() {
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
List<AgentEntity> agents = agentService.getUserAgents(user.getId());
|
||||
return new Result<List<AgentEntity>>().ok(agents);
|
||||
}
|
||||
|
||||
@GetMapping("/all")
|
||||
@Operation(summary = "智能体列表(管理员)")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
@Parameters({
|
||||
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
||||
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
||||
})
|
||||
public Result<PageData<AgentEntity>> adminAgentList(
|
||||
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||
PageData<AgentEntity> page = agentService.adminAgentList(params);
|
||||
return new Result<PageData<AgentEntity>>().ok(page);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取智能体详情")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<AgentEntity> getAgentById(@PathVariable("id") String id) {
|
||||
AgentEntity agent = agentService.getAgentById(id);
|
||||
return new Result<AgentEntity>().ok(agent);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建智能体")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> save(@RequestBody @Valid AgentCreateDTO dto) {
|
||||
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
|
||||
|
||||
// 设置用户ID和创建者信息
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
entity.setUserId(user.getId());
|
||||
entity.setCreator(user.getId());
|
||||
entity.setCreatedAt(new Date());
|
||||
|
||||
// ID、智能体编码和排序会在Service层自动生成
|
||||
agentService.insert(entity);
|
||||
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新智能体")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
|
||||
// 先查询现有实体
|
||||
AgentEntity existingEntity = agentService.getAgentById(id);
|
||||
if (existingEntity == null) {
|
||||
return new Result<Void>().error("智能体不存在");
|
||||
}
|
||||
|
||||
// 只更新提供的非空字段
|
||||
if (dto.getAgentName() != null) {
|
||||
existingEntity.setAgentName(dto.getAgentName());
|
||||
}
|
||||
if (dto.getAgentCode() != null) {
|
||||
existingEntity.setAgentCode(dto.getAgentCode());
|
||||
}
|
||||
if (dto.getAsrModelId() != null) {
|
||||
existingEntity.setAsrModelId(dto.getAsrModelId());
|
||||
}
|
||||
if (dto.getVadModelId() != null) {
|
||||
existingEntity.setVadModelId(dto.getVadModelId());
|
||||
}
|
||||
if (dto.getLlmModelId() != null) {
|
||||
existingEntity.setLlmModelId(dto.getLlmModelId());
|
||||
}
|
||||
if (dto.getTtsModelId() != null) {
|
||||
existingEntity.setTtsModelId(dto.getTtsModelId());
|
||||
}
|
||||
if (dto.getTtsVoiceId() != null) {
|
||||
existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
|
||||
}
|
||||
if (dto.getMemModelId() != null) {
|
||||
existingEntity.setMemModelId(dto.getMemModelId());
|
||||
}
|
||||
if (dto.getIntentModelId() != null) {
|
||||
existingEntity.setIntentModelId(dto.getIntentModelId());
|
||||
}
|
||||
if (dto.getSystemPrompt() != null) {
|
||||
existingEntity.setSystemPrompt(dto.getSystemPrompt());
|
||||
}
|
||||
if (dto.getLangCode() != null) {
|
||||
existingEntity.setLangCode(dto.getLangCode());
|
||||
}
|
||||
if (dto.getLanguage() != null) {
|
||||
existingEntity.setLanguage(dto.getLanguage());
|
||||
}
|
||||
if (dto.getSort() != null) {
|
||||
existingEntity.setSort(dto.getSort());
|
||||
}
|
||||
|
||||
// 设置更新者信息
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
existingEntity.setUpdater(user.getId());
|
||||
existingEntity.setUpdatedAt(new Date());
|
||||
|
||||
agentService.updateById(existingEntity);
|
||||
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除智能体")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> delete(@PathVariable String id) {
|
||||
agentService.deleteById(id);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@GetMapping("/template")
|
||||
@Operation(summary = "智能体模板模板列表")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<List<AgentTemplateEntity>> templateList() {
|
||||
List<AgentTemplateEntity> list = agentTemplateService
|
||||
.list(new QueryWrapper<AgentTemplateEntity>().orderByAsc("sort"));
|
||||
return new Result<List<AgentTemplateEntity>>().ok(list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package xiaozhi.modules.agent.dao;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import xiaozhi.common.dao.BaseDao;
|
||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||
|
||||
@Mapper
|
||||
public interface AgentDao extends BaseDao<AgentEntity> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package xiaozhi.modules.agent.dao;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||
|
||||
/**
|
||||
* @author chenerlei
|
||||
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Mapper
|
||||
* @createDate 2025-03-22 11:48:18
|
||||
*/
|
||||
@Mapper
|
||||
public interface AgentTemplateDao extends BaseMapper<AgentTemplateEntity> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package xiaozhi.modules.agent.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 智能体创建DTO
|
||||
* 专用于新增智能体,不包含id、agentCode和sort字段,这些字段由系统自动生成/设置默认值
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "智能体创建对象")
|
||||
public class AgentCreateDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "智能体名称", example = "客服助手")
|
||||
@NotBlank(message = "智能体名称不能为空")
|
||||
private String agentName;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package xiaozhi.modules.agent.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 智能体更新DTO
|
||||
* 专用于更新智能体,id字段是必需的,用于标识要更新的智能体
|
||||
* 其他字段均为非必填,只更新提供的字段
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "智能体更新对象")
|
||||
public class AgentUpdateDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "智能体编码", example = "AGT_1234567890", required = false)
|
||||
private String agentCode;
|
||||
|
||||
@Schema(description = "智能体名称", example = "客服助手", required = false)
|
||||
private String agentName;
|
||||
|
||||
@Schema(description = "语音识别模型标识", example = "asr_model_02", required = false)
|
||||
private String asrModelId;
|
||||
|
||||
@Schema(description = "语音活动检测标识", example = "vad_model_02", required = false)
|
||||
private String vadModelId;
|
||||
|
||||
@Schema(description = "大语言模型标识", example = "llm_model_02", required = false)
|
||||
private String llmModelId;
|
||||
|
||||
@Schema(description = "语音合成模型标识", example = "tts_model_02", required = false)
|
||||
private String ttsModelId;
|
||||
|
||||
@Schema(description = "音色标识", example = "voice_02", required = false)
|
||||
private String ttsVoiceId;
|
||||
|
||||
@Schema(description = "记忆模型标识", example = "mem_model_02", required = false)
|
||||
private String memModelId;
|
||||
|
||||
@Schema(description = "意图模型标识", example = "intent_model_02", required = false)
|
||||
private String intentModelId;
|
||||
|
||||
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false)
|
||||
private String systemPrompt;
|
||||
|
||||
@Schema(description = "语言编码", example = "zh_CN", required = false)
|
||||
private String langCode;
|
||||
|
||||
@Schema(description = "交互语种", example = "中文", required = false)
|
||||
private String language;
|
||||
|
||||
@Schema(description = "排序", example = "1", required = false)
|
||||
private Integer sort;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package xiaozhi.modules.agent.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("ai_agent")
|
||||
@Schema(description = "智能体信息")
|
||||
public class AgentEntity {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
@Schema(description = "智能体唯一标识")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "所属用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "智能体编码")
|
||||
private String agentCode;
|
||||
|
||||
@Schema(description = "智能体名称")
|
||||
private String agentName;
|
||||
|
||||
@Schema(description = "语音识别模型标识")
|
||||
private String asrModelId;
|
||||
|
||||
@Schema(description = "语音活动检测标识")
|
||||
private String vadModelId;
|
||||
|
||||
@Schema(description = "大语言模型标识")
|
||||
private String llmModelId;
|
||||
|
||||
@Schema(description = "语音合成模型标识")
|
||||
private String ttsModelId;
|
||||
|
||||
@Schema(description = "音色标识")
|
||||
private String ttsVoiceId;
|
||||
|
||||
@Schema(description = "记忆模型标识")
|
||||
private String memModelId;
|
||||
|
||||
@Schema(description = "意图模型标识")
|
||||
private String intentModelId;
|
||||
|
||||
@Schema(description = "角色设定参数")
|
||||
private String systemPrompt;
|
||||
|
||||
@Schema(description = "语言编码")
|
||||
private String langCode;
|
||||
|
||||
@Schema(description = "交互语种")
|
||||
private String language;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
private Long creator;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createdAt;
|
||||
|
||||
@Schema(description = "更新者")
|
||||
private Long updater;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private Date updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package xiaozhi.modules.agent.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 智能体配置模板表
|
||||
*
|
||||
* @TableName ai_agent_template
|
||||
*/
|
||||
@TableName(value = "ai_agent_template")
|
||||
@Data
|
||||
public class AgentTemplateEntity implements Serializable {
|
||||
/**
|
||||
* 智能体唯一标识
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 智能体编码
|
||||
*/
|
||||
private String agentCode;
|
||||
|
||||
/**
|
||||
* 智能体名称
|
||||
*/
|
||||
private String agentName;
|
||||
|
||||
/**
|
||||
* 语音识别模型标识
|
||||
*/
|
||||
private String asrModelId;
|
||||
|
||||
/**
|
||||
* 语音活动检测标识
|
||||
*/
|
||||
private String vadModelId;
|
||||
|
||||
/**
|
||||
* 大语言模型标识
|
||||
*/
|
||||
private String llmModelId;
|
||||
|
||||
/**
|
||||
* 语音合成模型标识
|
||||
*/
|
||||
private String ttsModelId;
|
||||
|
||||
/**
|
||||
* 音色标识
|
||||
*/
|
||||
private String ttsVoiceId;
|
||||
|
||||
/**
|
||||
* 记忆模型标识
|
||||
*/
|
||||
private String memModelId;
|
||||
|
||||
/**
|
||||
* 意图模型标识
|
||||
*/
|
||||
private String intentModelId;
|
||||
|
||||
/**
|
||||
* 角色设定参数
|
||||
*/
|
||||
private String systemPrompt;
|
||||
|
||||
/**
|
||||
* 语言编码
|
||||
*/
|
||||
private String langCode;
|
||||
|
||||
/**
|
||||
* 交互语种
|
||||
*/
|
||||
private String language;
|
||||
|
||||
/**
|
||||
* 排序权重
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 创建者 ID
|
||||
*/
|
||||
private Long creator;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createdAt;
|
||||
|
||||
/**
|
||||
* 更新者 ID
|
||||
*/
|
||||
private Long updater;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updatedAt;
|
||||
|
||||
@TableField(exist = false)
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package xiaozhi.modules.agent.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.BaseService;
|
||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||
|
||||
public interface AgentService extends BaseService<AgentEntity> {
|
||||
/**
|
||||
* 根据用户ID获取智能体列表
|
||||
*/
|
||||
List<AgentEntity> getUserAgents(Long userId);
|
||||
|
||||
/**
|
||||
* 管理员获取所有智能体列表(分页)
|
||||
*/
|
||||
PageData<AgentEntity> adminAgentList(Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 获取智能体详情
|
||||
*/
|
||||
AgentEntity getAgentById(String id);
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package xiaozhi.modules.agent.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||
|
||||
/**
|
||||
* @author chenerlei
|
||||
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service
|
||||
* @createDate 2025-03-22 11:48:18
|
||||
*/
|
||||
public interface AgentTemplateService extends IService<AgentTemplateEntity> {
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package xiaozhi.modules.agent.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.modules.agent.dao.AgentDao;
|
||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||
import xiaozhi.modules.agent.service.AgentService;
|
||||
|
||||
@Service
|
||||
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
|
||||
private final AgentDao agentDao;
|
||||
|
||||
public AgentServiceImpl(AgentDao agentDao) {
|
||||
this.agentDao = agentDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AgentEntity> getUserAgents(Long userId) {
|
||||
QueryWrapper<AgentEntity> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("user_id", userId);
|
||||
return agentDao.selectList(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
||||
IPage<AgentEntity> page = agentDao.selectPage(
|
||||
getPage(params, "sort", true),
|
||||
new QueryWrapper<>());
|
||||
return new PageData<>(page.getRecords(), page.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentEntity getAgentById(String id) {
|
||||
return agentDao.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean insert(AgentEntity entity) {
|
||||
// 如果ID为空,自动生成一个UUID作为ID
|
||||
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
|
||||
entity.setId(UUID.randomUUID().toString().replace("-", ""));
|
||||
}
|
||||
|
||||
// 如果智能体编码为空,自动生成一个带前缀的编码
|
||||
if (entity.getAgentCode() == null || entity.getAgentCode().trim().isEmpty()) {
|
||||
entity.setAgentCode("AGT_" + System.currentTimeMillis());
|
||||
}
|
||||
|
||||
// 如果排序字段为空,设置默认值0
|
||||
if (entity.getSort() == null) {
|
||||
entity.setSort(0);
|
||||
}
|
||||
|
||||
return super.insert(entity);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package xiaozhi.modules.agent.service.impl;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
import xiaozhi.modules.agent.dao.AgentTemplateDao;
|
||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||
|
||||
/**
|
||||
* @author chenerlei
|
||||
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service实现
|
||||
* @createDate 2025-03-22 11:48:18
|
||||
*/
|
||||
@Service
|
||||
public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateDao, AgentTemplateEntity>
|
||||
implements AgentTemplateService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package xiaozhi.modules.agent.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class AgentTemplateVO extends AgentTemplateEntity {
|
||||
// 角色音色
|
||||
private String ttsModelName;
|
||||
|
||||
// 角色模型
|
||||
private String llmModelName;
|
||||
}
|
||||
+29
-43
@@ -1,83 +1,69 @@
|
||||
package xiaozhi.modules.device.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "设备管理")
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/device")
|
||||
@Tag(name = "设备管理")
|
||||
|
||||
public class DeviceController {
|
||||
private final DeviceService deviceService;
|
||||
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
|
||||
@PostMapping("/bind/{deviceCode}")
|
||||
@PostMapping("/bind/{agentId}/{deviceCode}")
|
||||
@Operation(summary = "绑定设备")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<DeviceEntity> bindDevice(@PathVariable String deviceCode) {
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
|
||||
String deviceHeaders = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode));
|
||||
if (StringUtils.isBlank(deviceHeaders)) {
|
||||
return new Result<DeviceEntity>().error(ErrorCode.DEVICE_CAPTCHA_ERROR);
|
||||
public Result<Void> bindDevice(@PathVariable String agentId, @PathVariable String deviceCode) {
|
||||
String macAddress = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode));
|
||||
if (StringUtils.isBlank(macAddress)) {
|
||||
return new Result<Void>().error(ErrorCode.DEVICE_CAPTCHA_ERROR);
|
||||
}
|
||||
DeviceHeaderDTO deviceHeader = JsonUtils.parseObject(deviceHeaders.getBytes(), DeviceHeaderDTO.class);
|
||||
DeviceEntity device = deviceService.bindDevice(user.getId(), deviceHeader);
|
||||
return new Result<DeviceEntity>().ok(device);
|
||||
Long user = SecurityUser.getUser().getId();
|
||||
DeviceBindDTO deviceBindDTO = new DeviceBindDTO(macAddress, user, agentId);
|
||||
deviceService.bindDevice(deviceBindDTO);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@GetMapping("/bind")
|
||||
@GetMapping("/bind/{agentId}")
|
||||
@Operation(summary = "获取已绑定设备")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<List<DeviceEntity>> getUserDevices() {
|
||||
public Result<List<DeviceEntity>> getUserDevices(@PathVariable String agentId) {
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
List<DeviceEntity> devices = deviceService.getUserDevices(user.getId());
|
||||
List<DeviceEntity> devices = deviceService.getUserDevices(user.getId(), agentId);
|
||||
return new Result<List<DeviceEntity>>().ok(devices);
|
||||
}
|
||||
|
||||
@PostMapping("/unbind")
|
||||
@Operation(summary = "解绑设备")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) {
|
||||
public Result<Void> unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) {
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId());
|
||||
return new Result();
|
||||
return new Result<Void>();
|
||||
}
|
||||
|
||||
@GetMapping("/all")
|
||||
@Operation(summary = "设备列表(管理员)")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
@Parameters({
|
||||
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
||||
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
||||
})
|
||||
public Result<PageData<DeviceEntity>> adminDeviceList(
|
||||
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||
PageData<DeviceEntity> page = deviceService.adminDeviceList(params);
|
||||
return new Result<PageData<DeviceEntity>>().ok(page);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package xiaozhi.modules.device.controller;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.device.utils.NetworkUtil;
|
||||
|
||||
@Tag(name = "设备管理", description = "OTA 相关接口")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/ota")
|
||||
public class OTAController {
|
||||
private final DeviceService deviceService;
|
||||
|
||||
@Operation(summary = "检查 OTA 版本和设备激活状态")
|
||||
@PostMapping
|
||||
public ResponseEntity<String> checkOTAVersion(
|
||||
@RequestBody DeviceReportReqDTO deviceReportReqDTO,
|
||||
|
||||
@Parameter(name = "Device-Id", description = "设备唯一标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Device-Id") String deviceId,
|
||||
|
||||
@Parameter(name = "Client-Id", description = "客户端标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Client-Id") String clientId) {
|
||||
if (StringUtils.isAnyBlank(deviceId, clientId)) {
|
||||
return createResponse(DeviceReportRespDTO.createError("Device ID is required"));
|
||||
}
|
||||
String macAddress = deviceReportReqDTO.getMacAddress();
|
||||
boolean macAddressValid = NetworkUtil.isMacAddressValid(macAddress);
|
||||
// 设备Id和Mac地址应是一致的, 并且必须需要application字段
|
||||
if (!deviceId.equals(macAddress) || !macAddressValid || deviceReportReqDTO.getApplication() == null) {
|
||||
return createResponse(DeviceReportRespDTO.createError("Invalid OTA request"));
|
||||
}
|
||||
return createResponse(deviceService.checkDeviceActive(macAddress, deviceId, clientId, deviceReportReqDTO));
|
||||
}
|
||||
|
||||
@Operation(summary = "设备激活")
|
||||
@GetMapping("/activation")
|
||||
public Result<Boolean> deviceActivation(@RequestParam String code) {
|
||||
return new Result<Boolean>().ok(deviceService.deviceActivation(code));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private ResponseEntity<String> createResponse(DeviceReportRespDTO deviceReportRespDTO) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
String json = objectMapper.writeValueAsString(deviceReportRespDTO);
|
||||
byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.contentLength(jsonBytes.length)
|
||||
.body(json);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package xiaozhi.modules.device.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
|
||||
@Mapper
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package xiaozhi.modules.device.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 设备绑定的DTO
|
||||
*
|
||||
* @author zjy
|
||||
* @since 2025-3-28
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "设备连接头信息")
|
||||
public class DeviceBindDTO {
|
||||
|
||||
@Schema(description = "mac地址")
|
||||
private String macAddress;
|
||||
|
||||
@Schema(description = "所属用户id")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "智能体id")
|
||||
private String agentId;
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package xiaozhi.modules.device.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "设备连接头信息")
|
||||
public class DeviceHeaderDTO {
|
||||
|
||||
@Schema(description = "设备ID")
|
||||
private String deviceId;
|
||||
|
||||
@Schema(description = "协议版本号")
|
||||
private Long protocolVersion;
|
||||
|
||||
@Schema(description = "认证信息")
|
||||
private String authorization;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package xiaozhi.modules.device.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@Schema(description = "设备固件信息上报求请求体")
|
||||
public class DeviceReportReqDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
// region 实体属性
|
||||
@Schema(description = "板子固件版本号")
|
||||
private Integer version;
|
||||
|
||||
@Schema(description = "闪存大小(单位:字节)")
|
||||
@JsonProperty("flash_size")
|
||||
private Integer flashSize;
|
||||
|
||||
@Schema(description = "最小空闲堆内存(字节)")
|
||||
@JsonProperty("minimum_free_heap_size")
|
||||
private Integer minimumFreeHeapSize;
|
||||
|
||||
@Schema(description = "设备 MAC 地址")
|
||||
@JsonProperty("mac_address")
|
||||
private String macAddress;
|
||||
|
||||
@Schema(description = "设备唯一标识 UUID")
|
||||
private String uuid;
|
||||
|
||||
@Schema(description = "芯片型号名称")
|
||||
@JsonProperty("chip_model_name")
|
||||
private String chipModelName;
|
||||
|
||||
@Schema(description = "芯片详细信息")
|
||||
@JsonProperty("chip_info")
|
||||
private ChipInfo chipInfo;
|
||||
|
||||
@Schema(description = "应用程序信息")
|
||||
private Application application;
|
||||
|
||||
@Schema(description = "分区表列表")
|
||||
@JsonProperty("partition_table")
|
||||
private List<Partition> partitionTable;
|
||||
|
||||
@Schema(description = "当前运行的 OTA 分区信息")
|
||||
private OtaInfo ota;
|
||||
|
||||
@Schema(description = "板子配置信息")
|
||||
private BoardInfo board;
|
||||
|
||||
// endregion
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(description = "芯片信息")
|
||||
public static class ChipInfo {
|
||||
@Schema(description = "芯片模型代码")
|
||||
private Integer model;
|
||||
|
||||
@Schema(description = "核心数")
|
||||
private Integer cores;
|
||||
|
||||
@Schema(description = "硬件修订版本")
|
||||
private Integer revision;
|
||||
|
||||
@Schema(description = "芯片功能标志位")
|
||||
private Integer features;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(description = "板子编译信息")
|
||||
public static class Application {
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "应用版本号")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "编译时间(UTC ISO格式)")
|
||||
@JsonProperty("compile_time")
|
||||
private String compileTime;
|
||||
|
||||
@Schema(description = "ESP-IDF 版本号")
|
||||
@JsonProperty("idf_version")
|
||||
private String idfVersion;
|
||||
|
||||
@Schema(description = "ELF 文件 SHA256 校验")
|
||||
@JsonProperty("elf_sha256")
|
||||
private String elfSha256;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(description = "分区信息")
|
||||
public static class Partition {
|
||||
@Schema(description = "分区标签名")
|
||||
private String label;
|
||||
|
||||
@Schema(description = "分区类型")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "子类型")
|
||||
private Integer subtype;
|
||||
|
||||
@Schema(description = "起始地址")
|
||||
private Integer address;
|
||||
|
||||
@Schema(description = "分区大小")
|
||||
private Integer size;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(description = "OTA信息")
|
||||
public static class OtaInfo {
|
||||
@Schema(description = "当前OTA标签")
|
||||
private String label;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(description = "板子连接和网络信息")
|
||||
public static class BoardInfo {
|
||||
@Schema(description = "板子类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "连接的 Wi-Fi SSID")
|
||||
private String ssid;
|
||||
|
||||
@Schema(description = "Wi-Fi 信号强度(RSSI)")
|
||||
private Integer rssi;
|
||||
|
||||
@Schema(description = "Wi-Fi 信道")
|
||||
private Integer channel;
|
||||
|
||||
@Schema(description = "IP 地址")
|
||||
private String ip;
|
||||
|
||||
@Schema(description = "MAC 地址")
|
||||
private String mac;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package xiaozhi.modules.device.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Data
|
||||
@Schema(description = "设备OTA检测版本返回体,包含激活码要求")
|
||||
public class DeviceReportRespDTO {
|
||||
@Schema(description = "服务器时间")
|
||||
private ServerTime serverTime;
|
||||
|
||||
@Schema(description = "激活码")
|
||||
private Activation activation;
|
||||
|
||||
@Schema(description = "错误信息")
|
||||
private String error;
|
||||
|
||||
@Schema(description = "固件版本信息")
|
||||
private Firmware firmware;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Firmware {
|
||||
@Schema(description = "版本号")
|
||||
private String version;
|
||||
@Schema(description = "下载地址")
|
||||
private String url;
|
||||
}
|
||||
|
||||
public static DeviceReportRespDTO createError(String message) {
|
||||
DeviceReportRespDTO resp = new DeviceReportRespDTO();
|
||||
resp.setError(message);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public static class Activation {
|
||||
@Schema(description = "激活码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "激活码信息: 激活地址")
|
||||
private String message;
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class ServerTime {
|
||||
@Schema(description = "时间戳")
|
||||
private Long timestamp;
|
||||
|
||||
@Schema(description = "时区")
|
||||
private String timeZone;
|
||||
|
||||
@Schema(description = "时区偏移量,单位为分钟")
|
||||
private Integer timezoneOffset;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package xiaozhi.modules.device.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 设备解绑表单
|
||||
*/
|
||||
@@ -14,6 +15,6 @@ public class DeviceUnBindDTO implements Serializable {
|
||||
|
||||
@Schema(description = "设备ID")
|
||||
@NotBlank(message = "设备ID不能为空")
|
||||
private Long deviceId;
|
||||
private String deviceId;
|
||||
|
||||
}
|
||||
@@ -1,17 +1,26 @@
|
||||
package xiaozhi.modules.device.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("ai_device")
|
||||
@Schema(description = "设备信息")
|
||||
public class DeviceEntity {
|
||||
@Schema(description = "设备ID")
|
||||
private Long id;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
@Schema(description = "ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "关联用户ID")
|
||||
private Long userId;
|
||||
@@ -40,15 +49,19 @@ public class DeviceEntity {
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
private Long creator;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createDate;
|
||||
|
||||
@Schema(description = "更新者")
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Long updater;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Date updateDate;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long creator;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createDate;
|
||||
}
|
||||
@@ -1,18 +1,42 @@
|
||||
package xiaozhi.modules.device.service;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
|
||||
import java.util.List;
|
||||
|
||||
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface DeviceService {
|
||||
DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader);
|
||||
|
||||
List<DeviceEntity> getUserDevices(Long userId);
|
||||
|
||||
void unbindDevice(Long userId, Long deviceId);
|
||||
|
||||
PageData<DeviceEntity> adminDeviceList(Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 根据Mac地址获取设备信息
|
||||
*/
|
||||
DeviceEntity getDeviceById(String macAddress);
|
||||
|
||||
/**
|
||||
* 检查设备是否激活
|
||||
*/
|
||||
DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId,
|
||||
DeviceReportReqDTO deviceReport);
|
||||
|
||||
/**
|
||||
* 绑定设备
|
||||
*/
|
||||
DeviceEntity bindDevice(DeviceBindDTO deviceHeader);
|
||||
|
||||
/**
|
||||
* 获取用户设备列表
|
||||
*/
|
||||
List<DeviceEntity> getUserDevices(Long userId, String agentId);
|
||||
|
||||
/**
|
||||
* 解绑设备
|
||||
*/
|
||||
void unbindDevice(Long userId, String deviceId);
|
||||
|
||||
/**
|
||||
* 设备激活
|
||||
*/
|
||||
Boolean deviceActivation(String activationCode);
|
||||
}
|
||||
+181
-30
@@ -1,57 +1,208 @@
|
||||
package xiaozhi.modules.device.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springframework.stereotype.Service;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.modules.device.dao.DeviceDao;
|
||||
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.modules.device.dao.DeviceDao;
|
||||
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
|
||||
@Service
|
||||
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
|
||||
|
||||
private final DeviceDao deviceDao;
|
||||
|
||||
private final String frontedUrl;
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
// 添加构造函数来初始化 deviceMapper
|
||||
public DeviceServiceImpl(DeviceDao deviceDao) {
|
||||
public DeviceServiceImpl(DeviceDao deviceDao,
|
||||
@Value("${app.fronted-url:http://localhost:8001}") String frontedUrl,
|
||||
RedisTemplate<String, Object> redisTemplate) {
|
||||
this.deviceDao = deviceDao;
|
||||
this.frontedUrl = frontedUrl;
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader) {
|
||||
DeviceEntity device = new DeviceEntity();
|
||||
device.setUserId(userId);
|
||||
device.setMacAddress(deviceHeader.getDeviceId());
|
||||
device.setCreateDate(new Date());
|
||||
deviceDao.insert(device);
|
||||
return device;
|
||||
public DeviceEntity getDeviceById(String deviceId) {
|
||||
LambdaQueryWrapper<DeviceEntity> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(DeviceEntity::getId, deviceId);
|
||||
return deviceDao.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceEntity> getUserDevices(Long userId) {
|
||||
public Boolean deviceActivation(String activationCode) {
|
||||
if (StringUtils.isBlank(activationCode)) {
|
||||
throw new RenException("激活码不能为空");
|
||||
}
|
||||
String deviceKey = "ota:activation:code:" + activationCode;
|
||||
Object cacheDeviceId = redisTemplate.opsForValue().get(deviceKey);
|
||||
if (cacheDeviceId == null) {
|
||||
throw new RenException("激活码错误");
|
||||
}
|
||||
String deviceId = (String) cacheDeviceId;
|
||||
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
||||
String cacheDeviceKey = String.format("ota:activation:data:%s", safeDeviceId);
|
||||
Map<Object, Object> cacheMap = redisTemplate.opsForHash().entries(cacheDeviceKey);
|
||||
if (cacheMap == null) {
|
||||
throw new RenException("激活码错误");
|
||||
}
|
||||
String cachedCode = (String) cacheMap.get("activation_code");
|
||||
if (!activationCode.equals(cachedCode)) {
|
||||
throw new RenException("激活码错误");
|
||||
}
|
||||
// 检查设备有没有被激活
|
||||
if (selectById(deviceId) != null) {
|
||||
throw new RenException("设备已激活");
|
||||
}
|
||||
|
||||
String macAddress = (String) cacheMap.get("mac_address");
|
||||
String board = (String) cacheMap.get("board");
|
||||
String appVersion = (String) cacheMap.get("app_version");
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
if (user.getId() == null) {
|
||||
throw new RenException("用户未登录");
|
||||
}
|
||||
|
||||
Date currentTime = new Date();
|
||||
DeviceEntity deviceEntity = new DeviceEntity();
|
||||
deviceEntity.setId(deviceId);
|
||||
deviceEntity.setBoard(board);
|
||||
deviceEntity.setAppVersion(appVersion);
|
||||
deviceEntity.setMacAddress(macAddress);
|
||||
deviceEntity.setUserId(user.getId());
|
||||
deviceEntity.setCreator(user.getId());
|
||||
deviceEntity.setCreateDate(currentTime);
|
||||
deviceEntity.setUpdater(user.getId());
|
||||
deviceEntity.setUpdateDate(currentTime);
|
||||
deviceEntity.setLastConnectedAt(currentTime);
|
||||
deviceDao.insert(deviceEntity);
|
||||
|
||||
// 清理redis缓存
|
||||
redisTemplate.delete(cacheDeviceKey);
|
||||
redisTemplate.delete(deviceKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId,
|
||||
DeviceReportReqDTO deviceReport) {
|
||||
DeviceReportRespDTO response = new DeviceReportRespDTO();
|
||||
response.setServerTime(buildServerTime());
|
||||
// todo: 此处是固件信息,目前是针对固件上传上来的版本号再返回回去
|
||||
// 在未来开发了固件更新功能,需要更换此处代码,
|
||||
// 或写定时任务定期请求虾哥的OTA,获取最新的版本讯息保存到服务内
|
||||
DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware();
|
||||
firmware.setVersion(deviceReport.getApplication().getVersion());
|
||||
firmware.setUrl("http://localhost:8002/xiaozhi-esp32-api/api/v1/ota/download");
|
||||
response.setFirmware(firmware);
|
||||
|
||||
DeviceEntity deviceById = getDeviceById(deviceId);
|
||||
if (deviceById != null) { // 如果设备存在,则更新上次连接时间
|
||||
deviceById.setLastConnectedAt(new Date());
|
||||
deviceDao.updateById(deviceById);
|
||||
} else { // 如果设备不存在,则生成激活码
|
||||
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
||||
String dataKey = String.format("ota:activation:data:%s", safeDeviceId);
|
||||
|
||||
Map<Object, Object> cacheMap = redisTemplate.opsForHash().entries(dataKey);
|
||||
DeviceReportRespDTO.Activation code = new DeviceReportRespDTO.Activation();
|
||||
|
||||
if (cacheMap != null && cacheMap.containsKey("activation_code")) {
|
||||
String cachedCode = (String) cacheMap.get("activation_code");
|
||||
code.setCode(cachedCode);
|
||||
code.setMessage(frontedUrl + "\n" + cachedCode);
|
||||
} else {
|
||||
String newCode = RandomUtil.randomNumbers(6);
|
||||
code.setCode(newCode);
|
||||
code.setMessage(frontedUrl + "\n" + newCode);
|
||||
|
||||
Map<String, Object> dataMap = new HashMap<>();
|
||||
dataMap.put("id", deviceId);
|
||||
dataMap.put("mac_address", macAddress);
|
||||
dataMap.put("board", (deviceReport.getChipModelName() != null) ? deviceReport.getChipModelName()
|
||||
: (deviceReport.getBoard() != null ? deviceReport.getBoard().getType() : "unknown"));
|
||||
dataMap.put("app_version", (deviceReport.getApplication() != null)
|
||||
? deviceReport.getApplication().getVersion()
|
||||
: null);
|
||||
dataMap.put("deviceId", deviceId);
|
||||
dataMap.put("activation_code", newCode);
|
||||
|
||||
// 写入主数据 key
|
||||
redisTemplate.opsForHash().putAll(dataKey, dataMap);
|
||||
redisTemplate.expire(dataKey, 24, TimeUnit.HOURS);
|
||||
|
||||
// 写入反查激活码 key
|
||||
String codeKey = "ota:activation:code:" + newCode;
|
||||
redisTemplate.opsForValue().set(codeKey, deviceId, 24, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
response.setActivation(code);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceEntity bindDevice(DeviceBindDTO dto) {
|
||||
// 查看是否已经被绑定
|
||||
DeviceEntity deviceEntity = baseDao
|
||||
.selectOne(new LambdaQueryWrapper<DeviceEntity>().eq(DeviceEntity::getMacAddress, dto.getMacAddress()));
|
||||
if (deviceEntity != null) {
|
||||
throw new RenException("设备已绑定");
|
||||
}
|
||||
deviceEntity = ConvertUtils.sourceToTarget(dto, DeviceEntity.class);
|
||||
baseDao.insert(deviceEntity);
|
||||
return deviceEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceEntity> getUserDevices(Long userId, String agentId) {
|
||||
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("user_id", userId);
|
||||
return deviceDao.selectList(wrapper);
|
||||
wrapper.eq("agent_id", agentId);
|
||||
return baseDao.selectList(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindDevice(Long userId, Long deviceId) {
|
||||
deviceDao.deleteById(deviceId);
|
||||
public void unbindDevice(Long userId, String deviceId) {
|
||||
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
|
||||
wrapper.eq("user_id", userId);
|
||||
wrapper.eq("id", deviceId);
|
||||
baseDao.delete(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<DeviceEntity> adminDeviceList(Map<String, Object> params) {
|
||||
IPage<DeviceEntity> page = deviceDao.selectPage(
|
||||
getPage(params, "sort", true),
|
||||
new QueryWrapper<>()
|
||||
);
|
||||
return new PageData<>(page.getRecords(), page.getTotal());
|
||||
private DeviceReportRespDTO.ServerTime buildServerTime() {
|
||||
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
|
||||
TimeZone tz = TimeZone.getDefault();
|
||||
serverTime.setTimestamp(Instant.now().toEpochMilli());
|
||||
serverTime.setTimeZone(tz.getID());
|
||||
serverTime.setTimezoneOffset(tz.getOffset(System.currentTimeMillis()) / (60 * 1000));
|
||||
return serverTime;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package xiaozhi.modules.device.utils;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* 网络工具类
|
||||
*/
|
||||
public class NetworkUtil {
|
||||
/**
|
||||
* MAC地址正则表达式
|
||||
*/
|
||||
private static final Pattern macPattern = Pattern.compile("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
|
||||
|
||||
/**
|
||||
* 判断MAC地址是否合法
|
||||
*/
|
||||
public static boolean isMacAddressValid(String mac) {
|
||||
if (StringUtils.isBlank(mac)) {
|
||||
return false;
|
||||
}
|
||||
// 正则校验格式
|
||||
if (!macPattern.matcher(mac).matches()) {
|
||||
return false;
|
||||
}
|
||||
// 校验MAC地址是否为单播地址
|
||||
String normalized = mac.toLowerCase();
|
||||
String[] parts = normalized.split("[:-]");
|
||||
int firstByte = Integer.parseInt(parts[0], 16);
|
||||
return (firstByte & 1) == 0; // 最低位为0表示单播地址,合法
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package xiaozhi.modules.device.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceOtaVO {
|
||||
private Activation activation;
|
||||
private Mqtt mqtt;
|
||||
private ServerTime server_time;
|
||||
private Firmware firmware;
|
||||
private String error;
|
||||
|
||||
@Data
|
||||
public static class Activation {
|
||||
private String code;
|
||||
private String message;
|
||||
|
||||
public Activation() {
|
||||
}
|
||||
|
||||
public Activation(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public class Mqtt {
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ServerTime {
|
||||
private Long timestamp;
|
||||
private Integer timezone_offset;
|
||||
|
||||
public ServerTime() {
|
||||
}
|
||||
|
||||
public ServerTime(Long timestamp, Integer timezone_offset) {
|
||||
this.timestamp = timestamp;
|
||||
this.timezone_offset = timezone_offset;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Firmware {
|
||||
private String version;
|
||||
private String url;
|
||||
|
||||
public Firmware() {
|
||||
}
|
||||
|
||||
public Firmware(String version, String url) {
|
||||
this.version = version;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package xiaozhi.modules.model.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigDTO;
|
||||
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
||||
import xiaozhi.modules.model.dto.ModelProviderFieldDTO;
|
||||
import xiaozhi.modules.model.service.ModelConfigService;
|
||||
import xiaozhi.modules.model.service.ModelProviderService;
|
||||
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/models")
|
||||
@Tag(name = "模型配置")
|
||||
public class ModelController {
|
||||
|
||||
private final ModelProviderService modelProviderService;
|
||||
|
||||
private final ModelConfigService modelConfigService;
|
||||
|
||||
@GetMapping("/models/names")
|
||||
@Operation(summary = "获取所有模型名称")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<List<String>> getModelNames(@RequestParam String modelType,
|
||||
@RequestParam(required = false) String modelName) {
|
||||
List<String> modelNameList = modelConfigService.getModelCodeList(modelType, modelName);
|
||||
return new Result<List<String>>().ok(modelNameList);
|
||||
}
|
||||
|
||||
@GetMapping("/{modelType}/provideTypes")
|
||||
@Operation(summary = "获取模型供应器列表")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<List<ModelProviderDTO>> getModelProviderList(@PathVariable String modelType) {
|
||||
List<ModelProviderDTO> modelProviderDTOS = modelProviderService.getListByModelType(modelType);
|
||||
return new Result<List<ModelProviderDTO>>().ok(modelProviderDTOS);
|
||||
}
|
||||
|
||||
@GetMapping("/{modelType}/{provideCode}/fields")
|
||||
@Operation(summary = "获取模型供应器字段")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<List<ModelProviderFieldDTO>> getModelProviderFields(@PathVariable String modelType,
|
||||
@PathVariable String provideCode) {
|
||||
List<ModelProviderFieldDTO> fieldList = modelProviderService.getFieldList(modelType, provideCode);
|
||||
return new Result<List<ModelProviderFieldDTO>>().ok(fieldList);
|
||||
}
|
||||
|
||||
@GetMapping("/models/list")
|
||||
@Operation(summary = "获取模型配置列表")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<PageData<ModelConfigDTO>> getModelConfigList(
|
||||
@RequestParam(required = true) String modelType,
|
||||
@RequestParam(required = false) String modelName,
|
||||
@RequestParam(required = true, defaultValue = "0") String page,
|
||||
@RequestParam(required = true, defaultValue = "10") String limit) {
|
||||
PageData<ModelConfigDTO> pageList = modelConfigService.getPageList(modelType, modelName, page, limit);
|
||||
return new Result<PageData<ModelConfigDTO>>().ok(pageList);
|
||||
}
|
||||
|
||||
@PostMapping("/models/{modelType}/{provideCode}")
|
||||
@Operation(summary = "新增模型配置")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<ModelConfigDTO> addModelConfig(@PathVariable String modelType,
|
||||
@PathVariable String provideCode,
|
||||
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
|
||||
ModelConfigDTO modelConfigDTO = modelConfigService.add(modelType, provideCode, modelConfigBodyDTO);
|
||||
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
||||
}
|
||||
|
||||
@PutMapping("/models/{modelType}/{provideCode}/{id}")
|
||||
@Operation(summary = "编辑模型配置")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<ModelConfigDTO> editModelConfig(@PathVariable String modelType,
|
||||
@PathVariable String provideCode,
|
||||
@PathVariable String id,
|
||||
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
|
||||
ModelConfigDTO modelConfigDTO = modelConfigService.edit(modelType, provideCode, id, modelConfigBodyDTO);
|
||||
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
||||
}
|
||||
|
||||
@DeleteMapping("/models/{modelType}/{provideCode}/{id}")
|
||||
@Operation(summary = "删除模型配置")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<Void> deleteModelConfig(@PathVariable String modelType, @PathVariable String provideCode,
|
||||
@PathVariable String id) {
|
||||
modelConfigService.delete(modelType, provideCode, id);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@GetMapping("/models/{modelId}/voices")
|
||||
@Operation(summary = "获取模型音色")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<List<String>> getVoiceList(@PathVariable String modelId,
|
||||
@RequestParam(required = false) String voiceName) {
|
||||
|
||||
List<String> voiceList = modelConfigService.getVoiceList(modelId, voiceName);
|
||||
return new Result<List<String>>().ok(voiceList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package xiaozhi.modules.model.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import xiaozhi.common.dao.BaseDao;
|
||||
import xiaozhi.modules.model.entity.ModelConfigEntity;
|
||||
|
||||
@Mapper
|
||||
public interface ModelConfigDao extends BaseDao<ModelConfigEntity> {
|
||||
|
||||
/**
|
||||
* get model_code list
|
||||
*/
|
||||
List<String> getModelCodeList(@Param("modelType") String modelType, @Param("modelName") String modelName);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package xiaozhi.modules.model.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import xiaozhi.common.dao.BaseDao;
|
||||
import xiaozhi.modules.model.entity.ModelProviderEntity;
|
||||
|
||||
@Mapper
|
||||
public interface ModelProviderDao extends BaseDao<ModelProviderEntity> {
|
||||
|
||||
List<String> getFieldList(@Param("modelType") String modelType, @Param("providerCode") String providerCode);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package xiaozhi.modules.model.dto;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "模型供应器/商")
|
||||
public class ModelConfigBodyDTO {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
|
||||
// private String modelType;
|
||||
//
|
||||
@Schema(description = "模型编码(如AliLLM、DoubaoTTS)")
|
||||
private String modelCode;
|
||||
|
||||
@Schema(description = "模型名称")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description = "是否默认配置(0否 1是)")
|
||||
private Integer isDefault;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Integer isEnabled;
|
||||
|
||||
@Schema(description = "模型配置(JSON格式)")
|
||||
private String configJson;
|
||||
|
||||
@Schema(description = "官方文档链接")
|
||||
private String docLink;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package xiaozhi.modules.model.dto;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "模型供应器/商")
|
||||
public class ModelConfigDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
|
||||
private String modelType;
|
||||
|
||||
@Schema(description = "模型编码(如AliLLM、DoubaoTTS)")
|
||||
private String modelCode;
|
||||
|
||||
@Schema(description = "模型名称")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description = "是否默认配置(0否 1是)")
|
||||
private Integer isDefault;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Integer isEnabled;
|
||||
|
||||
@Schema(description = "模型配置(JSON格式)")
|
||||
private String configJson;
|
||||
|
||||
@Schema(description = "官方文档链接")
|
||||
private String docLink;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package xiaozhi.modules.model.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "模型供应器/商")
|
||||
public class ModelProviderDTO implements Serializable {
|
||||
//
|
||||
// @Schema(description = "主键")
|
||||
// private Long id;
|
||||
|
||||
@Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
|
||||
private String modelType;
|
||||
|
||||
@Schema(description = "供应器类型")
|
||||
private String providerCode;
|
||||
|
||||
@Schema(description = "供应器名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "供应器字段列表(JSON格式)")
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private String fields;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "更新者")
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Long updater;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Date updateDate;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long creator;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createDate;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package xiaozhi.modules.model.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "模型供应器字段")
|
||||
public class ModelProviderFieldDTO implements Serializable {
|
||||
@Schema(description = "字段名")
|
||||
private String key;
|
||||
|
||||
@Schema(description = "字段标签")
|
||||
private String label;
|
||||
|
||||
@Schema(description = "字段类型")
|
||||
private String type;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package xiaozhi.modules.model.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("ai_model_config")
|
||||
@Schema(description = "模型配置表")
|
||||
public class ModelConfigEntity {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
|
||||
private String modelType;
|
||||
|
||||
@Schema(description = "模型编码(如AliLLM、DoubaoTTS)")
|
||||
private String modelCode;
|
||||
|
||||
@Schema(description = "模型名称")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description = "是否默认配置(0否 1是)")
|
||||
private Integer isDefault;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Integer isEnabled;
|
||||
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
@Schema(description = "模型配置(JSON格式)")
|
||||
private String configJson;
|
||||
|
||||
@Schema(description = "官方文档链接")
|
||||
private String docLink;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "更新者")
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Long updater;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Date updateDate;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long creator;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createDate;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package xiaozhi.modules.model.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("ai_model_provider")
|
||||
@Schema(description = "模型供应器表")
|
||||
public class ModelProviderEntity {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
|
||||
private String modelType;
|
||||
|
||||
@Schema(description = "供应器类型,如 openai、")
|
||||
private String providerCode;
|
||||
|
||||
@Schema(description = "供应器名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "供应器字段列表(JSON格式)")
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private String fields;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
private Long creator;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createDate;
|
||||
|
||||
@Schema(description = "更新者")
|
||||
private Long updater;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private Date updateDate;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package xiaozhi.modules.model.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigDTO;
|
||||
|
||||
public interface ModelConfigService {
|
||||
|
||||
List<String> getModelCodeList(String modelType, String modelName);
|
||||
|
||||
PageData<ModelConfigDTO> getPageList(String modelType, String modelName, String page, String limit);
|
||||
|
||||
ModelConfigDTO add(String modelType, String provideCode, ModelConfigBodyDTO modelConfigBodyDTO);
|
||||
|
||||
ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO);
|
||||
|
||||
void delete(String modelType, String provideCode, String id);
|
||||
|
||||
List<String> getVoiceList(String modelName, String voiceName);
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package xiaozhi.modules.model.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
||||
import xiaozhi.modules.model.dto.ModelProviderFieldDTO;
|
||||
import xiaozhi.modules.model.entity.ModelProviderEntity;
|
||||
|
||||
public interface ModelProviderService {
|
||||
|
||||
// List<String> getModelNames(String modelType, String modelName);
|
||||
|
||||
List<ModelProviderDTO> getListByModelType(String modelType);
|
||||
|
||||
ModelProviderDTO add(ModelProviderEntity modelProviderEntity);
|
||||
|
||||
ModelProviderDTO edit(ModelProviderEntity modelProviderEntity);
|
||||
|
||||
void delete();
|
||||
|
||||
List<ModelProviderDTO> getList(String modelType, String provideCode);
|
||||
|
||||
List<ModelProviderFieldDTO> getFieldList(String modelType, String provideCode);
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package xiaozhi.modules.model.service.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.modules.model.dao.ModelConfigDao;
|
||||
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigDTO;
|
||||
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
||||
import xiaozhi.modules.model.entity.ModelConfigEntity;
|
||||
import xiaozhi.modules.model.service.ModelConfigService;
|
||||
import xiaozhi.modules.model.service.ModelProviderService;
|
||||
import xiaozhi.modules.timbre.service.TimbreService;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, ModelConfigEntity>
|
||||
implements ModelConfigService {
|
||||
|
||||
private final ModelConfigDao modelConfigDao;
|
||||
private final ModelProviderService modelProviderService;
|
||||
private final TimbreService timbreService;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ModelConfigServiceImpl.class);
|
||||
|
||||
@Override
|
||||
public List<String> getModelCodeList(String modelType, String modelName) {
|
||||
return modelConfigDao.getModelCodeList(modelType, modelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<ModelConfigDTO> getPageList(String modelType, String modelName, String page, String limit) {
|
||||
Map<String, Object> params = new HashMap<String, Object>();
|
||||
params.put(Constant.PAGE, page);
|
||||
params.put(Constant.LIMIT, limit);
|
||||
IPage<ModelConfigEntity> modelConfigEntityIPage = modelConfigDao.selectPage(
|
||||
getPage(params, "sort", true),
|
||||
new QueryWrapper<ModelConfigEntity>()
|
||||
.eq("model_type", modelType)
|
||||
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%"));
|
||||
return getPageData(modelConfigEntityIPage, ModelConfigDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelConfigDTO add(String modelType, String provideCode, ModelConfigBodyDTO modelConfigBodyDTO) {
|
||||
// 先验证有没有供应器
|
||||
if (StringUtils.isBlank(modelType) || StringUtils.isBlank(provideCode)) {
|
||||
throw new RenException("modelType和provideCode不能为空");
|
||||
}
|
||||
List<ModelProviderDTO> providerList = modelProviderService.getList(modelType, provideCode);
|
||||
if (CollectionUtil.isEmpty(providerList)) {
|
||||
throw new RenException("供应器不存在");
|
||||
}
|
||||
|
||||
// 再保存供应器提供的模型
|
||||
ModelConfigEntity modelConfigEntity = ConvertUtils.sourceToTarget(modelConfigBodyDTO, ModelConfigEntity.class);
|
||||
modelConfigEntity.setModelType(modelType);
|
||||
modelConfigDao.insert(modelConfigEntity);
|
||||
return ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO) {
|
||||
// 先验证有没有供应器
|
||||
if (StringUtils.isBlank(modelType) || StringUtils.isBlank(provideCode)) {
|
||||
throw new RenException("modelType和provideCode不能为空");
|
||||
}
|
||||
List<ModelProviderDTO> providerList = modelProviderService.getList(modelType, provideCode);
|
||||
if (CollectionUtil.isEmpty(providerList)) {
|
||||
throw new RenException("供应器不存在");
|
||||
}
|
||||
|
||||
// 再更新供应器提供的模型
|
||||
ModelConfigEntity modelConfigEntity = ConvertUtils.sourceToTarget(modelConfigBodyDTO, ModelConfigEntity.class);
|
||||
modelConfigEntity.setId(id);
|
||||
modelConfigEntity.setModelType(modelType);
|
||||
modelConfigDao.updateById(modelConfigEntity);
|
||||
return ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String modelType, String provideCode, String id) {
|
||||
// 先验证有没有供应器
|
||||
if (StringUtils.isBlank(modelType) || StringUtils.isBlank(provideCode)) {
|
||||
throw new RenException("modelType和provideCode不能为空");
|
||||
}
|
||||
List<ModelProviderDTO> providerList = modelProviderService.getList(modelType, provideCode);
|
||||
if (CollectionUtil.isEmpty(providerList)) {
|
||||
throw new RenException("供应器不存在");
|
||||
}
|
||||
|
||||
modelConfigDao.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getVoiceList(String modelId, String voiceName) {
|
||||
return timbreService.getVoiceNames(modelId, voiceName);
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package xiaozhi.modules.model.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.modules.model.dao.ModelProviderDao;
|
||||
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
||||
import xiaozhi.modules.model.dto.ModelProviderFieldDTO;
|
||||
import xiaozhi.modules.model.entity.ModelProviderEntity;
|
||||
import xiaozhi.modules.model.service.ModelProviderService;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao, ModelProviderEntity>
|
||||
implements ModelProviderService {
|
||||
|
||||
private final ModelProviderDao modelProviderDao;
|
||||
|
||||
@Override
|
||||
public List<ModelProviderDTO> getListByModelType(String modelType) {
|
||||
|
||||
QueryWrapper<ModelProviderEntity> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("model_type", StringUtils.isBlank(modelType) ? "" : modelType);
|
||||
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
|
||||
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelProviderDTO add(ModelProviderEntity modelProviderEntity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelProviderDTO edit(ModelProviderEntity modelProviderEntity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ModelProviderDTO> getList(String modelType, String providerCode) {
|
||||
QueryWrapper<ModelProviderEntity> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("model_type", StringUtils.isBlank(modelType) ? "" : modelType);
|
||||
queryWrapper.eq("provider_code", StringUtils.isBlank(providerCode) ? "" : providerCode);
|
||||
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
|
||||
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ModelProviderFieldDTO> getFieldList(String modelType, String providerCode) {
|
||||
List<String> modelProviderEntities = modelProviderDao.getFieldList(modelType, providerCode);
|
||||
if (modelProviderEntities == null || modelProviderEntities.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String fields = modelProviderEntities.getFirst();
|
||||
List<ModelProviderFieldDTO> fieldList = JsonUtils.parseArray(fields, ModelProviderFieldDTO.class);
|
||||
return fieldList;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
|
||||
|
||||
/**
|
||||
* Filter配置
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
@@ -18,7 +17,7 @@ public class FilterConfig {
|
||||
public FilterRegistrationBean<DelegatingFilterProxy> shiroFilterRegistration() {
|
||||
FilterRegistrationBean<DelegatingFilterProxy> registration = new FilterRegistrationBean<>();
|
||||
registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
|
||||
//该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
|
||||
// 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
|
||||
registration.addInitParameter("targetFilterLifecycle", "true");
|
||||
registration.setEnabled(true);
|
||||
registration.setOrder(Integer.MAX_VALUE - 1);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package xiaozhi.modules.security.config;
|
||||
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Realm;
|
||||
import jakarta.servlet.Filter;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.session.mgt.SessionManager;
|
||||
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||
@@ -14,9 +15,9 @@ import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import jakarta.servlet.Filter;
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Realm;
|
||||
|
||||
/**
|
||||
* Shiro的配置文件
|
||||
@@ -53,24 +54,29 @@ public class ShiroConfig {
|
||||
shiroFilter.setSecurityManager(securityManager);
|
||||
shiroFilter.setShiroFilterConfiguration(config);
|
||||
|
||||
//oauth过滤
|
||||
// oauth过滤
|
||||
Map<String, Filter> filters = new HashMap<>();
|
||||
filters.put("oauth2", new Oauth2Filter());
|
||||
shiroFilter.setFilters(filters);
|
||||
|
||||
//添加Shiro的内置过滤器
|
||||
/*anon:无需认证就可以访问
|
||||
authc:必须认证了才能让问
|
||||
user:必须拥有,记住我功能,才能访问
|
||||
perms:拥有对某个资源的权限才能访问
|
||||
role:拥有某个角色权限才能访问*/
|
||||
// 添加Shiro的内置过滤器
|
||||
/*
|
||||
* anon:无需认证就可以访问
|
||||
* authc:必须认证了才能让问
|
||||
* user:必须拥有,记住我功能,才能访问
|
||||
* perms:拥有对某个资源的权限才能访问
|
||||
* role:拥有某个角色权限才能访问
|
||||
*/
|
||||
Map<String, String> filterMap = new LinkedHashMap<>();
|
||||
filterMap.put("/ota/**", "anon");
|
||||
filterMap.put("/webjars/**", "anon");
|
||||
filterMap.put("/druid/**", "anon");
|
||||
filterMap.put("/v3/api-docs/**", "anon");
|
||||
filterMap.put("/doc.html", "anon");
|
||||
filterMap.put("/favicon.ico", "anon");
|
||||
filterMap.put("/user/**", "anon");
|
||||
filterMap.put("/user/captcha", "anon");
|
||||
filterMap.put("/user/login", "anon");
|
||||
filterMap.put("/user/register", "anon");
|
||||
filterMap.put("/**", "oauth2");
|
||||
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package xiaozhi.modules.security.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
@@ -15,8 +14,10 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
@@ -45,14 +46,14 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
//忽略未知属性
|
||||
// 忽略未知属性
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
//日期格式转换
|
||||
//mapper.setDateFormat(new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN));
|
||||
// 日期格式转换
|
||||
// mapper.setDateFormat(new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN));
|
||||
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
|
||||
|
||||
//Long类型转String类型
|
||||
// Long类型转String类型
|
||||
SimpleModule simpleModule = new SimpleModule();
|
||||
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
|
||||
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
|
||||
|
||||
+31
-27
@@ -1,32 +1,37 @@
|
||||
package xiaozhi.modules.security.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.page.TokenDTO;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.common.validator.AssertUtils;
|
||||
import xiaozhi.modules.security.dao.SysUserTokenDao;
|
||||
import xiaozhi.common.validator.ValidatorUtils;
|
||||
import xiaozhi.modules.security.dto.LoginDTO;
|
||||
import xiaozhi.modules.security.password.PasswordUtils;
|
||||
import xiaozhi.modules.security.service.CaptchaService;
|
||||
import xiaozhi.modules.security.service.SysUserTokenService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
import xiaozhi.modules.sys.dto.PasswordDTO;
|
||||
import xiaozhi.modules.sys.dto.SysUserDTO;
|
||||
import xiaozhi.modules.sys.service.SysUserService;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 登录控制层
|
||||
*/
|
||||
@Tag(name = "登录管理")
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
@@ -36,15 +41,13 @@ public class LoginController {
|
||||
private final SysUserTokenService sysUserTokenService;
|
||||
private final CaptchaService captchaService;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
|
||||
|
||||
@GetMapping("/captcha")
|
||||
@Operation(summary = "验证码")
|
||||
public void captcha(HttpServletResponse response, String uuid) throws IOException {
|
||||
//uuid不能为空
|
||||
// uuid不能为空
|
||||
AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL);
|
||||
|
||||
//生成验证码
|
||||
// 生成验证码
|
||||
captchaService.create(response, uuid);
|
||||
}
|
||||
|
||||
@@ -79,32 +82,33 @@ public class LoginController {
|
||||
}
|
||||
// 按照用户名获取用户
|
||||
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
|
||||
if (userDTO != null){
|
||||
if (userDTO != null) {
|
||||
throw new RenException("此手机号码已经注册过");
|
||||
}
|
||||
userDTO = new SysUserDTO();
|
||||
userDTO.setUsername(login.getUsername());
|
||||
userDTO.setPassword(login.getPassword());
|
||||
sysUserService.save(userDTO);
|
||||
return new Result<Void>();
|
||||
return new Result<>();
|
||||
|
||||
}
|
||||
|
||||
@GetMapping("/info")
|
||||
@Operation(summary = "用户信息获取")
|
||||
public Result<SysUserDTO> info(@RequestHeader("Authorization")String authorization) {
|
||||
logger.info("the authorization:{}", authorization);
|
||||
public Result<UserDetail> info() {
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
Result<UserDetail> result = new Result<>();
|
||||
result.setData(user);
|
||||
return result;
|
||||
}
|
||||
|
||||
String token;
|
||||
if (StringUtils.isBlank(authorization) && authorization.contains("Bearer ")) {
|
||||
throw new RenException(ErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
token = authorization.replace("Bearer ", "");
|
||||
if (StringUtils.isBlank(token)) {
|
||||
throw new RenException(ErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
SysUserDTO sysUserDTO = sysUserTokenService.getUserByToken(token);
|
||||
Result result = new Result<SysUserDTO>();
|
||||
return result.ok(sysUserDTO);
|
||||
@PutMapping("/change-password")
|
||||
@Operation(summary = "修改用户密码")
|
||||
public Result<?> changePassword(@RequestBody PasswordDTO passwordDTO) {
|
||||
// 判断非空
|
||||
ValidatorUtils.validateEntity(passwordDTO);
|
||||
Long userId = SecurityUser.getUserId();
|
||||
sysUserTokenService.changePassword(userId, passwordDTO);
|
||||
return new Result<>();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package xiaozhi.modules.security.dao;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import xiaozhi.common.dao.BaseDao;
|
||||
import xiaozhi.modules.security.entity.SysUserTokenEntity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 系统用户Token
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package xiaozhi.modules.security.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 登录表单
|
||||
*/
|
||||
|
||||
+4
-3
@@ -1,13 +1,14 @@
|
||||
package xiaozhi.modules.security.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 系统用户Token
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user