Compare commits

...
18 Commits
Author SHA1 Message Date
a8f06f5718 Test prompt (#305)
* fix: 退出指令无法识别 (#302)

修复指令text包含标点的情况下,无法正确识别退出指令的问题

* 优化提示词 (#303)

* 添加 使用 coze 会话。 (#282)

历史消息在coze 上可配置历史消息的轮次。
同个会话id,coze 上自动带历史上下文。

会话和当前session关联。一次连接一个会话,断开后,重连是新会话无历史消息。

* update:去除无用代码

---------

Co-authored-by: Jad <journey.adc@gmail.com>
Co-authored-by: Jiao Haoyang <108573524+XuSenfeng@users.noreply.github.com>
Co-authored-by: xu <494462498@qq.com>
Co-authored-by: hrz <1710360675@qq.com>
2025-03-13 00:54:01 +08:00
c458a1b59a Test login api (#304)
* 优化登录注册流程,跑通注册登录、接口 (#297)

* fix manager-api bug

* 优化登录注册流程,跑通注册登录、接口

* update package

* update:第一个注册的用户是超级管理员

---------

Co-authored-by: zhisheng <pzs034@gmail.com>
Co-authored-by: hrz <1710360675@qq.com>
2025-03-12 23:31:59 +08:00
欣南科技andGitHub 29d59b3974 Main add video (#301)
* 更新教程视频.md

* Update README.md
2025-03-12 22:12:33 +08:00
欣南科技andGitHub 27f267cb3d 更新教程视频.md (#300) 2025-03-12 21:57:53 +08:00
0a26ad8925 Manager code login (#276)
* update:增加前端设计图

* 前端代码优化

* update:底部信息纠正

* 增加:flyio

* update:去除org.quartz

* update:登陆功能

* aliyunTTS常联token

* 新增aliyunTTS长期Token方式

* 超时错误处理

* 本地编译文件

* 验证码获取

* 验证码获取测试

* 验证码展示

* 登录成功

* delete 测试代码

* 注册页面

* Register 风格统一

* web验证码登录注册

* 登录注册功能

* update:登陆注册api结果优化

* delete:删除未用到的代码

---------

Co-authored-by: hrz <1710360675@qq.com>
Co-authored-by: CGD <3030332422@qq.com>
Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>
2025-03-12 13:38:44 +08:00
a8c6c7cb36 update:优化无意图识别的日志输出 (#295)
Co-authored-by: hrz <1710360675@qq.com>
2025-03-12 12:46:29 +08:00
81fee53085 绑定设备功能 (#294)
Co-authored-by: CGD <3030332422@qq.com>
2025-03-12 12:44:25 +08:00
7c5f2d95a4 🎈 perf: 更新文档+优雅的退出 (#292)
* 🎈 perf: 更新文档+优雅的退出

* update:gsv代码可以使用gpt_sovits_v2适配器代码拓展,不需要单独。如果需要定义模型名称,可以在gpt_sovits_v2代码中添加扩展

---------

Co-authored-by: hrz <1710360675@qq.com>
2025-03-12 10:57:25 +08:00
vonwellerandGitHub 3d6bd0e23e 修复ActionResponse为NOTFOUND时候卡住主线程的bug (#285)
修复ActionResponse为NOTFOUND时候卡住主线程的bug
2025-03-11 21:10:44 +08:00
gitjianyuandGitHub 8064e84276 登录。注册。验证码 (#271)
* 修改shiro配置,对/user/*的请求不继续登录验证拦截
--ShiroConfig.java

* 添加了注册,登录接口
--LoginController.java
修改登录请求dto,使得符合Apifox定义接口请求属性
--LoginDTO.java

* 为数据库sys_user表补全创建者的字段
--202503101631.sql
--db.changelog-master.yaml

* 修改系统上下文路径的前缀,使得符合Apifox的接口请求前缀
--application.yml

* 注册接口补上验证码判断
--LoginController.java
2025-03-11 21:06:03 +08:00
香草味的纳西妲andGitHub f8ece2d984 add: GSV本地语音合成相关 (#266) 2025-03-11 21:04:04 +08:00
gitjianyuandGitHub 442eef36ba 修正 (#277)
* 改正自定义错误编码和i18n国际化错误说明对应
--ErrorCode.java 自定义编码
--messages.properties 默认错误说明
--messages_en_US.properties 英文错误说明
--messages_zh_CN.properties 中文简体说明
--messages_zh_TW.properties 中文繁体说明

* i18n国际化剔除自定义参数验证不符合条件的情况说明
--validation.properties 默认说明
--validation_en_US.properties 英文说明
--validation_zh_CN.properties 中文简体说明
--validation_zh_TW.properties 中文繁体说明
2025-03-11 20:58:37 +08:00
7d737ad312 update:增加开发者贡献指南 (#279)
Co-authored-by: hrz <1710360675@qq.com>
2025-03-11 17:28:33 +08:00
2b662d3a14 fix:function call bug (#268)
* 优化function call消息处理

* fix:用户说话重复记录bug

* 2025-3-10-优化llm intent (#254)

Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>

* update:回复上版提示词,无需再二次识别歌曲名

---------

Co-authored-by: 玄凤科技 <eric230308@gmail.com>
Co-authored-by: hrz <1710360675@qq.com>
Co-authored-by: Jiao Haoyang <108573524+XuSenfeng@users.noreply.github.com>
2025-03-11 00:25:33 +08:00
4f3fae81c1 fix:修复长时间不说话关闭意图bug (#267)
Co-authored-by: hrz <1710360675@qq.com>
2025-03-10 22:45:03 +08:00
7b266ad7c0 完成搜索功能开发 (#263)
Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com>
2025-03-10 18:29:08 +08:00
CGDandGitHub 16601a67e3 完成解绑功能 (#264) 2025-03-10 18:21:59 +08:00
224d59d50d fix:声音卡顿问题 (#262)
Co-authored-by: hrz <1710360675@qq.com>
2025-03-10 15:56:28 +08:00
52 changed files with 1013 additions and 1184 deletions
+5 -8
View File
@@ -100,6 +100,8 @@
- [小智安卓端](https://github.com/TOM88812/xiaozhi-android-client) - [小智安卓端](https://github.com/TOM88812/xiaozhi-android-client)
- [小智电脑端](https://github.com/Huang-junsen/py-xiaozhi) - [小智电脑端](https://github.com/Huang-junsen/py-xiaozhi)
如果你是一名软件开发者,这里有一份[《致开发者的公开信》](docs/contributor_open_letter.md),欢迎加入!
--- ---
## 警告 ⚠️ ## 警告 ⚠️
@@ -230,14 +232,9 @@ server:
本项目支持以下三种部署方式,您可根据实际需求选择。 本项目支持以下三种部署方式,您可根据实际需求选择。
本项目的文档主要是`文字版本`的教程,如果你想要`视频版本`
的教程,您可以学习一下[这个大佬的手把手教程](https://www.bilibili.com/video/BV1gePuejEvT)。
如果你能把`文字版本的教程``视频版本的教程`结合起来一起看,可以让你更快上手。
1. [Docker 快速部署](./docs/Deployment.md) 1. [Docker 快速部署](./docs/Deployment.md)
适合快速体验的普通用户,不需过多环境配置。缺点是,拉取镜像有点慢。 适合快速体验的普通用户,不需过多环境配置。缺点是,拉取镜像有点慢。视频教程可参考[美女大佬教你Docker部署](https://www.bilibili.com/video/BV1RNQnYDE5t)
2. [借助 Docker 环境运行部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2) 2. [借助 Docker 环境运行部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2)
@@ -247,7 +244,7 @@ server:
适合熟悉`Conda` 环境或希望从零搭建运行环境的用户。 适合熟悉`Conda` 环境或希望从零搭建运行环境的用户。
对于对响应速度要求较高的场景,推荐使用本地源码运行方式以降低额外开销。 对于对响应速度要求较高的场景,推荐使用本地源码运行方式以降低额外开销。视频教程可参考[帅哥大佬教你源码部署](https://www.bilibili.com/video/BV1GvQWYZEd2)
### 二、[固件编译](./docs/firmware-build.md) ### 二、[固件编译](./docs/firmware-build.md)
@@ -376,4 +373,4 @@ VAD:
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" /> <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" /> <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
</picture> </picture>
</a> </a>
+10 -26
View File
@@ -93,7 +93,7 @@ docker logs -f xiaozhi-esp32-server
``` ```
docker stop xiaozhi-esp32-server docker stop xiaozhi-esp32-server
docker rm xiaozhi-esp32-server docker rm xiaozhi-esp32-server
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:latest docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
``` ```
3、重新按docker方式部署 3、重新按docker方式部署
@@ -117,7 +117,7 @@ docker run -it --name xiaozhi-env --restart always --security-opt seccomp:unconf
-p 8000:8000 \ -p 8000:8000 \
-p 8002:8002 \ -p 8002:8002 \
-v ./:/app \ -v ./:/app \
kalicyh/poetry:v3.10_xiaozhi kalicyh/python:xiaozhi
``` ```
然后就和正常开发一样了 然后就和正常开发一样了
@@ -127,29 +127,13 @@ docker run -it --name xiaozhi-env --restart always --security-opt seccomp:unconf
在刚刚的打开的终端运行 在刚刚的打开的终端运行
```sh ```sh
poetry install --no-root pip install -r requirements.txt
```
```sh
apt-get update
apt-get install -y --no-install-recommends libopus0 ffmpeg
```
速度慢可以尝试使用清华镜像
```sh
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-backports main contrib non-free non-free-firmware" >> /etc/apt/sources.list
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list
apt-get update
apt-get install -y --no-install-recommends libopus0 ffmpeg
``` ```
## 6.运行项目 ## 6.运行项目
```sh ```sh
poetry run python app.py python app.py
``` ```
# 方式三:本地源码运行 # 方式三:本地源码运行
@@ -299,14 +283,14 @@ LLM:
以下是一些常见问题,供参考: 以下是一些常见问题,供参考:
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [1、为什么我说的话,小智识别出来很多韩文、日文、英文](../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)
[2、为什么会出现“TTS 任务出错 文件不存在”?](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [2、为什么会出现“TTS 任务出错 文件不存在”?](../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)
[3、TTS 经常失败,经常超时](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [3、TTS 经常失败,经常超时](../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)
[4、如何提高小智对话响应速度?](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [4、如何提高小智对话响应速度?](../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)
[5、我说话很慢,停顿时小智老是抢话](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [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#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) [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)
+51
View File
@@ -0,0 +1,51 @@
# 致开发者的公开信
"春江水暖鸭先知,正是河豚欲上时!"
亲爱的朋友,今天,我怀着无比真挚的心情,向热爱AI技术与创新的你发出这封公开信。
我们的开源项目 **xiaozhi-esp32-server** 正处在发展的阶段,它承载着我们对智能硬件和低成本民用贾维斯解决方案的美好憧憬,也希望借助每一位开发者的智慧,共同开创技术的新局面。
## 项目目标
1. **民用低成本贾维斯解决方案**
2. **智能联动周边硬件的解决方案**
## 加入我们
我们热忱欢迎志同道合的朋友加入,共同为项目贡献力量。参与方式如下:
### 1、成为普通贡献者
Fork 项目,提交 PR,由开发者审核后合入主分支。
### 2、成为开发者
当你累计提交 3 次有效 PR 后,可以联系群主申请成为开发者,群主将邀请你加入独立的开发者群,共同探讨项目未来。
## 开发者开发流程
1. **创建新分支**
每个功能点请以新分支方式开发,分支名称应简洁明了,让人一眼看出所实现的功能,避免功能撞车。
2. **提交 PR 审核**
功能开发完成后,请在 GitHub 上提交 PR,由其他开发者审核,审核通过后合并入主分支。
## 功能点的来源
- **创新的点子与功能**
你的每一个灵感都可能成为项目的突破点,欢迎大胆提出前沿的创意与功能建议。
- **代码优化**
如果你发现代码中存在改进的空间,欢迎提交优化方案,让我们的代码更加高效、易读。
- **解决 Issues 问题**
Issues 中有很多问题需要解决,我们一同完善、打造产品。
亲爱的开发者们,
每一次提交、每一份建议,都是对我们共同梦想的加持。每一代人有每一代人的使命,让我们共同书写智能生活的新篇章!
期待与你携手前行,共创未来。
John2025.3.11,广州
+1 -1
View File
@@ -3,7 +3,7 @@
# 项目目录介绍 # 项目目录介绍
当你看到这份文件的时候,这个这个项目还没完善好。我们还有很多东西要做。 当你看到这份文件的时候,这个这个项目还没完善好。我们还有很多东西要做。
如果你会开发,我们非常欢迎您的加入。 如果你是一名软件开发者,这里有一份[《致开发者的公开信》](../docs/contributor_open_letter.md),欢迎归队!
``` ```
xiaozhi-esp32-server xiaozhi-esp32-server
@@ -8,6 +8,6 @@ public class AdminApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args); SpringApplication.run(AdminApplication.class, args);
System.out.println("http://localhost:8002/xiaozhi-esp32-api/doc.html"); System.out.println("http://localhost:8002/xiaozhi-esp32-api/api/v1/doc.html");
} }
} }
@@ -22,24 +22,24 @@ public interface ErrorCode {
int CAPTCHA_ERROR = 10007; int CAPTCHA_ERROR = 10007;
int SUB_MENU_EXIST = 10008; int SUB_MENU_EXIST = 10008;
int PASSWORD_ERROR = 10009; int PASSWORD_ERROR = 10009;
int SUPERIOR_DEPT_ERROR = 10011; int SUPERIOR_DEPT_ERROR = 10011;
int SUPERIOR_MENU_ERROR = 10012; int SUPERIOR_MENU_ERROR = 10012;
int DATA_SCOPE_PARAMS_ERROR = 10013; int DATA_SCOPE_PARAMS_ERROR = 10013;
int DEPT_SUB_DELETE_ERROR = 10014; int DEPT_SUB_DELETE_ERROR = 10014;
int DEPT_USER_DELETE_ERROR = 10015; int DEPT_USER_DELETE_ERROR = 10015;
int UPLOAD_FILE_EMPTY = 10019; int UPLOAD_FILE_EMPTY = 10019;
int TOKEN_NOT_EMPTY = 10020; int TOKEN_NOT_EMPTY = 10020;
int TOKEN_INVALID = 10021; int TOKEN_INVALID = 10021;
int ACCOUNT_LOCK = 10022; int ACCOUNT_LOCK = 10022;
int OSS_UPLOAD_FILE_ERROR = 10024; int OSS_UPLOAD_FILE_ERROR = 10024;
int REDIS_ERROR = 10027; int REDIS_ERROR = 10027;
int JOB_ERROR = 10028; int JOB_ERROR = 10028;
int INVALID_SYMBOL = 10029; int INVALID_SYMBOL = 10029;
// 密码相关错误码
int PASSWORD_LENGTH_ERROR = 10030; int PASSWORD_LENGTH_ERROR = 10030;
int PASSWORD_WEAK_ERROR = 10031; int PASSWORD_WEAK_ERROR = 10031;
int DEL_MYSELF_ERROR = 10032; int DEL_MYSELF_ERROR = 10032;
// 验证码错误
int VERIFICATION_CODE = 10033;
} }
@@ -69,6 +69,7 @@ public class ShiroConfig {
filterMap.put("/captcha", "anon"); filterMap.put("/captcha", "anon");
filterMap.put("/favicon.ico", "anon"); filterMap.put("/favicon.ico", "anon");
filterMap.put("/mobile/**", "anon"); filterMap.put("/mobile/**", "anon");
filterMap.put("/user/**", "anon");
filterMap.put("/**", "oauth2"); filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap); shiroFilter.setFilterChainDefinitionMap(filterMap);
@@ -2,41 +2,37 @@ package xiaozhi.modules.security.controller;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.exception.RenException;
import xiaozhi.common.utils.PropertiesUtils;
import xiaozhi.common.utils.Result; import xiaozhi.common.utils.Result;
import xiaozhi.common.validator.AssertUtils; import xiaozhi.common.validator.AssertUtils;
import xiaozhi.modules.security.dto.LoginDTO; import xiaozhi.modules.security.dto.LoginDTO;
import xiaozhi.modules.security.password.PasswordUtils;
import xiaozhi.modules.security.service.CaptchaService; import xiaozhi.modules.security.service.CaptchaService;
import xiaozhi.modules.security.service.SysUserTokenService; import xiaozhi.modules.security.service.SysUserTokenService;
import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.dto.SysUserDTO;
import xiaozhi.modules.sys.service.SysUserService; import xiaozhi.modules.sys.service.SysUserService;
import java.io.IOException; import java.io.IOException;
/** /**
* 登录 * 登录控制层
*/ */
@Tag(name = "登录管理")
@AllArgsConstructor @AllArgsConstructor
@RestController @RestController
@RequestMapping("/user")
@Tag(name = "登录管理") @Tag(name = "登录管理")
public class LoginController { public class LoginController {
private final SysUserService sysUserService; private final SysUserService sysUserService;
private final SysUserTokenService sysUserTokenService; private final SysUserTokenService sysUserTokenService;
private final CaptchaService captchaService; private final CaptchaService captchaService;
private final RedisUtils redisUtils;
private final SysParamsService sysParamsService;
private final PropertiesUtils propertiesUtils;
@GetMapping("captcha")
@GetMapping("/captcha")
@Operation(summary = "验证码") @Operation(summary = "验证码")
public void captcha(HttpServletResponse response, String uuid) throws IOException { public void captcha(HttpServletResponse response, String uuid) throws IOException {
//uuid不能为空 //uuid不能为空
@@ -46,10 +42,46 @@ public class LoginController {
captchaService.create(response, uuid); captchaService.create(response, uuid);
} }
@PostMapping("login") @PostMapping("/login")
@Operation(summary = "登录") @Operation(summary = "登录")
public Result login(HttpServletRequest request, @RequestBody LoginDTO login) { public Result login( @RequestBody LoginDTO login) {
return sysUserTokenService.createToken(1L); // 验证是否正确输入验证码
boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha());
if (!validate) {
throw new RenException("验证码错误,请重新获取");
}
// 按照用户名获取用户
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
// 判断用户是否存在
if (userDTO == null) {
throw new RenException("请检测用户和密码是否输入错误");
}
// 判断密码是否正确,不一样则进入if
if (!PasswordUtils.matches(login.getPassword(), userDTO.getPassword())) {
throw new RenException("请检测用户和密码是否输入错误");
}
return sysUserTokenService.createToken(userDTO.getId());
}
@PostMapping("/register")
@Operation(summary = "注册")
public Result<Void> register(@RequestBody LoginDTO login) {
// 验证是否正确输入验证码
boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha());
if (!validate) {
throw new RenException("验证码错误,请重新获取");
}
// 按照用户名获取用户
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
if (userDTO != null){
throw new RenException("此手机号码已经注册过");
}
userDTO = new SysUserDTO();
userDTO.setUsername(login.getUsername());
userDTO.setPassword(login.getPassword());
sysUserService.save(userDTO);
return new Result<Void>();
} }
} }
@@ -13,7 +13,7 @@ import java.io.Serializable;
@Schema(description = "登录表单") @Schema(description = "登录表单")
public class LoginDTO implements Serializable { public class LoginDTO implements Serializable {
@Schema(description = "用户名", required = true) @Schema(description = "手机号码")
@NotBlank(message = "{sysuser.username.require}") @NotBlank(message = "{sysuser.username.require}")
private String username; private String username;
@@ -27,6 +27,6 @@ public class LoginDTO implements Serializable {
@Schema(description = "唯一标识") @Schema(description = "唯一标识")
@NotBlank(message = "{sysuser.uuid.require}") @NotBlank(message = "{sysuser.uuid.require}")
private String uuid; private String captchaId;
} }
@@ -80,7 +80,6 @@ public class SysUserController {
//效验数据 //效验数据
ValidatorUtils.validateEntity(dto); ValidatorUtils.validateEntity(dto);
String newPassword = dto.getNewPassword(); String newPassword = dto.getNewPassword();
String password = dto.getPassword();
//密码的强度 //密码的强度
if (newPassword == null || newPassword.length() < 8) { if (newPassword == null || newPassword.length() < 8) {
@@ -38,23 +38,6 @@ public interface SysUserService extends BaseService<SysUserEntity> {
*/ */
void updatePassword(Long id, String newPassword); void updatePassword(Long id, String newPassword);
/**
* 根据部门ID,查询用户数
*/
int getCountByDeptId(Long deptId);
/**
* 根据部门ID,查询用户Id列表
*/
List<Long> getUserIdListByDeptId(List<Long> deptIdList);
/**
* 删除用户缓存
*
* @param userId
*/
void deleteUserCache(Long userId);
/** /**
* 验证密码强度 * 验证密码强度
* *
@@ -63,5 +46,4 @@ public interface SysUserService extends BaseService<SysUserEntity> {
*/ */
boolean isStrongPassword(String newPassword); boolean isStrongPassword(String newPassword);
String getName(Long creator);
} }
@@ -1,25 +1,23 @@
package xiaozhi.modules.sys.service.impl; package xiaozhi.modules.sys.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.modules.security.password.PasswordUtils; import xiaozhi.modules.security.password.PasswordUtils;
import xiaozhi.modules.security.user.SecurityUser;
import xiaozhi.modules.sys.dao.SysUserDao; import xiaozhi.modules.sys.dao.SysUserDao;
import xiaozhi.modules.sys.dto.SysUserDTO; import xiaozhi.modules.sys.dto.SysUserDTO;
import xiaozhi.modules.sys.entity.SysUserEntity; import xiaozhi.modules.sys.entity.SysUserEntity;
import xiaozhi.modules.sys.enums.SuperAdminEnum; import xiaozhi.modules.sys.enums.SuperAdminEnum;
import xiaozhi.modules.sys.service.SysUserService; import xiaozhi.modules.sys.service.SysUserService;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -86,7 +84,13 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
entity.setPassword(password); entity.setPassword(password);
//保存用户 //保存用户
entity.setSuperAdmin(SuperAdminEnum.NO.value()); Long userCount = getUserCount();
if (userCount == 0) {
entity.setSuperAdmin(SuperAdminEnum.YES.value());
} else {
entity.setSuperAdmin(SuperAdminEnum.NO.value());
}
insert(entity); insert(entity);
} }
@@ -139,23 +143,11 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
baseDao.updatePassword(id, newPassword); baseDao.updatePassword(id, newPassword);
} }
@Override private Long getUserCount() {
public int getCountByDeptId(Long deptId) { QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
return baseDao.getCountByDeptId(deptId); return baseDao.selectCount(queryWrapper);
} }
@Override
public List<Long> getUserIdListByDeptId(List<Long> deptIdList) {
return baseDao.getUserIdListByDeptId(deptIdList);
}
@Override
public void deleteUserCache(Long userId) {
// 删除缓存
redisUtils.delete(RedisKeys.getUserInfoKey(userId));
redisUtils.delete(RedisKeys.getDataScopeListKey(userId));
redisUtils.delete(RedisKeys.getSysUserName(userId));
}
@Override @Override
public boolean isStrongPassword(String password) { public boolean isStrongPassword(String password) {
@@ -165,17 +157,4 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
Matcher matcher = pattern.matcher(password); Matcher matcher = pattern.matcher(password);
return matcher.matches(); return matcher.matches();
} }
@Override
public String getName(Long id) {
String name = (String) redisUtils.get(RedisKeys.getSysUserName(id));
if (StringUtils.isBlank(name)) {
SysUserEntity sysUserEntity = selectById(id);
if (sysUserEntity != null) {
redisUtils.set(RedisKeys.getSysUserName(id), sysUserEntity.getUsername());
return sysUserEntity.getUsername();
}
}
return name;
}
} }
@@ -7,7 +7,7 @@ server:
min-spare: 30 min-spare: 30
port: 8002 port: 8002
servlet: servlet:
context-path: /xiaozhi-esp32-api context-path: /xiaozhi-esp32-api/api/v1
session: session:
cookie: cookie:
http-only: true http-only: true
@@ -0,0 +1,2 @@
-- 给用户表添加一个创建者
ALTER TABLE sys_user ADD COLUMN creator BIGINT COMMENT '创建者';
@@ -8,4 +8,11 @@ databaseChangeLog:
changes: changes:
- sqlFile: - sqlFile:
encoding: utf8 encoding: utf8
path: classpath:db/changelog/001create_sys.sql path: classpath:db/changelog/001create_sys.sql
- changeSet:
id: 202503101631
author: zjy
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202503101631.sql
@@ -9,7 +9,6 @@
10005=\u8D26\u53F7\u5DF2\u88AB\u505C\u7528 10005=\u8D26\u53F7\u5DF2\u88AB\u505C\u7528
10006=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A 10006=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
10007=\u9A8C\u8BC1\u7801\u4E0D\u6B63\u786E 10007=\u9A8C\u8BC1\u7801\u4E0D\u6B63\u786E
10008=\u5148\u5220\u9664\u5B50\u83DC\u5355\u6216\u6309\u94AE
10009=\u539F\u5BC6\u7801\u4E0D\u6B63\u786E 10009=\u539F\u5BC6\u7801\u4E0D\u6B63\u786E
10010=\u8D26\u53F7\u6216\u5BC6\u7801\u4E0D\u6B63\u786E,\u60A8\u8FD8\u6709\u53EF\u4EE5\u5C1D\u8BD5{0}\u6B21 10010=\u8D26\u53F7\u6216\u5BC6\u7801\u4E0D\u6B63\u786E,\u60A8\u8FD8\u6709\u53EF\u4EE5\u5C1D\u8BD5{0}\u6B21
10011=\u4E0A\u7EA7\u90E8\u95E8\u9009\u62E9\u9519\u8BEF 10011=\u4E0A\u7EA7\u90E8\u95E8\u9009\u62E9\u9519\u8BEF
@@ -31,24 +30,6 @@
10027=Redis\u670D\u52A1\u5F02\u5E38 10027=Redis\u670D\u52A1\u5F02\u5E38
10028=\u5B9A\u65F6\u4EFB\u52A1\u5931\u8D25 10028=\u5B9A\u65F6\u4EFB\u52A1\u5931\u8D25
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26 10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
10030=\u53C2\u6570\u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u8BF7\u4F7F\u7528JSON\u683C\u5F0F 10030=\u5BC6\u7801\u957F\u5EA6\u4E0D\u8DB3{0}\u4F4D
10031=\u8BF7\u5148\u5B8C\u6210\u77ED\u4FE1\u914D\u7F6E 10031=\u5F31\u5BC6\u7801
10032=\u4EFB\u52A1\u5DF2\u88AB\u7B7E\u6536\uFF0C\u64CD\u4F5C\u5931\u8D25 10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38
10033=\u4E0D\u5B58\u5728\u7684\u6D41\u7A0B\u5B9A\u4E49
10034=\u4E0A\u7EA7\u8282\u70B9\u4E0D\u5B58\u5728
10035=\u9A73\u56DE
10036=\u56DE\u9000
10037=\u4EFB\u52A1\u6CA1\u6709\u5206\u7EC4\uFF0C\u65E0\u6CD5\u53D6\u6D88\u8BA4\u9886
10038=\u4E0A\u7EA7\u533A\u57DF\u9009\u62E9\u9519\u8BEF
10039=\u8BF7\u5148\u5220\u9664\u4E0B\u7EA7\u533A\u57DF
10040=\u6D41\u7A0B\u5DF2\u6302\u8D77\uFF0C\u4E0D\u80FD\u542F\u52A8\u5B9E\u4F8B
10041=\u591A\u5B9E\u4F8B\u4EFB\u52A1\u4E0D\u80FD\u9A73\u56DE
10042=\u5B58\u5728\u591A\u4E2A\u5904\u7406\u4E2D\u7684\u4EFB\u52A1\uFF0C\u4E0D\u80FD\u9A73\u56DE
10043=\u591A\u5B9E\u4F8B\u4EFB\u52A1\u4E0D\u80FD\u7EC8\u6B62
10044=\u5B58\u5728\u591A\u4E2A\u5904\u7406\u4E2D\u7684\u4EFB\u52A1\uFF0C\u4E0D\u80FD\u7EC8\u6B62\u6D41\u7A0B
10045=\u7EC8\u6B62
10046=\u591A\u5B9E\u4F8B\u4EFB\u52A1\u4E0D\u80FD\u56DE\u9000
10047=\u5B58\u5728\u591A\u4E2A\u5E76\u884C\u6267\u884C\u7684\u4EFB\u52A1\uFF0C\u4E0D\u80FD\u56DE\u9000
10048=\u767B\u5F55\u8D26\u53F7\uFF0C\u65E0\u6743\u5220\u9664
10055=\u60A8\u7684\u5BC6\u7801\u957F\u5EA6\u4E0D\u591F8\u4F4D
10056=\u60A8\u7684\u5BC6\u7801\u590D\u6742\u5EA6\u4E0D\u591F\uFF0C\u9700\u8981\u540C\u65F6\u5305\u542B\u6570\u5B57,\u5C0F\u5199\u82F1\u6587,\u5927\u5199\u82F1\u6587
@@ -11,44 +11,24 @@
10007=The verification code is incorrect 10007=The verification code is incorrect
10008=First delete submenu or button 10008=First delete submenu or button
10009=The original password is incorrect 10009=The original password is incorrect
10010=password error,you can try {0} times
10011=The superior department made a wrong choice 10011=The superior department made a wrong choice
10012=Upper menu cannot be for itself 10012=Upper menu cannot be for itself
10013=Data permission interface, which can only be a Map type parameter. 10013=Data permission interface, which can only be a Map type parameter.
10014=Please delete the subordinate department first 10014=Please delete the subordinate department first
10015=Please delete the user under the department first 10015=Please delete the user under the department first
10016=Deployment failed, no process
10017=Model diagram is incorrect, please check
10018=The export failed with the model ID {0}
10019=Please upload a file 10019=Please upload a file
10020=token cannot be empty 10020=token cannot be empty
10021=token is invalid, please log in again 10021=token is invalid, please log in again
10022=The account has been locked 10022=The account has been locked
10023=Please upload zip, bar, bpmn, bpmn20.xml format file
10024=Failed to upload file {0} 10024=Failed to upload file {0}
10025=Failed to send SMS {0}
10026=Mail template does not exist
10027=Redis service exception 10027=Redis service exception
10028=Timed task failed 10028=Timed task failed
10029=Cannot contain illegal characters 10029=Cannot contain illegal characters
10030=The parameter format is incorrect. Please use JSON format.
10031=Please complete the SMS configuration first. 10030=The password is less than {0} digits.
10032=Task has been signed and operation failed 10031=The password must consist of numbers, uppercase and lowercase letters, and special characters at the same time
10033=Non-existent process definition 10032=Exception in deleting this data
10034=Superior node does not exist
10035=Reject
10036=Rollback
10037=Tasks are not grouped and cannot be cancelled
10038=Upper area selection error
10039=Please delete the subordinate area first
10040=The process has been suspended and the instance cannot be started
10041=Multi-instance tasks cannot be rejected
10042=Tasks in multiple processes cannot be rejected
10043=Multi-instance tasks cannot be terminated
10044=There are tasks in multiple processes that cannot terminate the process
10045=END
10046=Multi-instance tasks cannot be rolled back
10047=There are multiple parallel tasks that cannot be rolled back
10048 = Login account, no right to delete
10055=Your password length is less than 8 digits
10056=Your password is not complex enough and needs to include both numbers, lowercase English, and uppercase English
@@ -2,6 +2,7 @@
500=\u670D\u52A1\u5668\u5185\u90E8\u5F02\u5E38 500=\u670D\u52A1\u5668\u5185\u90E8\u5F02\u5E38
401=\u672A\u6388\u6743 401=\u672A\u6388\u6743
403=\u62D2\u7EDD\u8BBF\u95EE\uFF0C\u6CA1\u6709\u6743\u9650 403=\u62D2\u7EDD\u8BBF\u95EE\uFF0C\u6CA1\u6709\u6743\u9650
10001={0}\u4E0D\u80FD\u4E3A\u7A7A 10001={0}\u4E0D\u80FD\u4E3A\u7A7A
10002=\u6570\u636E\u5E93\u4E2D\u5DF2\u5B58\u5728\u8BE5\u8BB0\u5F55 10002=\u6570\u636E\u5E93\u4E2D\u5DF2\u5B58\u5728\u8BE5\u8BB0\u5F55
10003=\u83B7\u53D6\u53C2\u6570\u5931\u8D25 10003=\u83B7\u53D6\u53C2\u6570\u5931\u8D25
@@ -11,44 +12,23 @@
10007=\u9A8C\u8BC1\u7801\u4E0D\u6B63\u786E 10007=\u9A8C\u8BC1\u7801\u4E0D\u6B63\u786E
10008=\u5148\u5220\u9664\u5B50\u83DC\u5355\u6216\u6309\u94AE 10008=\u5148\u5220\u9664\u5B50\u83DC\u5355\u6216\u6309\u94AE
10009=\u539F\u5BC6\u7801\u4E0D\u6B63\u786E 10009=\u539F\u5BC6\u7801\u4E0D\u6B63\u786E
10010=\u8D26\u53F7\u6216\u5BC6\u7801\u4E0D\u6B63\u786E,\u60A8\u8FD8\u6709\u53EF\u4EE5\u5C1D\u8BD5{0}\u6B21
10011=\u4E0A\u7EA7\u90E8\u95E8\u9009\u62E9\u9519\u8BEF 10011=\u4E0A\u7EA7\u90E8\u95E8\u9009\u62E9\u9519\u8BEF
10012=\u4E0A\u7EA7\u83DC\u5355\u4E0D\u80FD\u4E3A\u81EA\u8EAB 10012=\u4E0A\u7EA7\u83DC\u5355\u4E0D\u80FD\u4E3A\u81EA\u8EAB
10013=\u6570\u636E\u6743\u9650\u63A5\u53E3\uFF0C\u53EA\u80FD\u662FMap\u7C7B\u578B\u53C2\u6570 10013=\u6570\u636E\u6743\u9650\u63A5\u53E3\uFF0C\u53EA\u80FD\u662FMap\u7C7B\u578B\u53C2\u6570
10014=\u8BF7\u5148\u5220\u9664\u4E0B\u7EA7\u90E8\u95E8 10014=\u8BF7\u5148\u5220\u9664\u4E0B\u7EA7\u90E8\u95E8
10015=\u8BF7\u5148\u5220\u9664\u90E8\u95E8\u4E0B\u7684\u7528\u6237 10015=\u8BF7\u5148\u5220\u9664\u90E8\u95E8\u4E0B\u7684\u7528\u6237
10016=\u90E8\u7F72\u5931\u8D25\uFF0C\u6CA1\u6709\u6D41\u7A0B
10017=\u6A21\u578B\u56FE\u4E0D\u6B63\u786E\uFF0C\u8BF7\u68C0\u67E5
10018=\u5BFC\u51FA\u5931\u8D25\uFF0C\u6A21\u578BID\u4E3A{0}
10019=\u8BF7\u4E0A\u4F20\u6587\u4EF6 10019=\u8BF7\u4E0A\u4F20\u6587\u4EF6
10020=token\u4E0D\u80FD\u4E3A\u7A7A 10020=token\u4E0D\u80FD\u4E3A\u7A7A
10021=token\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 10021=token\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55
10022=\u8D26\u53F7\u5DF2\u88AB\u9501\u5B9A 10022=\u8D26\u53F7\u5DF2\u88AB\u9501\u5B9A
10023=\u8BF7\u4E0A\u4F20zip\u3001bar\u3001bpmn\u3001bpmn20.xml\u683C\u5F0F\u6587\u4EF6
10024=\u4E0A\u4F20\u6587\u4EF6\u5931\u8D25{0} 10024=\u4E0A\u4F20\u6587\u4EF6\u5931\u8D25{0}
10025=\u53D1\u9001\u77ED\u4FE1\u5931\u8D25{0}
10026=\u90AE\u4EF6\u6A21\u677F\u4E0D\u5B58\u5728
10027=Redis\u670D\u52A1\u5F02\u5E38 10027=Redis\u670D\u52A1\u5F02\u5E38
10028=\u5B9A\u65F6\u4EFB\u52A1\u5931\u8D25 10028=\u5B9A\u65F6\u4EFB\u52A1\u5931\u8D25
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26 10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
10030=\u53C2\u6570\u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u8BF7\u4F7F\u7528JSON\u683C\u5F0F 10030=\u5BC6\u7801\u957F\u5EA6\u4E0D\u8DB3{0}\u4F4D
10031=\u8BF7\u5148\u5B8C\u6210\u77ED\u4FE1\u914D\u7F6E 10031=\u5BC6\u7801\u5FC5\u987B\u540C\u65F6\u5305\u542B\u6570\u5B57\u3001\u5927\u5C0F\u5B57\u6BCD\u548C\u7279\u6B8A\u5B57\u7B26\u7EC4\u6210
10032=\u4EFB\u52A1\u5DF2\u88AB\u7B7E\u6536\uFF0C\u64CD\u4F5C\u5931\u8D25 10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38
10033=\u4E0D\u5B58\u5728\u7684\u6D41\u7A0B\u5B9A\u4E49
10034=\u4E0A\u7EA7\u8282\u70B9\u4E0D\u5B58\u5728
10035=\u9A73\u56DE
10036=\u56DE\u9000
10037=\u4EFB\u52A1\u6CA1\u6709\u5206\u7EC4\uFF0C\u65E0\u6CD5\u53D6\u6D88\u8BA4\u9886
10038=\u4E0A\u7EA7\u533A\u57DF\u9009\u62E9\u9519\u8BEF
10039=\u8BF7\u5148\u5220\u9664\u4E0B\u7EA7\u533A\u57DF
10040=\u6D41\u7A0B\u5DF2\u6302\u8D77\uFF0C\u4E0D\u80FD\u542F\u52A8\u5B9E\u4F8B
10041=\u591A\u5B9E\u4F8B\u4EFB\u52A1\u4E0D\u80FD\u9A73\u56DE
10042=\u5B58\u5728\u591A\u4E2A\u5904\u7406\u4E2D\u7684\u4EFB\u52A1\uFF0C\u4E0D\u80FD\u9A73\u56DE
10043=\u591A\u5B9E\u4F8B\u4EFB\u52A1\u4E0D\u80FD\u7EC8\u6B62
10044=\u5B58\u5728\u591A\u4E2A\u5904\u7406\u4E2D\u7684\u4EFB\u52A1\uFF0C\u4E0D\u80FD\u7EC8\u6B62\u6D41\u7A0B
10045=\u7EC8\u6B62
10046=\u591A\u5B9E\u4F8B\u4EFB\u52A1\u4E0D\u80FD\u56DE\u9000
10047=\u5B58\u5728\u591A\u4E2A\u5E76\u884C\u6267\u884C\u7684\u4EFB\u52A1\uFF0C\u4E0D\u80FD\u56DE\u9000
10048=\u767B\u5F55\u8D26\u53F7\uFF0C\u65E0\u6743\u5220\u9664
10055=\u60A8\u7684\u5BC6\u7801\u957F\u5EA6\u4E0D\u591F8\u4F4D
10056=\u60A8\u7684\u5BC6\u7801\u590D\u6742\u5EA6\u4E0D\u591F\uFF0C\u9700\u8981\u540C\u65F6\u5305\u542B\u6570\u5B57,\u5C0F\u5199\u82F1\u6587,\u5927\u5199\u82F1\u6587
@@ -2,6 +2,7 @@
500=\u670D\u52D9\u5668\u5167\u90E8\u7570\u5E38 500=\u670D\u52D9\u5668\u5167\u90E8\u7570\u5E38
401=\u672A\u6388\u6B0A 401=\u672A\u6388\u6B0A
403=\u62D2\u7D55\u8A2A\u554F\uFF0C\u6C92\u6709\u6B0A\u9650 403=\u62D2\u7D55\u8A2A\u554F\uFF0C\u6C92\u6709\u6B0A\u9650
10001={0}\u4E0D\u80FD\u70BA\u7A7A 10001={0}\u4E0D\u80FD\u70BA\u7A7A
10002=\u6578\u64DA\u5EAB\u4E2D\u5DF2\u5B58\u5728\u8A72\u8A18\u9304 10002=\u6578\u64DA\u5EAB\u4E2D\u5DF2\u5B58\u5728\u8A72\u8A18\u9304
10003=\u7372\u53D6\u53C3\u6578\u5931\u6557 10003=\u7372\u53D6\u53C3\u6578\u5931\u6557
@@ -11,44 +12,23 @@
10007=\u9A57\u8B49\u78BC\u4E0D\u6B63\u78BA 10007=\u9A57\u8B49\u78BC\u4E0D\u6B63\u78BA
10008=\u5148\u522A\u9664\u5B50\u83DC\u55AE\u6216\u6309\u9215 10008=\u5148\u522A\u9664\u5B50\u83DC\u55AE\u6216\u6309\u9215
10009=\u539F\u5BC6\u78BC\u4E0D\u6B63\u78BA 10009=\u539F\u5BC6\u78BC\u4E0D\u6B63\u78BA
10010=\u8CEC\u865F\u6216\u5BC6\u78BC\u4E0D\u6B63\u78BA,\u60A8\u9084\u6709\u53EF\u4EE5\u5617\u8A66{0}\u6B21
10011=\u4E0A\u7D1A\u90E8\u9580\u9078\u64C7\u932F\u8AA4 10011=\u4E0A\u7D1A\u90E8\u9580\u9078\u64C7\u932F\u8AA4
10012=\u4E0A\u7D1A\u83DC\u55AE\u4E0D\u80FD\u70BA\u81EA\u8EAB 10012=\u4E0A\u7D1A\u83DC\u55AE\u4E0D\u80FD\u70BA\u81EA\u8EAB
10013=\u6578\u64DA\u6B0A\u9650\u63A5\u53E3\uFF0C\u53EA\u80FD\u662FMap\u985E\u578B\u53C3\u6578 10013=\u6578\u64DA\u6B0A\u9650\u63A5\u53E3\uFF0C\u53EA\u80FD\u662FMap\u985E\u578B\u53C3\u6578
10014=\u8ACB\u5148\u522A\u9664\u4E0B\u7D1A\u90E8\u9580 10014=\u8ACB\u5148\u522A\u9664\u4E0B\u7D1A\u90E8\u9580
10015=\u8ACB\u5148\u522A\u9664\u90E8\u9580\u4E0B\u7684\u7528\u6236 10015=\u8ACB\u5148\u522A\u9664\u90E8\u9580\u4E0B\u7684\u7528\u6236
10016=\u90E8\u7F72\u5931\u6557\uFF0C\u6C92\u6709\u6D41\u7A0B
10017=\u6A21\u578B\u5716\u4E0D\u6B63\u78BA\uFF0C\u8ACB\u6AA2\u67E5
10018=\u5C0E\u51FA\u5931\u6557\uFF0C\u6A21\u578BID\u70BA{0}
10019=\u8ACB\u4E0A\u50B3\u6587\u4EF6 10019=\u8ACB\u4E0A\u50B3\u6587\u4EF6
10020=token\u4E0D\u80FD\u70BA\u7A7A 10020=token\u4E0D\u80FD\u70BA\u7A7A
10021=token\u5931\u6548\uFF0C\u8ACB\u91CD\u65B0\u767B\u9304 10021=token\u5931\u6548\uFF0C\u8ACB\u91CD\u65B0\u767B\u9304
10022=\u8CEC\u865F\u5DF2\u88AB\u9396\u5B9A 10022=\u8CEC\u865F\u5DF2\u88AB\u9396\u5B9A
10023=\u8ACB\u4E0A\u50B3zip\u3001bar\u3001bpmn\u3001bpmn20.xml\u683C\u5F0F\u6587\u4EF6
10024=\u4E0A\u50B3\u6587\u4EF6\u5931\u6557{0} 10024=\u4E0A\u50B3\u6587\u4EF6\u5931\u6557{0}
10025=\u767C\u9001\u77ED\u4FE1\u5931\u6557{0}
10026=\u90F5\u4EF6\u6A21\u677F\u4E0D\u5B58\u5728
10027=Redis\u670D\u52D9\u7570\u5E38 10027=Redis\u670D\u52D9\u7570\u5E38
10028=\u5B9A\u6642\u4EFB\u52D9\u5931\u6557 10028=\u5B9A\u6642\u4EFB\u52D9\u5931\u6557
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26 10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
10030=\u53C3\u6578\u683C\u5F0F\u4E0D\u6B63\u78BA\uFF0C\u8ACB\u4F7F\u7528JSON\u683C\u5F0F 10030=\u5BC6\u78BC\u9577\u5EA6\u4E0D\u8DB3{0}\u4F4D
10031=\u8ACB\u5148\u5B8C\u6210\u77ED\u4FE1\u914D\u7F6E 10031=\u5BC6\u78BC\u5FC5\u9808\u540C\u6642\u5305\u542B\u6578\u5B57\u3001\u5927\u5C0F\u5B57\u6BCD\u548C\u7279\u6B8A\u5B57\u7B26\u7D44\u6210
10032=\u4EFB\u52D9\u5DF2\u88AB\u7C3D\u6536\uFF0C\u64CD\u4F5C\u5931\u6557 10032=\u522A\u9664\u6B64\u6578\u64DA\u7570\u5E38
10033=\u4E0D\u5B58\u5728\u7684\u6D41\u7A0B\u5B9A\u7FA9
10034=\u4E0A\u7D1A\u7BC0\u9EDE\u4E0D\u5B58\u5728
10035=\u99C1\u56DE
10036=\u56DE\u9000
10037=\u4EFB\u52D9\u6C92\u6709\u5206\u7D44\uFF0C\u7121\u6CD5\u53D6\u6D88\u8A8D\u9818
10038=\u4E0A\u7D1A\u5340\u57DF\u9078\u64C7\u932F\u8AA4
10039=\u8ACB\u5148\u522A\u9664\u4E0B\u7D1A\u5340\u57DF
10040=\u6D41\u7A0B\u5DF2\u639B\u8D77\uFF0C\u4E0D\u80FD\u555F\u52D5\u5BE6\u4F8B
10041=\u591A\u5BE6\u4F8B\u4EFB\u52D9\u4E0D\u80FD\u99C1\u56DE
10042=\u5B58\u5728\u591A\u500B\u8655\u7406\u4E2D\u7684\u4EFB\u52D9\uFF0C\u4E0D\u80FD\u99C1\u56DE
10043=\u591A\u5BE6\u4F8B\u4EFB\u52D9\u4E0D\u80FD\u7D42\u6B62
10044=\u5B58\u5728\u591A\u500B\u8655\u7406\u4E2D\u7684\u4EFB\u52D9\uFF0C\u4E0D\u80FD\u7D42\u6B62\u6D41\u7A0B
10045=\u7D42\u6B62
10046=\u591A\u5BE6\u4F8B\u4EFB\u52D9\u4E0D\u80FD\u56DE\u9000
10047=\u5B58\u5728\u591A\u500B\u4E26\u884C\u57F7\u884C\u7684\u4EFB\u52D9\uFF0C\u4E0D\u80FD\u56DE\u9000
10048=\u767B\u5165\u5E33\u865F\uFF0C\u7121\u6B0A\u5220\u9664
10055=\u60A8\u7684\u5BC6\u78BC\u9577\u5EA6\u4E0D\u59208\u4F4D
10056=\u60A8\u7684\u5BC6\u78BC\u5FA9\u96DC\u5EA6\u4E0D\u5920\uFF0C\u9700\u8981\u540C\u6642\u5305\u542B\u6578\u5B57,\u5C0F\u5BEB\u82F1\u6587,\u5927\u5BEB\u82F1\u6587
@@ -1,9 +1,13 @@
#\u7B80\u4F53\u4E2D\u6587 #\u7B80\u4F53\u4E2D\u6587
id.require=ID\u4E0D\u80FD\u4E3A\u7A7A id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
id.null=ID\u5FC5\u987B\u4E3A\u7A7A id.null=ID\u5FC5\u987B\u4E3A\u7A7A
pid.require=\u4E0A\u7EA7ID\uFF0C\u4E0D\u80FD\u4E3A\u7A7A
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0 sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysdict.label.require=\u5B57\u5178\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A
sysparams.paramcode.require=\u53C2\u6570\u7F16\u7801\u4E0D\u80FD\u4E3A\u7A7A sysparams.paramcode.require=\u53C2\u6570\u7F16\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysparams.paramvalue.require=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A sysparams.paramvalue.require=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A
@@ -11,99 +15,8 @@ sysuser.username.require=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A
sysuser.password.require=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A sysuser.password.require=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.realname.require=\u59D3\u540D\u4E0D\u80FD\u4E3A\u7A7A sysuser.realname.require=\u59D3\u540D\u4E0D\u80FD\u4E3A\u7A7A
sysuser.gender.range=\u6027\u522B\u53D6\u503C\u8303\u56F40~2 sysuser.gender.range=\u6027\u522B\u53D6\u503C\u8303\u56F40~2
sysuser.email.require=\u90AE\u7BB1\u4E0D\u80FD\u4E3A\u7A7A
sysuser.email.error=\u90AE\u7BB1\u683C\u5F0F\u4E0D\u6B63\u786E sysuser.email.error=\u90AE\u7BB1\u683C\u5F0F\u4E0D\u6B63\u786E
sysuser.mobile.require=\u624B\u673A\u53F7\u4E0D\u80FD\u4E3A\u7A7A
sysuser.deptId.require=\u90E8\u95E8\u4E0D\u80FD\u4E3A\u7A7A sysuser.deptId.require=\u90E8\u95E8\u4E0D\u80FD\u4E3A\u7A7A
sysuser.superadmin.range=\u8D85\u7EA7\u7BA1\u7406\u5458\u53D6\u503C\u8303\u56F40~1
sysuser.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1 sysuser.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1
sysuser.captcha.require=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A sysuser.captcha.require=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
sysmenu.pid.require=\u8BF7\u9009\u62E9\u4E0A\u7EA7\u83DC\u5355
sysmenu.name.require=\u83DC\u5355\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysmenu.type.range=\u83DC\u5355\u7C7B\u578B\u53D6\u503C\u8303\u56F40~1
sysmenu.openstyle.range=\u83DC\u5355\u6253\u5F00\u65B9\u5F0F\u53D6\u503C\u8303\u56F40~1
sysdept.pid.require=\u8BF7\u9009\u62E9\u4E0A\u7EA7\u90E8\u95E8
sysdept.name.require=\u90E8\u95E8\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysrole.name.require=\u89D2\u8272\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysdict.label.require=\u5B57\u5178\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A
schedule.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1
schedule.cron.require=cron\u8868\u8FBE\u5F0F\u4E0D\u80FD\u4E3A\u7A7A
schedule.bean.require=bean\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
oss.type.range=\u7C7B\u578B\u53D6\u503C\u8303\u56F41~5
aliyun.accesskeyid.require=\u963F\u91CC\u4E91AccessKeyId\u4E0D\u80FD\u4E3A\u7A7A
aliyun.accesskeysecret.require=\u963F\u91CC\u4E91AccessKeySecret\u4E0D\u80FD\u4E3A\u7A7A
aliyun.signname.require=\u963F\u91CC\u4E91\u77ED\u4FE1\u7B7E\u540D\u4E0D\u80FD\u4E3A\u7A7A
aliyun.templatecode.require=\u963F\u91CC\u4E91\u77ED\u4FE1\u6A21\u677F\u4E0D\u80FD\u4E3A\u7A7A
aliyun.domain.require=\u963F\u91CC\u4E91\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
aliyun.domain.url=\u963F\u91CC\u4E91\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
aliyun.endPoint.require=\u963F\u91CC\u4E91EndPoint\u4E0D\u80FD\u4E3A\u7A7A
aliyun.bucketname.require=\u963F\u91CC\u4E91BucketName\u4E0D\u80FD\u4E3A\u7A7A
qcloud.appid.require=\u817E\u8BAF\u4E91AppId\u4E0D\u80FD\u4E3A\u7A7A
qcloud.appkey.require=\u817E\u8BAF\u4E91AppKey\u4E0D\u80FD\u4E3A\u7A7A
qcloud.secretId.require=\u817E\u8BAF\u4E91SecretId\u4E0D\u80FD\u4E3A\u7A7A
qcloud.secretkey.require=\u817E\u8BAF\u4E91SecretKey\u4E0D\u80FD\u4E3A\u7A7A
qcloud.signname.require=\u817E\u8BAF\u4E91\u77ED\u4FE1\u7B7E\u540D\u4E0D\u80FD\u4E3A\u7A7A
qcloud.templateid.require=\u817E\u8BAF\u4E91\u77ED\u4FE1\u6A21\u677FID\u4E0D\u80FD\u4E3A\u7A7A
qcloud.domain.require=\u817E\u8BAF\u4E91\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
qcloud.domain.url=\u817E\u8BAF\u4E91\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
qcloud.bucketname.require=\u817E\u8BAF\u4E91BucketName\u4E0D\u80FD\u4E3A\u7A7A
qcloud.region.require=\u6240\u5C5E\u5730\u533A\u4E0D\u80FD\u4E3A\u7A7A
qiniu.domain.require=\u4E03\u725B\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
qiniu.domain.url=\u4E03\u725B\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
qiniu.accesskey.require=\u4E03\u725BAccessKey\u4E0D\u80FD\u4E3A\u7A7A
qiniu.secretkey.require=\u4E03\u725BSecretKey\u4E0D\u80FD\u4E3A\u7A7A
qiniu.bucketname.require=\u4E03\u725B\u7A7A\u95F4\u540D\u4E0D\u80FD\u4E3A\u7A7A
qiniu.templateId.require=\u4E03\u725B\u6A21\u677FID\u4E0D\u80FD\u4E3A\u7A7A
fastdfs.domain.require=FastDFS\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
fastdfs.domain.url=FastDFS\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
local.domain.require=\u672C\u5730\u4E0A\u4F20\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
local.domain.url=\u672C\u5730\u4E0A\u4F20\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
local.path.url=\u5B58\u50A8\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A
minio.endPoint.require=Minio EndPoint\u4E0D\u80FD\u4E3A\u7A7A
minio.accesskey.require=Minio AccessKey\u4E0D\u80FD\u4E3A\u7A7A
minio.secretkey.require=Minio SecretKey\u4E0D\u80FD\u4E3A\u7A7A
minio.bucketname.require=Minio BucketName\u4E0D\u80FD\u4E3A\u7A7A
sms.platform.range=\u5E73\u53F0\u7C7B\u578B\u53D6\u503C\u8303\u56F41~2
email.smtp.require=SMTP\u4E0D\u80FD\u4E3A\u7A7A
email.port.require=\u7AEF\u53E3\u53F7\u4E0D\u80FD\u4E3A\u7A7A
email.username.require=\u90AE\u7BB1\u8D26\u53F7\u4E0D\u80FD\u4E3A\u7A7A
email.password.require=\u90AE\u7BB1\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
mail.name.require=\u6A21\u677F\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
mail.subject.require=\u90AE\u4EF6\u4E3B\u9898\u4E0D\u80FD\u4E3A\u7A7A
mail.content.require=\u90AE\u4EF6\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A
model.name.require=\u6A21\u578B\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
model.key.require=\u6A21\u578B\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
news.title.require=\u6807\u9898\u4E0D\u80FD\u4E3A\u7A7A
news.content.require=\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A
news.pubdate.require=\u53D1\u5E03\u65F6\u95F4\u4E0D\u80FD\u4E3A\u7A7A
region.id.require=\u533A\u57DF\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
region.pid.require=\u4E0A\u7EA7\u533A\u57DF\u4E0D\u80FD\u4E3A\u7A7A
region.name.require=\u533A\u57DF\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
processBizRoute.procDefId.require=\u6D41\u7A0B\u5B9A\u4E49ID\u4E0D\u80FD\u4E3A\u7A7A
processBizRoute.bizRoute.require=\u4E1A\u52A1\u8DEF\u7531\u4E0D\u80FD\u4E3A\u7A7A
processBizRoute.procDefKey.require=\u6D41\u7A0B\u5B9A\u4E49KEY\u4E0D\u80FD\u4E3A\u7A7A
processBizRoute.version.require=\u6D41\u7A0B\u5B9A\u4E49\u7248\u672C\u53F7\u4E0D\u80FD\u4E3A\u7A7A
ProcessStart.processDefinitionKey.require=\u6D41\u7A0B\u5B9A\u4E49KEY\u4E0D\u80FD\u4E3A\u7A7A
ProcessStart.businessKey.require=\u4E1A\u52A1KEY\u4E0D\u80FD\u4E3A\u7A7A
@@ -1,9 +1,13 @@
#English #English
id.require=ID can not be empty id.require=ID can not be empty
id.null=ID has to be empty id.null=ID has to be empty
pid.require=Parent ID, cannot be empty
sort.number=The sort value cannot be less than 0 sort.number=The sort value cannot be less than 0
sysdict.type.require=The dictionary type cannot be empty
sysdict.name.require=The dictionary name cannot be empty
sysdict.label.require=Dictionary tag cannot be empty
sysparams.paramcode.require=Parameter encoding cannot be empty sysparams.paramcode.require=Parameter encoding cannot be empty
sysparams.paramvalue.require=Parameter values cannot be empty sysparams.paramvalue.require=Parameter values cannot be empty
@@ -11,100 +15,8 @@ sysuser.username.require=The username cannot be empty
sysuser.password.require=The password cannot be empty sysuser.password.require=The password cannot be empty
sysuser.realname.require=The realname cannot be empty sysuser.realname.require=The realname cannot be empty
sysuser.gender.range=Gender ranges from 0 to 2 sysuser.gender.range=Gender ranges from 0 to 2
sysuser.email.require=Mailbox cannot be empty
sysuser.email.error=Incorrect email format sysuser.email.error=Incorrect email format
sysuser.mobile.require=The phone number cannot be empty
sysuser.deptId.require=Departments cannot be empty sysuser.deptId.require=Departments cannot be empty
sysuser.superadmin.range=Super administrator values range from 0 to 1
sysuser.status.range=State ranges from 0 to 1 sysuser.status.range=State ranges from 0 to 1
sysuser.captcha.require=The captcha cannot be empty sysuser.captcha.require=The captcha cannot be empty
sysuser.uuid.require=The unique identifier cannot be empty sysuser.uuid.require=The unique identifier cannot be empty
sysmenu.pid.require=Please select superior menu
sysmenu.name.require=Menu name cannot be empty
sysmenu.type.range=Menu type ranges from 0 to 1
sysmenu.openstyle.range=Menu open style ranges from 0 to 1
sysdept.pid.require=Please select superior department
sysdept.name.require=The department name cannot be empty
sysrole.name.require=The role name cannot be empty
sysdict.type.require=The dictionary type cannot be empty
sysdict.name.require=The dictionary name cannot be empty
sysdict.label.require=Dictionary tag cannot be empty
schedule.status.range=Status ranges from 0 to 1
schedule.cron.require=Cron expression cannot be empty
schedule.bean.require=Bean name cannot be empty
oss.type.range=Type ranges from 1 to 5
aliyun.accesskeyid.require=Aliyun AccessKeyId cannot be empty
aliyun.accesskeysecret.require=Aliyun AccessKeySecret cannot be empty
aliyun.signname.require=Aliyun message signature cannot be empty
aliyun.templatecode.require=Aliyun message template cannot be empty
aliyun.domain.require=Aliyun bound domain name cannot be empty
aliyun.domain.url=Aliyun binding domain name format is incorrect
aliyun.endPoint.require=The aliyun EndPoint cannot be empty
aliyun.bucketname.require=Aliyun BucketName cannot be empty
qcloud.appid.require=Tencent cloud AppId cannot be empty
qcloud.appkey.require=Tencent cloud AppKey cannot be empty
qcloud.secretId.require=Tencent cloud SecretId cannot be empty
qcloud.secretkey.require=Tencent cloud SecretKey cannot be empty
qcloud.signname.require=Tencent cloud SMS signature cannot be empty
qcloud.templateid.require=Tencent cloud SMS template ID cannot be empty
qcloud.domain.require=Tencent cloud bound domain name cannot be empty
qcloud.domain.url=Tencent cloud binding domain name format is incorrect
qcloud.bucketname.require=Tencent cloud BucketName cannot be empty
qcloud.region.require=The region cannot be empty
qiniu.domain.require=Bound domain name cannot be empty
qiniu.domain.url=Binding domain name format is incorrect
qiniu.accesskey.require=The AccessKey cannot be empty
qiniu.secretkey.require=The SecretKey of seven cows cannot be empty
qiniu.bucketname.require=Space names cannot be empty
qiniu.templateId.require=Template ID cannot be empty
fastdfs.domain.require=FastDFS bound domain name cannot be empty
fastdfs.domain.url=FastDFS bound domain name format is incorrect
local.domain.require=Local upload bound domain name cannot be empty
local.domain.url=The domain name bound for local upload is not formatted correctly
local.path.url=The storage directory cannot be empty
minio.endPoint.require=Minio EndPoint cannot be empty
minio.accesskey.require=Minio AccessKey cannot be empty
minio.secretkey.require=Minio SecretKey cannot be empty
minio.bucketname.require=Minio BucketName cannot be empty
sms.platform.range=Platform type ranges from 1 to 2
email.smtp.require=SMTP cannot be empty
email.port.require=Port number cannot be empty
email.username.require=The email account cannot be empty
email.password.require=The password cannot be empty
mail.name.require=The template name cannot be empty
mail.subject.require=Mail subject cannot be empty
mail.content.require=Message text cannot be empty
model.name.require=The model name cannot be empty
model.key.require=The model key cannot be empty
news.title.require=The title cannot be empty
news.content.require=Content cannot be empty
news.pubdate.require=The release time cannot be empty
region.id.require=Region ID cannot be empty
region.pid.require=Upper area cannot be empty
region.name.require=Region name cannot be empty
processBizRoute.procDefId.require=Process Definition ID cannot be empty
processBizRoute.bizRoute.require=Service routing cannot be empty
processBizRoute.procDefKey.require=Process Definition KEY cannot be empty
processBizRoute.version.require=Process Definition Version number cannot be empty
ProcessStart.processDefinitionKey.require=Process Definition KEY cannot be empty
ProcessStart.businessKey.require=Business KEY cannot be empty
@@ -1,9 +1,13 @@
#\u7B80\u4F53\u4E2D\u6587 #\u7B80\u4F53\u4E2D\u6587
id.require=ID\u4E0D\u80FD\u4E3A\u7A7A id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
id.null=ID\u5FC5\u987B\u4E3A\u7A7A id.null=ID\u5FC5\u987B\u4E3A\u7A7A
pid.require=\u4E0A\u7EA7ID\uFF0C\u4E0D\u80FD\u4E3A\u7A7A
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0 sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysdict.label.require=\u5B57\u5178\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A
sysparams.paramcode.require=\u53C2\u6570\u7F16\u7801\u4E0D\u80FD\u4E3A\u7A7A sysparams.paramcode.require=\u53C2\u6570\u7F16\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysparams.paramvalue.require=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A sysparams.paramvalue.require=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A
@@ -11,99 +15,9 @@ sysuser.username.require=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A
sysuser.password.require=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A sysuser.password.require=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.realname.require=\u59D3\u540D\u4E0D\u80FD\u4E3A\u7A7A sysuser.realname.require=\u59D3\u540D\u4E0D\u80FD\u4E3A\u7A7A
sysuser.gender.range=\u6027\u522B\u53D6\u503C\u8303\u56F40~2 sysuser.gender.range=\u6027\u522B\u53D6\u503C\u8303\u56F40~2
sysuser.email.require=\u90AE\u7BB1\u4E0D\u80FD\u4E3A\u7A7A
sysuser.email.error=\u90AE\u7BB1\u683C\u5F0F\u4E0D\u6B63\u786E sysuser.email.error=\u90AE\u7BB1\u683C\u5F0F\u4E0D\u6B63\u786E
sysuser.mobile.require=\u624B\u673A\u53F7\u4E0D\u80FD\u4E3A\u7A7A
sysuser.deptId.require=\u90E8\u95E8\u4E0D\u80FD\u4E3A\u7A7A sysuser.deptId.require=\u90E8\u95E8\u4E0D\u80FD\u4E3A\u7A7A
sysuser.superadmin.range=\u8D85\u7EA7\u7BA1\u7406\u5458\u53D6\u503C\u8303\u56F40~1
sysuser.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1 sysuser.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1
sysuser.captcha.require=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A sysuser.captcha.require=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
sysmenu.pid.require=\u8BF7\u9009\u62E9\u4E0A\u7EA7\u83DC\u5355
sysmenu.name.require=\u83DC\u5355\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysmenu.type.range=\u83DC\u5355\u7C7B\u578B\u53D6\u503C\u8303\u56F40~1
sysmenu.openstyle.range=\u83DC\u5355\u6253\u5F00\u65B9\u5F0F\u53D6\u503C\u8303\u56F40~1
sysdept.pid.require=\u8BF7\u9009\u62E9\u4E0A\u7EA7\u90E8\u95E8
sysdept.name.require=\u90E8\u95E8\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysrole.name.require=\u89D2\u8272\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysdict.label.require=\u5B57\u5178\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A
schedule.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1
schedule.cron.require=cron\u8868\u8FBE\u5F0F\u4E0D\u80FD\u4E3A\u7A7A
schedule.bean.require=bean\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
oss.type.range=\u7C7B\u578B\u53D6\u503C\u8303\u56F41~5
aliyun.accesskeyid.require=\u963F\u91CC\u4E91AccessKeyId\u4E0D\u80FD\u4E3A\u7A7A
aliyun.accesskeysecret.require=\u963F\u91CC\u4E91AccessKeySecret\u4E0D\u80FD\u4E3A\u7A7A
aliyun.signname.require=\u963F\u91CC\u4E91\u77ED\u4FE1\u7B7E\u540D\u4E0D\u80FD\u4E3A\u7A7A
aliyun.templatecode.require=\u963F\u91CC\u4E91\u77ED\u4FE1\u6A21\u677F\u4E0D\u80FD\u4E3A\u7A7A
aliyun.domain.require=\u963F\u91CC\u4E91\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
aliyun.domain.url=\u963F\u91CC\u4E91\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
aliyun.endPoint.require=\u963F\u91CC\u4E91EndPoint\u4E0D\u80FD\u4E3A\u7A7A
aliyun.bucketname.require=\u963F\u91CC\u4E91BucketName\u4E0D\u80FD\u4E3A\u7A7A
qcloud.appid.require=\u817E\u8BAF\u4E91AppId\u4E0D\u80FD\u4E3A\u7A7A
qcloud.appkey.require=\u817E\u8BAF\u4E91AppKey\u4E0D\u80FD\u4E3A\u7A7A
qcloud.secretId.require=\u817E\u8BAF\u4E91SecretId\u4E0D\u80FD\u4E3A\u7A7A
qcloud.secretkey.require=\u817E\u8BAF\u4E91SecretKey\u4E0D\u80FD\u4E3A\u7A7A
qcloud.signname.require=\u817E\u8BAF\u4E91\u77ED\u4FE1\u7B7E\u540D\u4E0D\u80FD\u4E3A\u7A7A
qcloud.templateid.require=\u817E\u8BAF\u4E91\u77ED\u4FE1\u6A21\u677FID\u4E0D\u80FD\u4E3A\u7A7A
qcloud.domain.require=\u817E\u8BAF\u4E91\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
qcloud.domain.url=\u817E\u8BAF\u4E91\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
qcloud.bucketname.require=\u817E\u8BAF\u4E91BucketName\u4E0D\u80FD\u4E3A\u7A7A
qcloud.region.require=\u6240\u5C5E\u5730\u533A\u4E0D\u80FD\u4E3A\u7A7A
qiniu.domain.require=\u4E03\u725B\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
qiniu.domain.url=\u4E03\u725B\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
qiniu.accesskey.require=\u4E03\u725BAccessKey\u4E0D\u80FD\u4E3A\u7A7A
qiniu.secretkey.require=\u4E03\u725BSecretKey\u4E0D\u80FD\u4E3A\u7A7A
qiniu.bucketname.require=\u4E03\u725B\u7A7A\u95F4\u540D\u4E0D\u80FD\u4E3A\u7A7A
qiniu.templateId.require=\u4E03\u725B\u6A21\u677FID\u4E0D\u80FD\u4E3A\u7A7A
fastdfs.domain.require=FastDFS\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
fastdfs.domain.url=FastDFS\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
local.domain.require=\u672C\u5730\u4E0A\u4F20\u7ED1\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u4E3A\u7A7A
local.domain.url=\u672C\u5730\u4E0A\u4F20\u7ED1\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u786E
local.path.url=\u5B58\u50A8\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A
minio.endPoint.require=Minio EndPoint\u4E0D\u80FD\u4E3A\u7A7A
minio.accesskey.require=Minio AccessKey\u4E0D\u80FD\u4E3A\u7A7A
minio.secretkey.require=Minio SecretKey\u4E0D\u80FD\u4E3A\u7A7A
minio.bucketname.require=Minio BucketName\u4E0D\u80FD\u4E3A\u7A7A
sms.platform.range=\u5E73\u53F0\u7C7B\u578B\u53D6\u503C\u8303\u56F41~2
email.smtp.require=SMTP\u4E0D\u80FD\u4E3A\u7A7A
email.port.require=\u7AEF\u53E3\u53F7\u4E0D\u80FD\u4E3A\u7A7A
email.username.require=\u90AE\u7BB1\u8D26\u53F7\u4E0D\u80FD\u4E3A\u7A7A
email.password.require=\u90AE\u7BB1\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
mail.name.require=\u6A21\u677F\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
mail.subject.require=\u90AE\u4EF6\u4E3B\u9898\u4E0D\u80FD\u4E3A\u7A7A
mail.content.require=\u90AE\u4EF6\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A
model.name.require=\u6A21\u578B\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
model.key.require=\u6A21\u578B\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
news.title.require=\u6807\u9898\u4E0D\u80FD\u4E3A\u7A7A
news.content.require=\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A
news.pubdate.require=\u53D1\u5E03\u65F6\u95F4\u4E0D\u80FD\u4E3A\u7A7A
region.id.require=\u533A\u57DF\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
region.pid.require=\u4E0A\u7EA7\u533A\u57DF\u4E0D\u80FD\u4E3A\u7A7A
region.name.require=\u533A\u57DF\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
processBizRoute.procDefId.require=\u6D41\u7A0B\u5B9A\u4E49ID\u4E0D\u80FD\u4E3A\u7A7A
processBizRoute.bizRoute.require=\u4E1A\u52A1\u8DEF\u7531\u4E0D\u80FD\u4E3A\u7A7A
processBizRoute.procDefKey.require=\u6D41\u7A0B\u5B9A\u4E49KEY\u4E0D\u80FD\u4E3A\u7A7A
processBizRoute.version.require=\u6D41\u7A0B\u5B9A\u4E49\u7248\u672C\u53F7\u4E0D\u80FD\u4E3A\u7A7A
ProcessStart.processDefinitionKey.require=\u6D41\u7A0B\u5B9A\u4E49KEY\u4E0D\u80FD\u4E3A\u7A7A
ProcessStart.businessKey.require=\u4E1A\u52A1KEY\u4E0D\u80FD\u4E3A\u7A7A
@@ -1,110 +1,22 @@
#\u7E41\u4F53\u4E2D\u6587 #\u7B80\u4F53\u4E2D\u6587
id.require=ID\u4E0D\u80FD\u70BA\u7A7A id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
id.null=ID\u5FC5\u9808\u70BA\u7A7A id.null=ID\u5FC5\u987B\u4E3A\u7A7A
pid.require=\u4E0A\u7D1AID\uFF0C\u4E0D\u80FD\u70BA\u7A7A
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u65BC0
sysparams.paramcode.require=\u53C3\u6578\u7DE8\u78BC\u4E0D\u80FD\u70BA\u7A7A sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
sysparams.paramvalue.require=\u53C3\u6578\u503C\u4E0D\u80FD\u70BA\u7A7A
sysuser.username.require=\u7528\u6236\u540D\u4E0D\u80FD\u70BA\u7A7A sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
sysuser.password.require=\u5BC6\u78BC\u4E0D\u80FD\u70BA\u7A7A sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
sysuser.realname.require=\u59D3\u540D\u4E0D\u80FD\u70BA\u7A7A sysdict.label.require=\u5B57\u5178\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A
sysuser.gender.range=\u6027\u5225\u53D6\u503C\u7BC4\u570D0~2
sysuser.email.require=\u90F5\u7BB1\u4E0D\u80FD\u70BA\u7A7A
sysuser.email.error=\u90F5\u7BB1\u683C\u5F0F\u4E0D\u6B63\u78BA
sysuser.mobile.require=\u624B\u6A5F\u865F\u4E0D\u80FD\u70BA\u7A7A
sysuser.deptId.require=\u90E8\u9580\u4E0D\u80FD\u70BA\u7A7A
sysuser.superadmin.range=\u8D85\u7D1A\u7BA1\u7406\u54E1\u53D6\u503C\u7BC4\u570D0~1
sysuser.status.range=\u72C0\u614B\u53D6\u503C\u7BC4\u570D0~1
sysuser.captcha.require=\u9A57\u8B49\u78BC\u4E0D\u80FD\u70BA\u7A7A
sysuser.uuid.require=\u552F\u4E00\u6A19\u8B58\u4E0D\u80FD\u70BA\u7A7A
sysmenu.pid.require=\u8ACB\u9078\u64C7\u4E0A\u7D1A\u83DC\u55AE sysparams.paramcode.require=\u53C2\u6570\u7F16\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysmenu.name.require=\u83DC\u55AE\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A sysparams.paramvalue.require=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A
sysmenu.type.range=\u83DC\u55AE\u985E\u578B\u53D6\u503C\u7BC4\u570D0~1
sysmenu.openstyle.range=\u83DC\u55AE\u6253\u958B\u65B9\u5F0F\u53D6\u503C\u7BC4\u570D0~1
sysdept.pid.require=\u8ACB\u9078\u64C7\u4E0A\u7D1A\u90E8\u9580 sysuser.username.require=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A
sysdept.name.require=\u90E8\u9580\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A sysuser.password.require=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.realname.require=\u59D3\u540D\u4E0D\u80FD\u4E3A\u7A7A
sysrole.name.require=\u89D2\u8272\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A sysuser.gender.range=\u6027\u522B\u53D6\u503C\u8303\u56F40~2
sysuser.email.error=\u90AE\u7BB1\u683C\u5F0F\u4E0D\u6B63\u786E
sysdict.type.require=\u5B57\u5178\u985E\u578B\u4E0D\u80FD\u70BA\u7A7A sysuser.deptId.require=\u90E8\u95E8\u4E0D\u80FD\u4E3A\u7A7A
sysdict.name.require=\u5B57\u5178\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A sysuser.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1
sysdict.label.require=\u5B57\u5178\u6A19\u7C64\u4E0D\u80FD\u70BA\u7A7A sysuser.captcha.require=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
schedule.status.range=\u72C0\u614B\u53D6\u503C\u7BC4\u570D0~1
schedule.cron.require=cron\u8868\u9054\u5F0F\u4E0D\u80FD\u70BA\u7A7A
schedule.bean.require=bean\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A
oss.type.range=\u985E\u578B\u53D6\u503C\u7BC4\u570D1~5
aliyun.accesskeyid.require=\u963F\u91CC\u96F2AccessKeyId\u4E0D\u80FD\u70BA\u7A7A
aliyun.accesskeysecret.require=\u963F\u91CC\u96F2AccessKeySecret\u4E0D\u80FD\u70BA\u7A7A
aliyun.signname.require=\u963F\u91CC\u96F2\u77ED\u4FE1\u7C3D\u540D\u4E0D\u80FD\u70BA\u7A7A
aliyun.templatecode.require=\u963F\u91CC\u96F2\u77ED\u4FE1\u6A21\u677F\u4E0D\u80FD\u70BA\u7A7A
aliyun.domain.require=\u963F\u91CC\u96F2\u7D81\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u70BA\u7A7A
aliyun.domain.url=\u963F\u91CC\u96F2\u7D81\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u78BA
aliyun.endPoint.require=\u963F\u91CC\u96F2EndPoint\u4E0D\u80FD\u70BA\u7A7A
aliyun.bucketname.require=\u963F\u91CC\u96F2BucketName\u4E0D\u80FD\u70BA\u7A7A
qcloud.appid.require=\u9A30\u8A0A\u96F2AppId\u4E0D\u80FD\u70BA\u7A7A
qcloud.appkey.require=\u9A30\u8A0A\u96F2AppKey\u4E0D\u80FD\u70BA\u7A7A
qcloud.secretId.require=\u9A30\u8A0A\u96F2SecretId\u4E0D\u80FD\u70BA\u7A7A
qcloud.secretkey.require=\u9A30\u8A0A\u96F2SecretKey\u4E0D\u80FD\u70BA\u7A7A
qcloud.signname.require=\u9A30\u8A0A\u96F2\u77ED\u4FE1\u7C3D\u540D\u4E0D\u80FD\u70BA\u7A7A
qcloud.templateid.require=\u9A30\u8A0A\u96F2\u77ED\u4FE1\u6A21\u677FID\u4E0D\u80FD\u70BA\u7A7A
qcloud.domain.require=\u9A30\u8A0A\u96F2\u7D81\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u70BA\u7A7A
qcloud.domain.url=\u9A30\u8A0A\u96F2\u7D81\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u78BA
qcloud.bucketname.require=\u9A30\u8A0A\u96F2BucketName\u4E0D\u80FD\u70BA\u7A7A
qcloud.region.require=\u6240\u5C6C\u5730\u5340\u4E0D\u80FD\u70BA\u7A7A
qiniu.domain.require=\u4E03\u725B\u7D81\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u70BA\u7A7A
qiniu.domain.url=\u4E03\u725B\u7D81\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u78BA
qiniu.accesskey.require=\u4E03\u725BAccessKey\u4E0D\u80FD\u70BA\u7A7A
qiniu.secretkey.require=\u4E03\u725BSecretKey\u4E0D\u80FD\u70BA\u7A7A
qiniu.bucketname.require=\u4E03\u725B\u7A7A\u9593\u540D\u4E0D\u80FD\u70BA\u7A7A
qiniu.templateId.require=\u4E03\u725B\u6A21\u677FID\u4E0D\u80FD\u70BA\u7A7A
fastdfs.domain.require=FastDFS\u7D81\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u70BA\u7A7A
fastdfs.domain.url=FastDFS\u7D81\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u78BA
local.domain.require=\u672C\u5730\u4E0A\u50B3\u7D81\u5B9A\u7684\u57DF\u540D\u4E0D\u80FD\u70BA\u7A7A
local.domain.url=\u672C\u5730\u4E0A\u50B3\u7D81\u5B9A\u7684\u57DF\u540D\u683C\u5F0F\u4E0D\u6B63\u78BA
local.path.url=\u5B58\u5132\u76EE\u9304\u4E0D\u80FD\u70BA\u7A7A
minio.endPoint.require=Minio EndPoint\u4E0D\u80FD\u70BA\u7A7A
minio.accesskey.require=Minio AccessKey\u4E0D\u80FD\u70BA\u7A7A
minio.secretkey.require=Minio SecretKey\u4E0D\u80FD\u70BA\u7A7A
minio.bucketname.require=Minio BucketName\u4E0D\u80FD\u70BA\u7A7A
sms.platform.range=\u5E73\u53F0\u985E\u578B\u53D6\u503C\u7BC4\u570D1~2
email.smtp.require=SMTP\u4E0D\u80FD\u70BA\u7A7A
email.port.require=\u7AEF\u53E3\u865F\u4E0D\u80FD\u70BA\u7A7A
email.username.require=\u90F5\u7BB1\u8CEC\u865F\u4E0D\u80FD\u70BA\u7A7A
email.password.require=\u90F5\u7BB1\u5BC6\u78BC\u4E0D\u80FD\u70BA\u7A7A
mail.name.require=\u6A21\u677F\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A
mail.subject.require=\u90F5\u4EF6\u4E3B\u984C\u4E0D\u80FD\u70BA\u7A7A
mail.content.require=\u90F5\u4EF6\u6B63\u6587\u4E0D\u80FD\u70BA\u7A7A
model.name.require=\u6A21\u578B\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A
model.key.require=\u6A21\u578B\u6A19\u8B58\u4E0D\u80FD\u70BA\u7A7A
news.title.require=\u6A19\u984C\u4E0D\u80FD\u70BA\u7A7A
news.content.require=\u5167\u5BB9\u4E0D\u80FD\u70BA\u7A7A
news.pubdate.require=\u767C\u4F48\u6642\u9593\u4E0D\u80FD\u70BA\u7A7A
region.id.require=\u5340\u57DF\u6A19\u8B58\u4E0D\u80FD\u70BA\u7A7A
region.pid.require=\u4E0A\u7D1A\u5340\u57DF\u4E0D\u80FD\u70BA\u7A7A
region.name.require=\u5340\u57DF\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A
processBizRoute.procDefId.require=\u6D41\u7A0B\u5B9A\u7FA9ID\u4E0D\u80FD\u70BA\u7A7A
processBizRoute.bizRoute.require=\u696D\u52D9\u8DEF\u7531\u4E0D\u80FD\u70BA\u7A7A
processBizRoute.procDefKey.require=\u6D41\u7A0B\u5B9A\u7FA9KEY\u4E0D\u80FD\u70BA\u7A7A
processBizRoute.version.require=\u6D41\u7A0B\u5B9A\u7FA9\u7248\u672C\u865F\u4E0D\u80FD\u70BA\u7A7A
ProcessStart.processDefinitionKey.require=\u6D41\u7A0B\u5B9A\u7FA9KEY\u4E0D\u80FD\u70BA\u7A7A
ProcessStart.businessKey.require=\u696D\u52D9KEY\u4E0D\u80FD\u70BA\u7A7A
@@ -36,15 +36,4 @@
update sys_user set password = #{newPassword} where id = #{id} update sys_user set password = #{newPassword} where id = #{id}
</update> </update>
<select id="getCountByDeptId" resultType="int">
select count(*) from sys_user where dept_id = #{value}
</select>
<select id="getUserIdListByDeptId" resultType="Long">
select id from sys_user where dept_id in
<foreach item="deptId" collection="list" open="(" separator="," close=")">
#{deptId}
</foreach>
</select>
</mapper> </mapper>
+12 -255
View File
@@ -8,14 +8,14 @@
"name": "xiaozhi", "name": "xiaozhi",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"axios": "^1.8.1",
"element-ui": "^2.15.14", "element-ui": "^2.15.14",
"flyio": "^0.6.14", "flyio": "^0.6.14",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-axios": "^3.5.2", "vue-axios": "^3.5.2",
"vue-router": "^3.6.5", "vue-router": "^3.6.5",
"vuex": "^3.6.2" "vuex": "^3.6.2",
"xiaozhi": "file:"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-router": "~5.0.0", "@vue/cli-plugin-router": "~5.0.0",
@@ -325,48 +325,6 @@
"@parcel/watcher-win32-x64": "2.5.1" "@parcel/watcher-win32-x64": "2.5.1"
} }
}, },
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": { "node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.1", "version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
@@ -388,216 +346,6 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@polka/url": { "node_modules/@polka/url": {
"version": "1.0.0-next.28", "version": "1.0.0-next.28",
"resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.28.tgz", "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.28.tgz",
@@ -1777,6 +1525,7 @@
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.8.2.tgz", "resolved": "https://registry.npmmirror.com/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@@ -3488,6 +3237,7 @@
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6", "get-intrinsic": "^1.2.6",
@@ -3934,6 +3684,7 @@
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz", "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
@@ -4256,6 +4007,7 @@
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3"
}, },
@@ -7031,7 +6783,8 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/pseudomap": { "node_modules/pseudomap": {
"version": "1.0.2", "version": "1.0.2",
@@ -9405,6 +9158,10 @@
} }
} }
}, },
"node_modules/xiaozhi": {
"resolved": "",
"link": true
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
+2 -2
View File
@@ -7,14 +7,14 @@
"build": "vue-cli-service build" "build": "vue-cli-service build"
}, },
"dependencies": { "dependencies": {
"axios": "^1.8.1",
"element-ui": "^2.15.14", "element-ui": "^2.15.14",
"flyio": "^0.6.14", "flyio": "^0.6.14",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-axios": "^3.5.2", "vue-axios": "^3.5.2",
"vue-router": "^3.6.5", "vue-router": "^3.6.5",
"vuex": "^3.6.2" "vuex": "^3.6.2",
"xiaozhi": "file:"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-router": "~5.0.0", "@vue/cli-plugin-router": "~5.0.0",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

+1 -1
View File
@@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title> <title>小智-智控台</title>
</head> </head>
<body> <body>
<noscript> <noscript>
+4 -1
View File
@@ -3,10 +3,13 @@ import user from './module/user.js'
/** /**
* 接口地址 * 接口地址
* 在开发阶段,如果地址写的是相对路径,请与vue.config.js的devServer配置相结合,方便跨域请求 * 当前8002端口接口还没开发完成,暂时用 apifoxmock 接口代替
* 如果你想调用8002端口,就用'/xiaozhi-esp32-api/api/v1',请与vue.config.js的devServer配置相结合,方便跨域请求
* *
*/ */
const DEV_API_SERVICE = 'https://apifoxmock.com/m1/5931378-5618560-default' const DEV_API_SERVICE = 'https://apifoxmock.com/m1/5931378-5618560-default'
// 8002开发完成完成后使用这个
// const DEV_API_SERVICE = '/xiaozhi-esp32-api'
/** /**
* 根据开发环境返回接口url * 根据开发环境返回接口url
+19 -5
View File
@@ -23,21 +23,27 @@ function sendRequest() {
_data: {}, _data: {},
_header: {'content-type': 'application/json; charset=utf-8'}, _header: {'content-type': 'application/json; charset=utf-8'},
_url: '', _url: '',
_responseType: undefined, // 新增响应类型字段
'send'() { 'send'() {
this._header.token = localStorage.getItem(Constant.STORAGE_KEY.TOKEN) this._header.token = localStorage.getItem(Constant.STORAGE_KEY.TOKEN)
// 打印请求信息
fly.request(this._url, this._data, { fly.request(this._url, this._data, {
method: this._method, method: this._method,
headers: this._header headers: this._header,
responseType: this._responseType
}).then((res) => { }).then((res) => {
const error = httpHandlerError(res, this._failCallback) const error = httpHandlerError(res, this._failCallback);
if (error) { if (error) {
return return
} }
if (this._sucCallback) { if (this._sucCallback) {
this._sucCallback(res) this._sucCallback(res)
} }
}).catch((res) => { }).catch((res) => {
console.log(1111, res) // 打印失败响应
console.log(res)
httpHandlerError(res, this._failCallback) httpHandlerError(res, this._failCallback)
}) })
return this return this
@@ -75,6 +81,11 @@ function sendRequest() {
}, },
'async'(flag) { 'async'(flag) {
this.async = flag this.async = flag
},
// 新增类型设置方法
'type'(responseType) {
this._responseType = responseType;
return this;
} }
} }
} }
@@ -84,13 +95,16 @@ function sendRequest() {
* callBack 回调函数 * callBack 回调函数
* errTip 自定义错误信息 * errTip 自定义错误信息
*/ */
// 在错误处理函数中添加日志
function httpHandlerError(info, callBack) { function httpHandlerError(info, callBack) {
/** 请求成功,退出该函数 可以根据项目需求来判断是否请求成功。这里判断的是status为200的时候是成功 */ /** 请求成功,退出该函数 可以根据项目需求来判断是否请求成功。这里判断的是status为200的时候是成功 */
let networkError = false let networkError = false
if (info.status === 200) { if (info.status === 200) {
if (info.data.code === 'success' || info.data.code === 0) {
if (info.data.code === 'success' || info.data.code === 0 || info.data.code === undefined) {
return networkError return networkError
} else if (info.data.code === 401) { }else if (info.data.code === 401) {
goToPage(Constant.PAGE.LOGIN, true) goToPage(Constant.PAGE.LOGIN, true)
return true return true
} else { } else {
+62
View File
@@ -43,4 +43,66 @@ export default {
}) })
}).send() }).send()
}, },
// 解绑设备
unbindDevice(device_id, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/user/device/unbind/${device_id}`)
.method('PUT')
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.fail(() => {
RequestService.reAjaxFun(() => {
this.unbindDevice(device_id, callback);
});
}).send()
},
// 绑定设备
bindDevice(deviceCode, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/user/device/bind/${deviceCode}`)
.method('POST')
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.fail((err) => {
console.error('绑定设备失败:', err);
RequestService.reAjaxFun(() => {
this.bindDevice(deviceCode, callback);
});
}).send();
},
// 获取验证码
getCaptcha(uuid, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/user/captcha?uuid=${uuid}`)
.method('GET')
.type('blob')
.header({
'Content-Type': 'image/gif',
'Pragma': 'No-cache',
'Cache-Control': 'no-cache'
})
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.fail((err) => { // 添加错误参数
}).send()
},
// 注册账号
register(registerForm, callback) {
RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/register`).method('POST')
.data(registerForm)
.success((res) => {
RequestService.clearRequestTime()
callback(res)
})
.fail(() => {
}).send()
},
} }
+13 -2
View File
@@ -2,6 +2,7 @@ import Vue from 'vue'
import VueRouter from 'vue-router' import VueRouter from 'vue-router'
import Welcome from '../views/welcome.vue' import Welcome from '../views/welcome.vue'
import Login from '../views/login.vue' import Login from '../views/login.vue'
import Register from '@/views/register.vue'
Vue.use(VueRouter) Vue.use(VueRouter)
@@ -9,7 +10,7 @@ const routes = [
{ {
path: '/', path: '/',
name: 'welcome', name: 'welcome',
component: Welcome component: Login
}, },
{ {
path: '/login', path: '/login',
@@ -27,7 +28,17 @@ const routes = [
component: function () { component: function () {
return import(/* webpackChunkName: "about" */ '../views/home.vue') return import(/* webpackChunkName: "about" */ '../views/home.vue')
} }
} },
{
path: '/register',
name: 'Register',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: function () {
return import(/* webpackChunkName: "about" */ '../views/register.vue')
}
},
] ]
const router = new VueRouter({ const router = new VueRouter({
+3
View File
@@ -1,6 +1,9 @@
const HAVE_NO_RESULT = '暂无' const HAVE_NO_RESULT = '暂无'
export default { export default {
HAVE_NO_RESULT, // 项目的配置信息 HAVE_NO_RESULT, // 项目的配置信息
PAGE: {
LOGIN: '/login',
},
STORAGE_KEY: { STORAGE_KEY: {
TOKEN: 'TOKEN', TOKEN: 'TOKEN',
PUBLIC_KEY: 'PUBLIC_KEY', PUBLIC_KEY: 'PUBLIC_KEY',
+132
View File
@@ -0,0 +1,132 @@
.welcome {
min-width: 1200px;
min-height: 675px;
height: 100vh;
background-image: url("@/assets/login/background.png");
background-size: cover;
/* 确保背景图像覆盖整个元素 */
background-position: center;
/* 从顶部中心对齐 */
-webkit-background-size: cover;
/* 兼容老版本WebKit浏览器 */
-o-background-size: cover;
/* 兼容老版本Opera浏览器 */
}
.login-text {
font-weight: 700;
font-size: 32px;
text-align: left;
color: #3d4566;
}
.login-welcome {
font-weight: 400;
font-size: 9px;
text-align: left;
color: #818cae;
align-self: flex-end;
margin-bottom: 7px;
}
.login-box {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 18%;
background-color: #fff;
border-radius: 20px;
padding: 35px 0;
width: 450px;
box-sizing: border-box;
}
.el-dropdown-link {
font-weight: 400;
font-size: 14px;
text-align: left;
color: #979db1;
}
.input-icon {
width: 19px;
height: 22px;
flex-shrink: 0;
}
.login-btn {
height: 35px;
background: #5778ff;
border-radius: 10px;
font-weight: 400;
font-size: 14px;
cursor: pointer;
color: #fff;
line-height: 35px;
margin: 35px 15px 15px;
}
.code-send {
width: 70px;
height: 32px;
border-radius: 10px;
background: #e6ebff;
line-height: 32px;
font-weight: 400;
font-size: 14px;
color: #5778ff;
flex-shrink: 0;
cursor: pointer;
}
.input-box {
display: flex;
margin-top: 20px;
align-items: center;
border-radius: 10px;
background: #f6f8fb;
border: 1px solid #e4e6ef;
height: 40px;
padding: 0 15px;
gap: 20px;
}
::v-deep {
.el-tabs__nav-wrap::after {
height: 1px;
}
.el-tabs__nav-wrap::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
background-color: #e4e7ed;
z-index: 1;
}
.el-tabs__item {
height: 65px;
line-height: 65px;
font-weight: 700;
color: #3d4566;
}
.el-tabs__item.is-active {
color: #5778ff;
}
.el-tabs__nav-scroll {
padding: 0 30px;
}
.el-input__inner {
border: none;
background-color: transparent;
height: 56px;
padding: 0;
}
}
+71 -16
View File
@@ -21,9 +21,9 @@
</div> </div>
<div style="display: flex;align-items: center;gap: 8px;margin-top: 10px"> <div style="display: flex;align-items: center;gap: 8px;margin-top: 10px">
<div class="serach-box"> <div class="serach-box">
<el-input placeholder="输入名称搜索.." v-model="serach" style="border: none; background: transparent;" /> <el-input placeholder="输入名称搜索.." v-model="serach" style="border: none; background: transparent;" @keyup.enter.native="handleSearch" />
<img src="@/assets/home/search.png" alt="" <img src="@/assets/home/search.png" alt=""
style="width: 12px;height: 12px;margin-right: 11px;cursor: pointer;" /> style="width: 12px;height: 12px;margin-right: 11px;cursor: pointer;" @click="handleSearch" />
</div> </div>
<img src="@/assets/home/avatar.png" alt="" style="width: 21px;height: 21px;" /> <img src="@/assets/home/avatar.png" alt="" style="width: 21px;height: 21px;" />
<div class="user-info"> <div class="user-info">
@@ -56,16 +56,16 @@
</div> </div>
</div> </div>
<div <div
style="display: flex;flex-wrap: wrap;margin-top: 15px;gap: 15px;justify-content: space-between;box-sizing: border-box;"> style="display: flex;flex-wrap: wrap;margin-top: 15px;gap: 15px;justify-content: flex-start;box-sizing: border-box;">
<div class="device-item" v-for="(item,index) in deviceList" :key="index"> <div class="device-item" v-for="(item,index) in filteredDeviceList" :key="index">
<div style="display: flex;justify-content: space-between;"> <div style="display: flex;justify-content: space-between; align-items: center; ">
<div style="font-weight: 700;font-size: 18px;text-align: left;color: #3d4566;"> <div style="font-weight: 700;font-size: 18px;text-align: left;color: #3d4566;">
<!-- CC:ba:97:11:a6:ac--> <!-- CC:ba:97:11:a6:ac-->
{{item.list[0]?.mac_address}} {{item.list[0]?.mac_address}}
</div> </div>
<div> <div style="display: flex;align-items: center;">
<img src="@/assets/home/delete.png" alt="" <img src="@/assets/home/delete.png" alt=""
style="width: 18px;height: 18px;margin-right: 8px;" /> style="width: 18px;height: 18px;margin-right: 8px;" @click="unbindDevice(item.list[0]?.id)" />
<img src="@/assets/home/info.png" alt="" style="width: 18px;height: 18px;" /> <img src="@/assets/home/info.png" alt="" style="width: 18px;height: 18px;" />
</div> </div>
</div> </div>
@@ -210,7 +210,7 @@
</div> </div>
</div> </div>
<div style="display: flex;margin: 0 20px;gap: 10px;"> <div style="display: flex;margin: 0 20px;gap: 10px;">
<div class="dialog-btn" @click="addDeviceDialogVisible=false">确定</div> <div class="dialog-btn" @click="addDevice">确定</div>
<div class="dialog-btn" <div class="dialog-btn"
style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;" style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;"
@click="addDeviceDialogVisible=false"> @click="addDeviceDialogVisible=false">
@@ -229,10 +229,12 @@ export default {
name: 'home', name: 'home',
data() { data() {
return { return {
serach: '', serach: '', // 搜索框输入内容
deviceList: [], // 原始设备列表
filteredDeviceList: [], // 过滤后的设备列表
switchValue: false, switchValue: false,
addDeviceDialogVisible: false, addDeviceDialogVisible: false,
deviceCode: "", deviceCode: "", // 设备验证码
settingDevice: false, settingDevice: false,
form: { form: {
name: "", name: "",
@@ -250,13 +252,12 @@ export default {
}], }],
userInfo: { userInfo: {
mobile: '' // 初始化用户信息 mobile: '' // 初始化用户信息
}, }
deviceList:[]
}; };
}, },
methods: { methods: {
showAddDialog() { showAddDialog() {
this.addDeviceDialogVisible = true; this.addDeviceDialogVisible = true;
}, },
clickSettingDevice() { clickSettingDevice() {
this.settingDevice = true this.settingDevice = true
@@ -270,14 +271,68 @@ export default {
// 获取已绑设备 // 获取已绑设备
getList(){ getList(){
Api.user.getHomeList(({data})=>{ Api.user.getHomeList(({data})=>{
console.log(data.data) this.deviceList = data.data; // 保存原始设备列表
this.deviceList = data.data this.filteredDeviceList = data.data; // 初始化过滤后的设备列表
}) })
},
// 处理搜索
handleSearch() {
if (this.serach.trim() === '') {
// 如果搜索框为空,显示全部设备
this.filteredDeviceList = this.deviceList;
} else {
// 过滤设备列表
this.filteredDeviceList = this.deviceList.filter(device => {
return (
device.list[0]?.mac_address?.includes(this.serach) || // 匹配MAC地址
device.list[0]?.device_type?.includes(this.serach) || // 匹配设备型号
device.list[0]?.app_version?.includes(this.serach) // 匹配APP版本
);
});
}
},
// 解绑设备
unbindDevice(device_id) {
this.$confirm('确定要解绑该设备吗', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 调用解绑设备的接口
Api.user.unbindDevice(device_id, ({ data }) => {
if (data.code === 0) {
this.$message.success('解绑成功');
this.getList();
} else {
this.$message.error(data.msg || '解绑失败');
}
});
}).catch(() => {
// 用户取消操作
this.$message.info('已取消解绑');
});
},
// 添加设备
addDevice() {
if (!this.deviceCode) {
this.$message.warning('请输入设备验证码');
return;
}
Api.user.bindDevice(this.deviceCode, ({ data }) => {
if (data.code === 0) {
this.$message.success('设备绑定成功');
this.addDeviceDialogVisible = false;
this.getList(); // 刷新设备列表
} else {
this.$message.error(data.msg || '设备绑定失败');
}
});
} }
}, },
mounted() { mounted() {
this.fetchUserInfo(); // 组件加载时获取用户信息 this.fetchUserInfo(); // 组件加载时获取用户信息
this.getList() this.getList(); // 初始化设备列表
} }
} }
</script> </script>
+54 -144
View File
@@ -25,15 +25,23 @@
</div> </div>
<div class="input-box"> <div class="input-box">
<img src="@/assets/login/password.png" alt="" class="input-icon"/> <img src="@/assets/login/password.png" alt="" class="input-icon"/>
<el-input v-model="form.password" placeholder="请输入密码"/> <el-input v-model="form.password" type="password" placeholder="请输入密码"/>
</div> </div>
<div class="input-box"> <div style="display: flex; align-items: center; margin-top: 20px; width: 100%; gap: 10px;">
<img src="@/assets/login/shield.png" alt="" class="input-icon"/> <div class="input-box" style="width: calc(100% - 130px); margin-top: 0;">
<el-input v-model="form.captcha" placeholder="请输入验证码"/> <img src="@/assets/login/shield.png" alt="" class="input-icon"/>
<el-input v-model="form.captcha" placeholder="请输入验证码" style="flex: 1;"/>
</div>
<img v-if="captchaUrl"
:src="captchaUrl"
alt="验证码"
style="width: 150px; height: 40px; cursor: pointer;"
@click="fetchCaptcha"
/>
</div> </div>
<div <div
style="font-weight: 400;font-size: 14px;text-align: left;color: #5778ff;display: flex;justify-content: space-between;margin-top: 20px;"> style="font-weight: 400;font-size: 14px;text-align: left;color: #5778ff;display: flex;justify-content: space-between;margin-top: 20px;">
<div style="cursor: pointer;">新用户注册</div> <div style="cursor: pointer;" @click="goToRegister">新用户注册</div>
</div> </div>
</div> </div>
<div class="login-btn" @click="login">登陆</div> <div class="login-btn" @click="login">登陆</div>
@@ -55,8 +63,9 @@
</template> </template>
<script> <script>
import Api from '@/apis/api' import {getUUID, goToPage, showDanger, showSuccess} from '@/utils'
import {isNull, showDanger, showSuccess, goToPage} from '@/utils' import Api from '@/apis/api';
export default { export default {
name: 'login', name: 'login',
@@ -66,162 +75,63 @@ export default {
form: { form: {
username: '', username: '',
password: '', password: '',
captcha: '' captcha: '',
} captchaId: ''
},
captchaUuid: '',
captchaUrl: ''
} }
}, },
mounted() {
this.fetchCaptcha();
},
methods: { methods: {
login() { fetchCaptcha() {
if (isNull(this.form.username)) { this.captchaUuid = getUUID();
Api.user.getCaptcha(this.captchaUuid, (res) => {
if (res.status === 200) {
const blob = new Blob([res.data], {type: res.data.type});
this.captchaUrl = URL.createObjectURL(blob);
} else {
console.error('验证码加载异常:', error);
showDanger('验证码加载失败,点击刷新')
}
});
},
async login() {
if (!this.form.username.trim()) { // 替换isNull校验
showDanger('用户名不能为空') showDanger('用户名不能为空')
return return
} }
if (isNull(this.form.password)) { if (!this.form.password.trim()) { // 替换isNull校验
showDanger('密码不能为空') showDanger('密码不能为空')
return return
} }
if (isNull(this.form.captcha)) { if (!this.form.captcha.trim()) { // 替换isNull校验
showDanger('验证码不能为空') showDanger('验证码不能为空')
return return
} }
this.form.captchaId = this.captchaUuid
Api.user.login(this.form, ({data}) => { Api.user.login(this.form, ({data}) => {
console.log(data)
showSuccess('登陆成功!') showSuccess('登陆成功!')
goToPage('/home') goToPage('/home')
}) })
setTimeout(() => {
this.fetchCaptcha()
}, 1000)
},
goToRegister() {
goToPage('/register')
} }
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.welcome { @import './auth.scss'; // 添加这行引用
min-width: 1200px;
min-height: 675px;
height: 100vh;
background-image: url("@/assets/login/background.png");
background-size: cover;
/* 确保背景图像覆盖整个元素 */
background-position: center;
/* 从顶部中心对齐 */
-webkit-background-size: cover;
/* 兼容老版本WebKit浏览器 */
-o-background-size: cover;
/* 兼容老版本Opera浏览器 */
}
.login-text {
font-weight: 700;
font-size: 32px;
text-align: left;
color: #3d4566;
}
.login-welcome {
font-weight: 400;
font-size: 9px;
text-align: left;
color: #818cae;
align-self: flex-end;
margin-bottom: 7px;
}
.login-box {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 18%;
background-color: #fff;
border-radius: 20px;
padding: 35px 0;
width: 450px;
box-sizing: border-box;
}
.el-dropdown-link {
font-weight: 400;
font-size: 14px;
text-align: left;
color: #979db1;
}
.input-icon {
width: 19px;
height: 22px;
flex-shrink: 0;
}
.login-btn {
height: 35px;
background: #5778ff;
border-radius: 10px;
font-weight: 400;
font-size: 14px;
cursor: pointer;
color: #fff;
line-height: 35px;
margin: 35px 15px 15px;
}
.code-send {
width: 70px;
height: 32px;
border-radius: 10px;
background: #e6ebff;
line-height: 32px;
font-weight: 400;
font-size: 14px;
color: #5778ff;
flex-shrink: 0;
cursor: pointer;
}
.input-box {
display: flex;
margin-top: 20px;
align-items: center;
border-radius: 10px;
background: #f6f8fb;
border: 1px solid #e4e6ef;
height: 40px;
padding: 0 15px;
gap: 20px;
}
::v-deep {
.el-tabs__nav-wrap::after {
height: 1px;
}
.el-tabs__nav-wrap::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
background-color: #e4e7ed;
z-index: 1;
}
.el-tabs__item {
height: 65px;
line-height: 65px;
font-weight: 700;
color: #3d4566;
}
.el-tabs__item.is-active {
color: #5778ff;
}
.el-tabs__nav-scroll {
padding: 0 30px;
}
.el-input__inner {
border: none;
background-color: transparent;
height: 56px;
padding: 0;
}
}
</style> </style>
+164
View File
@@ -0,0 +1,164 @@
<template>
<div class="welcome">
<el-container style="height: 100%;">
<!-- 保持相同的头部 -->
<el-header>
<div style="display: flex;align-items: center;margin-top: 15px;margin-left: 10px;gap: 10px;">
<img src="@/assets/xiaozhi-logo.png" alt="" style="width: 45px;height: 45px;"/>
<img src="@/assets/xiaozhi-ai.png" alt="" style="width: 70px;height: 13px;"/>
</div>
</el-header>
<el-main style="position: relative;">
<div class="login-box">
<!-- 修改标题部分 -->
<div style="display: flex;align-items: center;gap: 20px;margin-bottom: 39px;padding: 0 30px;">
<img src="@/assets/login/hi.png" alt="" style="width: 34px;height: 34px;"/>
<div class="login-text">注册</div>
<div class="login-welcome">
WELCOME TO REGISTER
</div>
</div>
<div style="padding: 0 30px;">
<!-- 用户名输入框 -->
<div class="input-box">
<img src="@/assets/login/username.png" alt="" class="input-icon"/>
<el-input v-model="form.username" placeholder="请输入用户名"/>
</div>
<!-- 密码输入框 -->
<div class="input-box">
<img src="@/assets/login/password.png" alt="" class="input-icon"/>
<el-input v-model="form.password" type="password" placeholder="请输入密码"/>
</div>
<!-- 新增确认密码 -->
<div class="input-box">
<img src="@/assets/login/password.png" alt="" class="input-icon"/>
<el-input v-model="form.confirmPassword" type="password" placeholder="请确认密码"/>
</div>
<!-- 验证码部分保持相同 -->
<div style="display: flex; align-items: center; margin-top: 20px; width: 100%; gap: 10px;">
<div class="input-box" style="width: calc(100% - 130px); margin-top: 0;">
<img src="@/assets/login/shield.png" alt="" class="input-icon"/>
<el-input v-model="form.captcha" placeholder="请输入验证码" style="flex: 1;"/>
</div>
<img v-if="captchaUrl"
:src="captchaUrl"
alt="验证码"
style="width: 150px; height: 40px; cursor: pointer;"
@click="fetchCaptcha"
/>
</div>
<!-- 修改底部链接 -->
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #5778ff;margin-top: 20px;">
<div style="cursor: pointer;" @click="goToLogin">已有账号立即登录</div>
</div>
</div>
<!-- 修改按钮文本 -->
<div class="login-btn" @click="register">立即注册</div>
<!-- 保持相同的协议声明 -->
<div style="font-size: 14px;color: #979db1;">
注册即同意
<div style="display: inline-block;color: #5778FF;cursor: pointer;">用户协议</div>
<div style="display: inline-block;color: #5778FF;cursor: pointer;">隐私政策</div>
</div>
</div>
</el-main>
<!-- 保持相同的页脚 -->
<el-footer>
<div style="font-size: 12px;font-weight: 400;color: #979db1;">
©2025 xiaozhi-esp32-server
</div>
</el-footer>
</el-container>
</div>
</template>
<script>
import {getUUID, goToPage, showDanger, showSuccess} from '@/utils'
import Api from '@/apis/api';
export default {
name: 'register',
data() {
return {
form: {
username: '',
password: '',
confirmPassword: '',
captcha: '',
captchaId: ''
},
captchaUrl: ''
}
},
mounted() {
this.fetchCaptcha();
},
methods: {
// 复用验证码获取方法
fetchCaptcha() {
this.form.captchaId = getUUID();
Api.user.getCaptcha(this.form.captchaId, (res) => {
if (res.status === 200) {
const blob = new Blob([res.data], {type: res.data.type});
this.captchaUrl = URL.createObjectURL(blob);
} else {
console.error('验证码加载异常:', error);
showDanger('验证码加载失败,点击刷新');
}
});
},
// 注册逻辑
register() {
if (!this.form.username.trim()) {
showDanger('用户名不能为空')
return
}
if (!this.form.password.trim()) {
showDanger('密码不能为空')
return
}
if (this.form.password !== this.form.confirmPassword) {
showDanger('两次输入的密码不一致')
return
}
if (!this.form.captcha.trim()) {
showDanger('验证码不能为空')
return
}
Api.user.register(this.form, ({data}) => {
console.log(data)
if (data.code === 0) {
showSuccess('注册成功!')
goToPage('/login')
} else {
showDanger(data.msg || '注册失败')
this.fetchCaptcha()
}
})
setTimeout(() => {
this.fetchCaptcha()
}, 1000)
},
goToLogin() {
goToPage('/login')
}
}
}
</script>
<style scoped lang="scss">
@import './auth.scss'; // 修改为导入新建的SCSS文件
</style>
+23
View File
@@ -0,0 +1,23 @@
const { defineConfig } = require('@vue/cli-service');
const dotenv = require('dotenv');
// 确保加载 .env 文件
dotenv.config();
module.exports = defineConfig({
devServer: {
// Bug 修复:将代理配置为环境变量中定义的 API 基础 URL
proxy: {
'/xiaozhi-esp32-api': {
target: process.env.VUE_APP_API_BASE_URL || 'http://localhost:8002', // 后端 API 的基础 URL
changeOrigin: true, // 允许跨域
// pathRewrite: {
// '^/api': '', // 路径重写
// },
},
},
client: {
overlay: false,
},
},
});
+29 -4
View File
@@ -1,10 +1,27 @@
import asyncio import asyncio
import sys
import signal
from config.settings import load_config, check_config_file from config.settings import load_config, check_config_file
from core.websocket_server import WebSocketServer from core.websocket_server import WebSocketServer
from core.utils.util import check_ffmpeg_installed from core.utils.util import check_ffmpeg_installed
TAG = __name__ TAG = __name__
async def wait_for_exit():
"""Windows 和 Linux 兼容的退出监听"""
loop = asyncio.get_running_loop()
stop_event = asyncio.Event()
if sys.platform == "win32":
# Windows: 用 sys.stdin.read() 监听 Ctrl + C
await loop.run_in_executor(None, sys.stdin.read)
else:
# Linux/macOS: 用 signal 监听 Ctrl + C
def stop():
stop_event.set()
loop.add_signal_handler(signal.SIGINT, stop)
loop.add_signal_handler(signal.SIGTERM, stop) # 支持 kill 进程
await stop_event.wait()
async def main(): async def main():
check_config_file() check_config_file()
@@ -16,11 +33,19 @@ async def main():
ws_task = asyncio.create_task(ws_server.start()) ws_task = asyncio.create_task(ws_server.start())
try: try:
# 等待 WebSocket 服务器运行 await wait_for_exit() # 监听退出信号
await ws_task except asyncio.CancelledError:
print("任务被取消,清理资源中...")
finally: finally:
ws_task.cancel() ws_task.cancel()
try:
await ws_task
except asyncio.CancelledError:
pass
print("服务器已关闭,程序退出。")
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) try:
asyncio.run(main())
except KeyboardInterrupt:
print("手动中断,程序终止。")
+3 -2
View File
@@ -181,8 +181,9 @@ LLM:
CozeLLM: CozeLLM:
# 定义LLM API类型 # 定义LLM API类型
type: coze type: coze
bot_id: 你的bot_id # bot_id和user_id的内容写在引号之内
user_id: 你的user_id bot_id: "你的bot_id"
user_id: "你的user_id"
personal_access_token: 你的coze个人令牌 personal_access_token: 你的coze个人令牌
LMStudioLLM: LMStudioLLM:
# 定义LLM API类型 # 定义LLM API类型
+85 -49
View File
@@ -11,7 +11,7 @@ import websockets
from typing import Dict, Any from typing import Dict, Any
from core.utils.dialogue import Message, Dialogue from core.utils.dialogue import Message, Dialogue
from core.handle.textHandle import handleTextMessage from core.handle.textHandle import handleTextMessage
from core.utils.util import get_string_no_punctuation_or_emoji from core.utils.util import get_string_no_punctuation_or_emoji, extract_json_from_string
from concurrent.futures import ThreadPoolExecutor, TimeoutError from concurrent.futures import ThreadPoolExecutor, TimeoutError
from core.handle.sendAudioHandle import sendAudioMessage from core.handle.sendAudioHandle import sendAudioMessage
from core.handle.receiveAudioHandle import handleAudioMessage from core.handle.receiveAudioHandle import handleAudioMessage
@@ -95,8 +95,6 @@ class ConnectionHandler:
if self.config["selected_module"]["Intent"] == 'function_call': if self.config["selected_module"]["Intent"] == 'function_call':
self.use_function_call_mode = True self.use_function_call_mode = True
self.logger.bind(tag=TAG).info(f"use_function_call_mode:{self.use_function_call_mode}")
async def handle_connection(self, ws): async def handle_connection(self, ws):
try: try:
# 获取并验证headers # 获取并验证headers
@@ -307,8 +305,7 @@ class ConnectionHandler:
response_message = [] response_message = []
processed_chars = 0 # 跟踪已处理的字符位置 processed_chars = 0 # 跟踪已处理的字符位置
function_call_data = None # 存储function call数据
try: try:
start_time = time.time() start_time = time.time()
@@ -316,7 +313,7 @@ class ConnectionHandler:
future = asyncio.run_coroutine_threadsafe(self.memory.query_memory(query), self.loop) future = asyncio.run_coroutine_threadsafe(self.memory.query_memory(query), self.loop)
memory_str = future.result() memory_str = future.result()
# self.logger.bind(tag=TAG).info(f"记忆内容: {memory_str}") #self.logger.bind(tag=TAG).info(f"对话记录: {self.dialogue.get_llm_dialogue_with_memory(memory_str)}")
# 使用支持functions的streaming接口 # 使用支持functions的streaming接口
llm_responses = self.llm.response_with_functions( llm_responses = self.llm.response_with_functions(
@@ -332,48 +329,61 @@ class ConnectionHandler:
text_index = 0 text_index = 0
# 处理流式响应 # 处理流式响应
tool_call_flag = False
function_name = None
function_id = None
function_arguments = ""
content_arguments = ""
for response in llm_responses: for response in llm_responses:
if response["type"] == "content": content, tools_call = response
content = response["content"] if content is not None and len(content)>0:
response_message.append(content) if len(response_message)<=0 and content=="```":
tool_call_flag = True
if self.client_abort: if tools_call is not None:
break tool_call_flag = True
if tools_call[0].id is not None:
function_id = tools_call[0].id
if tools_call[0].function.name is not None:
function_name = tools_call[0].function.name
if tools_call[0].function.arguments is not None:
function_arguments += tools_call[0].function.arguments
end_time = time.time() if content is not None and len(content) > 0:
self.logger.bind(tag=TAG).debug(f"大模型返回时间: {end_time - start_time} 秒, 生成token={content}") if tool_call_flag:
content_arguments+=content
else:
response_message.append(content)
# 处理文本分段和TTS逻辑 if self.client_abort:
# 合并当前全部文本并处理未分割部分 break
full_text = "".join(response_message)
current_text = full_text[processed_chars:] # 从未处理的位置开始
# 查找最后一个有效标点 end_time = time.time()
punctuations = ("", "", "", "", "") self.logger.bind(tag=TAG).debug(f"大模型返回时间: {end_time - start_time} 秒, 生成token={content}")
last_punct_pos = -1
for punct in punctuations:
pos = current_text.rfind(punct)
if pos > last_punct_pos:
last_punct_pos = pos
# 找到分割点则处理 # 处理文本分段和TTS逻辑
if last_punct_pos != -1: # 合并当前全部文本并处理未分割部分
segment_text_raw = current_text[:last_punct_pos + 1] full_text = "".join(response_message)
segment_text = get_string_no_punctuation_or_emoji(segment_text_raw) current_text = full_text[processed_chars:] # 从未处理的位置开始
if segment_text:
text_index += 1
self.recode_first_last_text(segment_text, text_index)
future = self.executor.submit(self.speak_and_play, segment_text, text_index)
self.tts_queue.put(future)
processed_chars += len(segment_text_raw) # 更新已处理字符位置
elif response["type"] == "function_call": # 查找最后一个有效标点
# Extract function call data punctuations = ("", "", "", "", "")
function_call_data = { last_punct_pos = -1
"name": response["function_call"]["function"]["name"], for punct in punctuations:
"arguments": response["function_call"]["function"]["arguments"] pos = current_text.rfind(punct)
} if pos > last_punct_pos:
self.logger.bind(tag=TAG).info(f"Function call detected: {function_call_data}") last_punct_pos = pos
# 找到分割点则处理
if last_punct_pos != -1:
segment_text_raw = current_text[:last_punct_pos + 1]
segment_text = get_string_no_punctuation_or_emoji(segment_text_raw)
if segment_text:
text_index += 1
self.recode_first_last_text(segment_text, text_index)
future = self.executor.submit(self.speak_and_play, segment_text, text_index)
self.tts_queue.put(future)
processed_chars += len(segment_text_raw) # 更新已处理字符位置
# 处理最后剩余的文本 # 处理最后剩余的文本
full_text = "".join(response_message) full_text = "".join(response_message)
@@ -387,23 +397,49 @@ class ConnectionHandler:
self.tts_queue.put(future) self.tts_queue.put(future)
# 存储对话内容 # 存储对话内容
self.dialogue.put(Message(role="assistant", content="".join(response_message))) if len(response_message)>0:
self.dialogue.put(Message(role="assistant", content="".join(response_message)))
# 处理function call # 处理function call
if function_call_data: if tool_call_flag:
if function_id is None:
a = extract_json_from_string(content_arguments)
if a is not None:
content_arguments_json = json.loads(a)
function_name = content_arguments_json["function_name"]
function_arguments = json.dumps(content_arguments_json["args"], ensure_ascii=False)
function_id = str(uuid.uuid4().hex)
else:
return []
function_arguments = json.loads(function_arguments)
self.logger.bind(tag=TAG).info(f"function_name={function_name}, function_id={function_id}, function_arguments={function_arguments}")
function_call_data = {
"name": function_name,
"id": function_id,
"arguments": function_arguments
}
result = handle_llm_function_call(self, function_call_data) result = handle_llm_function_call(self, function_call_data)
if result.action == Action.RESPONSE: self._handle_function_result(result, function_call_data, text_index+1)
text = result.response
text_index += 1
self.recode_first_last_text(text, text_index)
future = self.executor.submit(self.speak_and_play, text, text_index)
self.tts_queue.put(future)
self.llm_finish_task = True self.llm_finish_task = True
self.logger.bind(tag=TAG).debug(json.dumps(self.dialogue.get_llm_dialogue(), indent=4, ensure_ascii=False)) self.logger.bind(tag=TAG).debug(json.dumps(self.dialogue.get_llm_dialogue(), indent=4, ensure_ascii=False))
return True return True
def _handle_function_result(self, result, function_call_data, text_index):
if result.action == Action.RESPONSE: # 直接回复前端
text = result.response
self.recode_first_last_text(text, text_index)
future = self.executor.submit(self.speak_and_play, text, text_index)
self.tts_queue.put(future)
self.dialogue.put(Message(role="assistant", content=text))
if result.action == Action.REQLLM: # 调用函数后再请求llm生成回复
text = result.response
if result.action == Action.NOTFOUND:
text = result.response
def _tts_priority_thread(self): def _tts_priority_thread(self):
while not self.stop_event.is_set(): while not self.stop_event.is_set():
text = None text = None
@@ -2,6 +2,7 @@ from config.logger import setup_logging
import json import json
from core.handle.sendAudioHandle import send_stt_message from core.handle.sendAudioHandle import send_stt_message
from core.utils.dialogue import Message from core.utils.dialogue import Message
from core.utils.util import remove_punctuation_and_length
from config.functionCallConfig import FunctionCallConfig from config.functionCallConfig import FunctionCallConfig
import asyncio import asyncio
from enum import Enum from enum import Enum
@@ -67,7 +68,7 @@ def handle_llm_function_call(conn, function_call_data):
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"处理音乐意图错误: {e}") logger.bind(tag=TAG).error(f"处理音乐意图错误: {e}")
else: else:
return ActionResponse(action=Action.NOTFOUND, result="没有找到对应的函数", response="") return ActionResponse(action=Action.NOTFOUND, result="没有找到对应的函数", response="没有找到对应的函数处理相对于的功能呢,你可以需要添加预设的对应函数处理呢")
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"处理function call错误: {e}") logger.bind(tag=TAG).error(f"处理function call错误: {e}")
@@ -93,8 +94,6 @@ async def handle_user_intent(conn, text):
# 使用支持function calling的聊天方法,不再进行意图分析 # 使用支持function calling的聊天方法,不再进行意图分析
return False return False
logger.bind(tag=TAG).info(f"分析用户意图: {text}")
# 使用LLM进行意图分析 # 使用LLM进行意图分析
intent = await analyze_intent_with_llm(conn, text) intent = await analyze_intent_with_llm(conn, text)
@@ -107,6 +106,7 @@ async def handle_user_intent(conn, text):
async def check_direct_exit(conn, text): async def check_direct_exit(conn, text):
"""检查是否有明确的退出命令""" """检查是否有明确的退出命令"""
_, text = remove_punctuation_and_length(text)
cmd_exit = conn.cmd_exit cmd_exit = conn.cmd_exit
for cmd in cmd_exit: for cmd in cmd_exit:
if text == cmd: if text == cmd:
@@ -122,13 +122,10 @@ async def analyze_intent_with_llm(conn, text):
logger.bind(tag=TAG).warning("意图识别服务未初始化") logger.bind(tag=TAG).warning("意图识别服务未初始化")
return None return None
# 创建对话历史记录 # 对话历史记录
dialogue = conn.dialogue dialogue = conn.dialogue
dialogue.put(Message(role="user", content=text))
try: try:
intent_result = await conn.intent.detect_intent(dialogue.dialogue) intent_result = await conn.intent.detect_intent(conn, dialogue.dialogue, text)
logger.bind(tag=TAG).info(f"意图识别结果: {intent_result}")
# 尝试解析JSON结果 # 尝试解析JSON结果
try: try:
@@ -67,8 +67,9 @@ async def no_voice_close_connect(conn):
else: else:
no_voice_time = time.time() * 1000 - conn.client_no_voice_last_time no_voice_time = time.time() * 1000 - conn.client_no_voice_last_time
close_connection_no_voice_time = conn.config.get("close_connection_no_voice_time", 120) close_connection_no_voice_time = conn.config.get("close_connection_no_voice_time", 120)
if no_voice_time > 1000 * close_connection_no_voice_time: if not conn.close_after_chat and no_voice_time > 1000 * close_connection_no_voice_time:
conn.close_after_chat = True
conn.client_abort = False conn.client_abort = False
conn.asr_server_receive = False conn.asr_server_receive = False
prompt = "时间过得真快,我都好久没说话了。请你用十个字左右话跟我告别,以“再见”或“拜拜”为结尾" prompt = "请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧。"
await startToChat(conn, prompt) await startToChat(conn, prompt)
@@ -13,28 +13,37 @@ async def sendAudioMessage(conn, audios, text, text_index=0):
logger.bind(tag=TAG).info(f"发送第一段语音: {text}") logger.bind(tag=TAG).info(f"发送第一段语音: {text}")
await send_tts_message(conn, "sentence_start", text) await send_tts_message(conn, "sentence_start", text)
# 初始化流控参数 # 流控参数优化
frame_duration = 60 # 毫秒 original_frame_duration = 60 # 原始帧时长(毫秒
start_time = time.perf_counter() # 使用高精度计时器 adjusted_frame_duration = int(original_frame_duration * 0.8) # 缩短20%
play_position = 0 # 已播放的时长(毫秒) total_frames = len(audios) # 获取总帧数
compensation = total_frames * (original_frame_duration - adjusted_frame_duration) / 1000 # 补偿时间(秒)
start_time = time.perf_counter()
play_position = 0 # 已播放时长(毫秒)
for opus_packet in audios: for opus_packet in audios:
if conn.client_abort: if conn.client_abort:
return return
# 计算当前包的预期发送时间 # 计算带加速因子的预期时间
expected_time = start_time + (play_position / 1000) expected_time = start_time + (play_position / 1000)
current_time = time.perf_counter() current_time = time.perf_counter()
# 等待直到预期时间 # 流控等待(使用加速后的帧时长)
delay = expected_time - current_time delay = expected_time - current_time
if delay > 0: if delay > 0:
await asyncio.sleep(delay) await asyncio.sleep(delay)
# 发送音频包
await conn.websocket.send(opus_packet) await conn.websocket.send(opus_packet)
play_position += frame_duration # 更新播放位置 play_position += adjusted_frame_duration # 使用调整后的帧时长
# 补偿因加速损失的时长
if compensation > 0:
await asyncio.sleep(compensation)
await send_tts_message(conn, "sentence_end", text) await send_tts_message(conn, "sentence_end", text)
# 发送结束消息(如果是最后一个文本) # 发送结束消息(如果是最后一个文本)
if conn.llm_finish_task and text_index == conn.tts_last_text_index: if conn.llm_finish_task and text_index == conn.tts_last_text_index:
await send_tts_message(conn, 'stop', None) await send_tts_message(conn, 'stop', None)
@@ -5,6 +5,7 @@ from config.logger import setup_logging
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
class IntentProviderBase(ABC): class IntentProviderBase(ABC):
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
@@ -17,9 +18,9 @@ class IntentProviderBase(ABC):
def set_llm(self, llm): def set_llm(self, llm):
self.llm = llm self.llm = llm
logger.bind(tag=TAG).debug("Set LLM for intent provider") logger.bind(tag=TAG).debug("Set LLM for intent provider")
@abstractmethod @abstractmethod
async def detect_intent(self, dialogue_history: List[Dict]) -> str: async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str:
""" """
检测用户最后一句话的意图 检测用户最后一句话的意图
Args: Args:
@@ -1,7 +1,7 @@
from typing import List, Dict from typing import List, Dict
from ..base import IntentProviderBase from ..base import IntentProviderBase
from config.logger import setup_logging from config.logger import setup_logging
import re
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
@@ -20,42 +20,90 @@ class IntentProvider(IntentProviderBase):
格式化后的系统提示词 格式化后的系统提示词
""" """
intent_list = [] intent_list = []
"""
"continue_chat": "1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等",
"end_chat": "2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候",
"play_music": "3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图"
"""
for key, value in self.intent_options.items(): for key, value in self.intent_options.items():
if key == "play_music": if key == "play_music":
intent_list.append(f"{value} [歌名]") intent_list.append("3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图")
elif key == "end_chat":
intent_list.append("2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候")
elif key == "continue_chat":
intent_list.append("1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等")
else: else:
intent_list.append(value) intent_list.append(value)
# "如果是唱歌、听歌、播放音乐,请指定歌名,格式为'播放音乐 [识别出的歌名]'。\n"
# "如果听不出具体歌名,可以返回'随机播放音乐'。\n"
# "只需要返回意图结果的json,不要解释。"
# "返回格式如下:\n"
prompt = ( prompt = (
"你是一个意图识别助手。你需要根据和用户的对话记录,重点分析用户的最后一句话,判断用户意图属于以下哪一类:\n" "你是一个意图识别助手。你需要根据和用户的对话记录,重点分析用户的最后一句话,判断用户意图属于以下哪一类(使用<start>和<end>标志)\n"
f"{', '.join(intent_list)}\n" "<start>"
"如果是唱歌、听歌、播放音乐,请指定歌名,格式为'播放音乐 [识别出的歌名]'\n" f"{', '.join(intent_list)}"
"如果听不出具体歌名,可以返回'随机播放音乐'\n" "<end>\n"
"需要返回意图结果的json,不要解释。" "需要按照以下的步骤处理用户的对话"
"返回格式如下:\n" "1. 思考出对话的意图是哪一类的"
"{intent: '用户意图'}" "2. 属于1和2的意图, 直接返回,返回格式如下:\n"
"{intent: '用户意图'}\n"
"3. 属于3的意图,则继续分析用户希望播放的音乐\n"
"4. 如果无法识别出具体歌名,可以返回'随机播放音乐'\n"
"{intent: '播放音乐 [获取的音乐名字]'}\n"
"下面是几个处理的示例(思考的内容不返回, 只返回json部分, 无额外的内容)\n"
"```"
"用户: 你今天怎么样?\n"
"思考(不返回): 用户发来的数据是一个问候语,属于继续聊天的意图, 是种类1, 种类1的需求是直接返回\n"
"返回结果: {intent: '继续聊天'}\n"
"```"
"用户: 我今天有点累了, 我们明天再聊吧\n"
"思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n"
"返回结果: {intent: '结束聊天'}\n"
"```"
"用户: 我今天有点累了, 我们明天再聊吧\n"
"思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n"
"返回结果: {intent: '结束聊天'}\n"
"```"
"用户: 你可以播放一首中秋月给我听吗\n"
"思考(不返回): 用户表达了想听音乐的续签,属于播放音乐的意图, 是种类3, 种类3的需求需要继续判断播放的音乐, 这里用户希望的歌曲名明确给出是中秋月\n"
"返回结果: {intent: '播放音乐 [中秋月]'}\n"
"```"
"你现在可以使用的音乐的名称如下(使用<start>和<end>标志):\n"
) )
return prompt return prompt
async def detect_intent(self, dialogue_history: List[Dict]) -> str: async def detect_intent(self, conn, dialogue_history: List[Dict], text:str) -> str:
if not self.llm: if not self.llm:
raise ValueError("LLM provider not set") raise ValueError("LLM provider not set")
# 构建用户最后一句话的提示 # 构建用户最后一句话的提示
msgStr = "" msgStr = ""
for msg in dialogue_history:
if msg.role == "user":
msgStr += f"User: {msg.content}\n"
elif msg.role== "assistant":
msgStr += f"Assistant: {msg.content}\n"
user_prompt = f"请分析用户的意图:\n{msgStr}" # 只使用最后两句即可
if len(dialogue_history) >= 2:
# 保证最少有两句话的时候处理
msgStr += f"{dialogue_history[-2].role}: {dialogue_history[-2].content}\n"
msgStr += f"{dialogue_history[-1].role}: {dialogue_history[-1].content}\n"
msgStr += f"User: {text}\n"
user_prompt = f"当前的对话如下:\n{msgStr}"
prompt_music = f"{self.promot}\n<start>{conn.music_handler.music_files}\n<end>"
logger.bind(tag=TAG).debug(f"User prompt: {prompt_music}")
# 使用LLM进行意图识别 # 使用LLM进行意图识别
intent = self.llm.response_no_stream( intent = self.llm.response_no_stream(
system_prompt=self.promot, system_prompt=prompt_music,
user_prompt=user_prompt user_prompt=user_prompt
) )
# 使用正则表达式提取大括号中的内容
# 使用正则表达式提取 {} 中的内容
match = re.search(r'\{.*?\}', intent)
if match:
result = match.group(0) # 获取匹配到的内容(包含 {}
print(result) # 输出:{intent: '播放音乐 [中秋月]'}
intent = result
else:
intent = "{intent: '继续聊天'}"
logger.bind(tag=TAG).info(f"Detected intent: {intent}") logger.bind(tag=TAG).info(f"Detected intent: {intent}")
return intent.strip() return intent.strip()
@@ -5,12 +5,14 @@ from config.logger import setup_logging
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
class IntentProvider(IntentProviderBase): class IntentProvider(IntentProviderBase):
async def detect_intent(self, dialogue_history: List[Dict]) -> str: async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str:
""" """
默认的意图识别实现,始终返回继续聊天 默认的意图识别实现,始终返回继续聊天
Args: Args:
dialogue_history: 对话历史记录列表 dialogue_history: 对话历史记录列表
text: 本次对话记录
Returns: Returns:
固定返回"继续聊天" 固定返回"继续聊天"
""" """
@@ -11,19 +11,31 @@ from cozepy import Coze, TokenAuth, Message, ChatStatus, MessageContentType, Cha
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
class LLMProvider(LLMProviderBase): class LLMProvider(LLMProviderBase):
def __init__(self, config): def __init__(self, config):
self.personal_access_token = config.get("personal_access_token") self.personal_access_token = config.get("personal_access_token")
self.bot_id = config.get("bot_id") self.bot_id = config.get("bot_id")
self.user_id = config.get("user_id") self.user_id = config.get("user_id")
self.session_conversation_map = {} # 存储session_id和conversation_id的映射
def response(self, session_id, dialogue): def response(self, session_id, dialogue):
coze_api_token = self.personal_access_token coze_api_token = self.personal_access_token
coze_api_base = COZE_CN_BASE_URL coze_api_base = COZE_CN_BASE_URL
last_msg = next(m for m in reversed(dialogue) if m["role"] == "user") last_msg = next(m for m in reversed(dialogue) if m["role"] == "user")
coze = Coze(auth=TokenAuth(token=coze_api_token), base_url=coze_api_base) coze = Coze(auth=TokenAuth(token=coze_api_token), base_url=coze_api_base)
conversation_id = self.session_conversation_map.get(session_id)
# 如果没有找到conversation_id,则创建新的对话
if not conversation_id:
conversation = coze.conversations.create(
messages=[
]
)
conversation_id = conversation.id
self.session_conversation_map[session_id] = conversation_id # 更新映射
for event in coze.chat.stream( for event in coze.chat.stream(
bot_id=self.bot_id, bot_id=self.bot_id,
@@ -31,6 +43,7 @@ class LLMProvider(LLMProviderBase):
additional_messages=[ additional_messages=[
Message.build_user_question_text(last_msg["content"]), Message.build_user_question_text(last_msg["content"]),
], ],
conversation_id=conversation_id,
): ):
if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA:
print(event.message.content, end="", flush=True) print(event.message.content, end="", flush=True)
@@ -51,30 +51,8 @@ class LLMProvider(LLMProviderBase):
tools=functions, tools=functions,
) )
current_function_call = None
current_content = ""
for chunk in stream: for chunk in stream:
delta = chunk.choices[0].delta yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls
if delta.content:
current_content += delta.content
yield {"type": "content", "content": delta.content}
if delta.tool_calls:
tool_call = delta.tool_calls[0]
# Handle the function call data using proper attribute access
if not current_function_call:
current_function_call = {
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments
}
}
if current_function_call:
logger.bind(tag=TAG).debug(f"ollama Function call detected: {current_function_call}")
yield {"type": "function_call", "function_call": current_function_call}
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"Error in Ollama function call: {e}") logger.bind(tag=TAG).error(f"Error in Ollama function call: {e}")
@@ -53,30 +53,8 @@ class LLMProvider(LLMProviderBase):
tools=functions, tools=functions,
) )
current_function_call = None
current_content = ""
for chunk in stream: for chunk in stream:
delta = chunk.choices[0].delta yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls
if delta.content:
current_content += delta.content
yield {"type": "content", "content": delta.content}
if delta.tool_calls:
tool_call = delta.tool_calls[0]
# Handle the function call data using proper attribute access
if not current_function_call:
current_function_call = {
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments
}
}
if current_function_call:
logger.bind(tag=TAG).debug(f"openai Function call detected: {current_function_call}")
yield {"type": "function_call", "function_call": current_function_call}
except Exception as e: except Exception as e:
self.logger.bind(tag=TAG).error(f"Error in function call streaming: {e}") self.logger.bind(tag=TAG).error(f"Error in function call streaming: {e}")
+9
View File
@@ -4,6 +4,7 @@ import yaml
import socket import socket
import subprocess import subprocess
import logging import logging
import re
def get_project_dir(): def get_project_dir():
@@ -119,3 +120,11 @@ def check_ffmpeg_installed():
error_msg += "1、按照项目的安装文档,正确进入conda环境\n" error_msg += "1、按照项目的安装文档,正确进入conda环境\n"
error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n" error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n"
raise ValueError(error_msg) raise ValueError(error_msg)
def extract_json_from_string(input_string):
"""提取字符串中的 JSON 部分"""
pattern = r'(\{.*\})'
match = re.search(pattern, input_string)
if match:
return match.group(1) # 返回提取的 JSON 字符串
return None
@@ -65,6 +65,8 @@ class WebSocketServer:
server_config = self.config["server"] server_config = self.config["server"]
host = server_config["ip"] host = server_config["ip"]
port = server_config["port"] port = server_config["port"]
selected_module = self.config.get("selected_module")
self.logger.bind(tag=TAG).info(f"selected_module: {selected_module}")
self.logger.bind(tag=TAG).info("Server is running at ws://{}:{}", get_local_ip(), port) self.logger.bind(tag=TAG).info("Server is running at ws://{}:{}", get_local_ip(), port)
self.logger.bind(tag=TAG).info("=======上面的地址是websocket协议地址,请勿用浏览器访问=======") self.logger.bind(tag=TAG).info("=======上面的地址是websocket协议地址,请勿用浏览器访问=======")