Compare commits

..
71 Commits
Author SHA1 Message Date
hrzandGitHub 9430094072 update:修复退出保存记忆bug (#619) 2025-04-01 00:03:05 +08:00
欣南科技andGitHub 50490b70b7 Merge pull request #618 from xinnan-tech/test-server-pr
合并若干提交
2025-03-31 23:28:57 +08:00
欣南科技andGitHub fac3a0acd7 Merge branch 'main' into test-server-pr 2025-03-31 23:28:37 +08:00
hrz 73c4d6a604 update:优化提示词 2025-03-31 23:26:23 +08:00
hrz 6ba63db47f 1、将耗时最大的iot处理改成异步
2、iot循环5次等待func_handler加载完成
2025-03-31 22:54:37 +08:00
hrz fdd16ad3c7 update:
1、await self.close(ws)已经在finally有关闭逻辑了,无需在except里关闭。
2、func_handler和mcp_manager的加载必须用异步实现,减少唤醒连接好使
3、关于iot等待func_handler问题,要在iot消息那里做等待
2025-03-31 21:40:48 +08:00
hrz 9178faef6d update:取消从音频文件转成文字,提高响应速度 2025-03-31 21:24:48 +08:00
hrz e2da300e09 update:优化腾讯tts生成指南 2025-03-31 21:13:50 +08:00
hrz e11a37ffec update:优化测试web工具 2025-03-31 21:04:11 +08:00
hrzandGitHub 9ba836b814 修复唤醒缓存文件过小bug (#613)
* fix:音色表id为String

* update:修复唤醒缓存文件过小bug
2025-03-31 16:48:56 +08:00
CGDandGitHub a345d41e9e Merge pull request #609 from xinnan-tech/web_HeaderBar_SignOut
清除本地token缓存,完成前端退出
2025-03-31 15:36:51 +08:00
Sakura-RanChenandGitHub d36ea8a906 Merge pull request #608 from xinnan-tech/web-api-admin-get
Web api admin get
2025-03-31 15:35:31 +08:00
Ran_Chen 28f8533d0a 清除本地token缓存,完成前端退出 2025-03-31 15:34:26 +08:00
CGD 587b9e8800 完成用户管理模块的搜索功能 2025-03-31 15:25:57 +08:00
CGD c3a1b0f609 完成”获取用户列表“ 2025-03-31 14:44:23 +08:00
CGDandGitHub 1b25ed5903 Merge pull request #604 from xinnan-tech/web_ModelConf_optimize
优化了“模型配置的页面”
2025-03-31 14:32:46 +08:00
Ran_Chen e87c96300b 优化了“模型配置的页面” 2025-03-31 14:28:29 +08:00
hrzandGitHub ba5bf33b5a fix:音色表id为String (#603) 2025-03-31 11:02:58 +08:00
hrzandGitHub 94e1c09647 fix:音色表id为String (#602) 2025-03-31 10:57:27 +08:00
hrzandGitHub 6bd7ad9413 Merge pull request #600 from AdJIa/feat-test-page
feat: 添加了方便用于测试xiaozhi-server的web页面,支持文本、语音(基于libopus库的编解码)对话
2025-03-31 10:52:54 +08:00
hrzandGitHub d75032e853 Merge pull request #601 from gmm932/xiaozhi-test-pr
add:添加 Server 测试网页
2025-03-31 10:50:44 +08:00
CGD 261ecfea4d 优化了用户管理的样式 2025-03-31 10:24:37 +08:00
lujia 6f9cd94e66 feat: 添加了方便用于测试xiaozhi-server的web页面,支持文本、语音(基于libopus库的编解码)对话 2025-03-31 09:38:26 +08:00
安稳 ee2e1ed5fc add:添加 Server 测试网页 2025-03-31 09:35:35 +08:00
hrzandGitHub 92c1317d4c Merge pull request #576 from joey-zhou/tencent_tts
feat:增加腾讯云TTS服务
2025-03-30 18:36:33 +08:00
hrzandGitHub cfb97a087b Merge pull request #587 from sxd-mike/main
添加路由守护
2025-03-30 18:36:06 +08:00
hrzandGitHub 76ba99aa6c Merge pull request #591 from journey-ad/patch-2
一些功能优化和bug修复
2025-03-30 18:35:27 +08:00
hrzandGitHub 330d7ed95c Merge pull request #596 from xhit-ux/patch-1
Update: 修改错别字
2025-03-30 18:34:10 +08:00
拾尘andGitHub 878e73524b Update: 修改错别字 2025-03-30 18:14:10 +08:00
hrzandGitHub 13b0aa1a7b update:初始化音色表、模型配置表数据 (#592) 2025-03-30 15:10:15 +08:00
Jad 326342b834 chore(server): 移除高频触发的debug日志输出 2025-03-30 13:11:52 +08:00
Jad 8bbd610f12 fix(server): 适配1.5.2版本客户端的设备名
已经在 4c91bff 修复了自动注册的iot方法,和此插件功能重复了,也许可以删掉此插件?
另外观察到function_call准确率不够高,可能是llm的问题
2025-03-30 13:09:18 +08:00
Jad 549c2af988 feat(server): asr保留更多的音频前导帧
语速较快或者声音不清晰时,保留更多前导帧可以提升asr识别效果
2025-03-30 13:06:15 +08:00
Jad d02c56a0e0 feat(server): 使用主动退出指令时显示到设备上 2025-03-30 13:03:01 +08:00
Jad 1f3654cc01 feat(server): 开启唤醒词加速时返回唤醒词文本 2025-03-30 13:01:04 +08:00
Jad b6b6b0310f fix(server): 退出时等待保存记忆导致立即重连时阻塞 2025-03-30 12:58:27 +08:00
Jad 4c91bffcc7 fix(server): 加载插件改为初始化时同步执行 #559 2025-03-30 12:46:54 +08:00
hrzandGitHub d730a6cd06 update:初始化模型供应器数据 (#589) 2025-03-30 01:31:26 +08:00
林zp 67050f972e 添加一个路由守护 2025-03-30 00:20:09 +08:00
欣南科技andGitHub ffafd32693 Merge pull request #579 from xinnan-tech/test-manager-api
manage-api集成测试
2025-03-29 21:56:04 +08:00
hrz ab4ec5e468 update:完成user、agent、device三个模块接口测试 2025-03-29 21:52:06 +08:00
hrzandGitHub a07277feb8 Merge pull request #580 from xinnan-tech/manager-api-newdevice
修改了设备模块的绑定设备接口的逻辑
2025-03-29 18:10:00 +08:00
hrzandGitHub a3cd4b6e66 Merge branch 'test-manager-api' into manager-api-newdevice 2025-03-29 18:09:51 +08:00
hrz 25b7e76407 update:格式化代码 2025-03-29 18:00:06 +08:00
hrz 630cb13066 update:优化泛型代码 2025-03-29 17:49:39 +08:00
hrz 81188a0129 update:删除无用文件 2025-03-29 17:15:19 +08:00
hrzandGitHub cf1abd90a0 Merge pull request #577 from CaixyPromise/main
实现OTA接口
2025-03-29 17:12:08 +08:00
hrzandGitHub 6865a8be45 Merge branch 'test-manager-api' into main 2025-03-29 17:11:58 +08:00
hrz b63fcc80a1 update:整合现有版本 2025-03-29 17:06:53 +08:00
caixypromise a19ba6d48b chore: 因为在OTA激活设备时要求绑定用户信息,如果不把这里的调试功能注释则拿不到用户信息,会数据不完整。 2025-03-29 16:04:57 +08:00
caixypromise 70b6efe922 feature: 新增实现OTA功能——实现设备激活、前端输入激活码验证并保存设备。 2025-03-29 16:02:30 +08:00
caixypromise 0b07269346 chore: DeviceEntity设备实体类的Id字段类型错误,数据库内为varchar,映射类型为Long; 2025-03-29 15:59:48 +08:00
JoeyZhou 336954eeeb feat:增加腾讯云TTS服务 2025-03-29 15:18:15 +08:00
hrz 2988e5eef2 merge:main分支的manager-web和xiaozhi-server 2025-03-29 10:49:55 +08:00
hrzandGitHub fe87889ccd Merge pull request #572 from dreamchen/feature_manager-web&api_agent&device&roleConfig&ota&syncConfig
Feature manager web&api agent&device&role config&ota&sync config
2025-03-29 10:27:55 +08:00
hrzandGitHub 0598ab4400 Merge pull request #574 from LinZeb906791229/main
get_ip_info接口优化
2025-03-29 09:06:05 +08:00
hrzandGitHub 7d090f8ecc Merge pull request #573 from mkanako/fix_function_call_type
修复function call的参数类型定义错误
2025-03-29 09:02:44 +08:00
kanako 088ee47b3c 修复function call的参数类型定义错误 2025-03-28 23:44:07 +08:00
Erlei Chen a320b4c001 update:调整方法鉴权 2025-03-28 23:17:53 +08:00
Erlei Chen 3adce95340 Merge branch 'main' into MVP 2025-03-28 23:13:24 +08:00
abin 2a6bfd792a get_ip_info接口优化
将失效的https://freeipapi.com/api/json替换为https://whois.pconline.com.cn/ipJson.jsp?json=true
2025-03-28 22:52:21 +08:00
Erlei Chen 0816f1f589 feature:为合并mian做准备 2025-03-28 22:22:23 +08:00
剑雨 8914beae26 修改了设备模块的绑定设备接口的逻辑
--DeviceBindDTO.java 把原来设备请求头dto改为绑定dto
--DeviceController.java 修改设备绑定接口,改为需要提供智能体和设备验证码2个参数,通过设备验证码获取到mac地址,把用户id,mac地址,智能体id保存到数据库
--DeviceEntity.java 继承基础实体类
--DeviceService.java 修改绑定设备方法定义
--DeviceServiceImpl.java 重写绑定设备方法
2025-03-28 18:04:01 +08:00
Erlei Chen c565bfc6bc feature:实现角色配置 2025-03-27 22:14:52 +08:00
Erlei Chen 41db6aafd9 feature:角色配置能力部分实现 2025-03-27 14:06:40 +08:00
Erlei Chen 4b90e70aad Merge branch 'main' into MVP 2025-03-26 19:44:02 +08:00
Erlei Chen ee0da9fc3a feature:OTA能力完善 2025-03-26 17:00:54 +08:00
Erlei Chen bdd18b384b feature:
1.OTA基本能力(固定配置),待优化
2.智能体管理与设备管理对接api,实现流程闭环(已测试)
2025-03-24 09:26:45 +08:00
Erlei Chen 66352de1eb Merge branch 'main' into feature_manage-web&api_agent&device 2025-03-22 08:37:38 +08:00
Erlei Chen a7730f6e93 update:修改样式、调整页面命名 2025-03-22 08:33:24 +08:00
Erlei Chen 4409f37000 合并样式及功能 2025-03-21 01:39:23 +08:00
177 changed files with 6256 additions and 1381 deletions
+1
View File
@@ -156,3 +156,4 @@ main/manager-web/node_modules
main/xiaozhi-server/models/SenseVoiceSmall/model.pt
main/xiaozhi-server/models/sherpa-onnx*
my_wakeup_words.mp3
main/manager-api/.vscode
+2 -2
View File
@@ -241,7 +241,7 @@ pip install -r requirements.txt
## 4.配置项目文件
接下,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
接下,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
[跳转到配置项目文件](#配置项目)
## 5.运行项目
@@ -338,4 +338,4 @@ LLM:
[5、我说话很慢,停顿时小智老是抢话](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
[6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
[6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
+1 -3
View File
@@ -18,6 +18,4 @@ xiaozhi-esp32-server
# manager-web 、manager-api接口协议
[manager前后端接口协议](https://app.apifox.com/invite/project?token=eXg2_tUv85q-gc3ZRowmn)
[前端页面设计图](https://codesign.qq.com/app/s/526108506410828)
https://2662r3426b.vicp.fun/xiaozhi-esp32-api/api/v1/doc.html
+25 -2
View File
@@ -5,12 +5,11 @@ manager-api 该项目基于SpringBoot框架开发。
开发使用代码编辑器,导入项目时,选择`manager-api`文件夹作为项目目录
参照[manager前后端接口协议](https://app.apifox.com/invite/project?token=H_8qhgfjUeaAL0wybghgU)开发
# 开发环境
JDK 21
Maven 3.8+
MySQL 8.0+
Redis 5.0+
Vue 3.x
# 创建数据库
@@ -43,6 +42,30 @@ spring:
password: 123456
```
# 连接Redis
如果还没有Redis,你可以通过docker安装redis
```
docker run --name xiaozhi-esp32-server-redis -d -p 6379:6379 redis
```
# 确认项目Redis连接信息
`src/main/resources/application-dev.yml`中配置Redis连接信息
```
spring:
data:
redis:
host: localhost
port: 6379
password:
database: 0
```
# 测试启动
本项目为SpringBoot项目,启动方式为:
+31 -2
View File
@@ -18,7 +18,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>21</java.version>
<junit.version>5.12.0</junit.version>
<junit.version>5.10.1</junit.version>
<druid.version>1.2.20</druid.version>
<mybatisplus.version>3.5.5</mybatisplus.version>
<hutool.version>5.8.24</hutool.version>
@@ -27,7 +27,6 @@
<shiro.version>2.0.2</shiro.version>
<captcha.version>1.6.2</captcha.version>
<guava.version>33.0.0-jre</guava.version>
<easyexcel.version>3.3.2</easyexcel.version>
<liquibase-core.version>4.20.0</liquibase-core.version>
</properties>
@@ -88,6 +87,36 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
<version>1.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
<version>1.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@@ -1,6 +1,10 @@
package xiaozhi.common.annotation;
import java.lang.annotation.*;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 数据过滤注解
@@ -1,6 +1,10 @@
package xiaozhi.common.annotation;
import java.lang.annotation.*;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 操作日志注解
@@ -1,14 +1,15 @@
package xiaozhi.common.aspect;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
/**
* Redis切面处理类
* Copyright (c) 人人开源 All rights reserved.
@@ -19,7 +20,7 @@ import org.springframework.stereotype.Component;
@Component
public class RedisAspect {
/**
* 是否开启redis缓存 true开启 false关闭
* 是否开启redis缓存 true开启 false关闭
*/
@Value("${renren.redis.open}")
private boolean open;
@@ -1,12 +1,14 @@
package xiaozhi.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import xiaozhi.common.interceptor.DataFilterInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* mybatis-plus配置
@@ -1,11 +1,12 @@
package xiaozhi.common.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
/**
* Swagger配置
* Copyright (c) 人人开源 All rights reserved.
@@ -14,11 +15,60 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public GroupedOpenApi deviceApi() {
return GroupedOpenApi.builder()
.group("device")
.pathsToMatch("/device/**")
.build();
}
@Bean
public GroupedOpenApi agentApi() {
return GroupedOpenApi.builder()
.group("agent")
.pathsToMatch("/agent/**")
.build();
}
@Bean
public GroupedOpenApi modelApi() {
return GroupedOpenApi.builder()
.group("models")
.pathsToMatch("/models/**")
.build();
}
@Bean
public GroupedOpenApi oatApi() {
return GroupedOpenApi.builder()
.group("ota")
.pathsToMatch("/ota/**")
.build();
}
@Bean
public GroupedOpenApi timbreApi() {
return GroupedOpenApi.builder()
.group("timbre")
.pathsToMatch("/timbre/**")
.build();
}
@Bean
public GroupedOpenApi sysApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/admin/**")
.build();
}
@Bean
public GroupedOpenApi userApi() {
String[] paths = {"/**"};
return GroupedOpenApi.builder().group("xiaozhi")
.pathsToMatch(paths).build();
return GroupedOpenApi.builder()
.group("user")
.pathsToMatch("/user/**")
.build();
}
@Bean
@@ -1,17 +1,17 @@
package xiaozhi.common.convert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
/**
* 日期转换
* Copyright (c) 人人开源 All rights reserved.
@@ -1,12 +1,13 @@
package xiaozhi.common.entity;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 基础实体类,所有实体都需要继承
@@ -1,6 +1,5 @@
package xiaozhi.common.exception;
import xiaozhi.common.utils.MessageUtils;
/**
@@ -10,7 +9,6 @@ import xiaozhi.common.utils.MessageUtils;
*/
public class RenException extends RuntimeException {
private int code;
private String msg;
@@ -1,13 +1,13 @@
package xiaozhi.common.exception;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import xiaozhi.common.utils.Result;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.utils.Result;
/**
* 异常处理器
@@ -23,35 +23,34 @@ public class RenExceptionHandler {
* 处理自定义异常
*/
@ExceptionHandler(RenException.class)
public Result handleRenException(RenException ex) {
Result result = new Result();
public Result<Void> handleRenException(RenException ex) {
Result<Void> result = new Result<>();
result.error(ex.getCode(), ex.getMsg());
return result;
}
@ExceptionHandler(DuplicateKeyException.class)
public Result handleDuplicateKeyException(DuplicateKeyException ex) {
Result result = new Result();
public Result<Void> handleDuplicateKeyException(DuplicateKeyException ex) {
Result<Void> result = new Result<>();
result.error(ErrorCode.DB_RECORD_EXISTS);
return result;
}
@ExceptionHandler(UnauthorizedException.class)
public Result handleUnauthorizedException(UnauthorizedException ex) {
Result result = new Result();
public Result<Void> handleUnauthorizedException(UnauthorizedException ex) {
Result<Void> result = new Result<>();
result.error(ErrorCode.FORBIDDEN);
return result;
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception ex) {
public Result<Void> handleException(Exception ex) {
log.error(ex.getMessage(), ex);
return new Result().error();
return new Result<Void>().error();
}
}
@@ -1,13 +1,15 @@
package xiaozhi.common.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.user.UserDetail;
import xiaozhi.modules.security.user.SecurityUser;
import java.util.Date;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.user.UserDetail;
import xiaozhi.modules.security.user.SecurityUser;
/**
* 公共字段,自动填充值
@@ -22,35 +24,34 @@ public class FieldMetaObjectHandler implements MetaObjectHandler {
private final static String UPDATER = "updater";
private final static String DATA_OPERATION = "dataOperation";
private final static String DEPT_ID = "deptId";
@Override
public void insertFill(MetaObject metaObject) {
UserDetail user = SecurityUser.getUser();
Date date = new Date();
//创建者
// 创建者
strictInsertFill(metaObject, CREATOR, Long.class, user.getId());
//创建时间
// 创建时间
strictInsertFill(metaObject, CREATE_DATE, Date.class, date);
//更新者
// 更新者
strictInsertFill(metaObject, UPDATER, Long.class, user.getId());
//更新时间
// 更新时间
strictInsertFill(metaObject, UPDATE_DATE, Date.class, date);
//数据标识
// 数据标识
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.INSERT.getValue());
}
@Override
public void updateFill(MetaObject metaObject) {
//更新者
// 更新者
strictUpdateFill(metaObject, UPDATER, Long.class, SecurityUser.getUserId());
//更新时间
// 更新时间
strictUpdateFill(metaObject, UPDATE_DATE, Date.class, new Date());
//数据标识
// 数据标识
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.UPDATE.getValue());
}
}
@@ -1,8 +1,17 @@
package xiaozhi.common.interceptor;
import cn.hutool.core.util.StrUtil;
import java.util.Map;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import cn.hutool.core.util.StrUtil;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
@@ -10,13 +19,6 @@ import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Map;
/**
* 数据过滤
@@ -25,8 +27,10 @@ import java.util.Map;
*/
public class DataFilterInterceptor implements InnerInterceptor {
@SuppressWarnings("rawtypes")
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
DataScope scope = getDataScope(parameter);
// 不进行数据过滤
if (scope == null || StrUtil.isBlank(scope.getSqlFilter())) {
@@ -48,7 +52,7 @@ public class DataFilterInterceptor implements InnerInterceptor {
// 判断参数里是否有DataScope对象
if (parameter instanceof Map) {
Map<?, ?> parameterMap = (Map<?, ?>) parameter;
for (Map.Entry entry : parameterMap.entrySet()) {
for (Map.Entry<?, ?> entry : parameterMap.entrySet()) {
if (entry.getValue() != null && entry.getValue() instanceof DataScope) {
return (DataScope) entry.getValue();
}
@@ -1,11 +1,11 @@
package xiaozhi.common.page;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 分页工具类
* Copyright (c) 人人开源 All rights reserved.
@@ -1,10 +1,10 @@
package xiaozhi.common.page;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 令牌信息
*
@@ -14,7 +14,6 @@ import java.io.Serializable;
@Schema(description = "令牌信息")
public class TokenDTO implements Serializable {
@Schema(description = "密码")
private String token;
@@ -1,6 +1,5 @@
package xiaozhi.common.redis;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -8,6 +7,8 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import jakarta.annotation.Resource;
/**
* Redis配置
* Copyright (c) 人人开源 All rights reserved.
@@ -1,14 +1,15 @@
package xiaozhi.common.redis;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
/**
* Redis工具类
* Copyright (c) 人人开源 All rights reserved.
@@ -1,10 +1,10 @@
package xiaozhi.common.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import java.io.Serializable;
import java.util.Collection;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
/**
* 基础服务接口,所有Service接口都要继承
* Copyright (c) 人人开源 All rights reserved.
@@ -56,7 +56,8 @@ public interface BaseService<T> {
* </p>
*
* @param entity 实体对象
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
* @param updateWrapper 实体对象封装操作类
* {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
boolean update(T entity, Wrapper<T> updateWrapper);
@@ -1,11 +1,11 @@
package xiaozhi.common.service;
import xiaozhi.common.page.PageData;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import xiaozhi.common.page.PageData;
/**
* CRUD基础服务接口
* Copyright (c) 人人开源 All rights reserved.
@@ -1,5 +1,18 @@
package xiaozhi.common.service.impl;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -10,22 +23,11 @@ import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.common.utils.ConvertUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* 基础服务类,所有Service都要继承
@@ -45,7 +47,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
* @param isAsc 排序方式
*/
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
//分页参数
// 分页参数
long curPage = 1;
long limit = 10;
@@ -56,17 +58,17 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
limit = Long.parseLong((String) params.get(Constant.LIMIT));
}
//分页对象
// 分页对象
Page<T> page = new Page<>(curPage, limit);
//分页参数
// 分页参数
params.put(Constant.PAGE, page);
//排序字段
// 排序字段
String orderField = (String) params.get(Constant.ORDER_FIELD);
String order = (String) params.get(Constant.ORDER);
//前端字段排序
// 前端字段排序
if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) {
if (Constant.ASC.equalsIgnoreCase(order)) {
return page.addOrder(OrderItem.asc(orderField));
@@ -75,12 +77,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
}
}
//没有排序字段,则不排序
// 没有排序字段,则不排序
if (StringUtils.isBlank(defaultOrderField)) {
return page;
}
//默认排序
// 默认排序
if (isAsc) {
page.addOrder(OrderItem.asc(defaultOrderField));
} else {
@@ -90,13 +92,13 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
return page;
}
protected <T> PageData<T> getPageData(List<?> list, long total, Class<T> target) {
List<T> targetList = ConvertUtils.sourceToTarget(list, target);
protected <D> PageData<D> getPageData(List<?> list, long total, Class<D> target) {
List<D> targetList = ConvertUtils.sourceToTarget(list, target);
return new PageData<>(targetList, total);
}
protected <T> PageData<T> getPageData(IPage page, Class<T> target) {
protected <D> PageData<D> getPageData(IPage<?> page, Class<D> target) {
return getPageData(page.getRecords(), page.getTotal(), target);
}
@@ -126,10 +128,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
return SqlHelper.retBool(result);
}
@SuppressWarnings("unchecked")
protected Class<M> currentMapperClass() {
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
}
@SuppressWarnings("unchecked")
@Override
public Class<T> currentModelClass() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
@@ -164,11 +168,11 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
/**
* 执行批量操作
*/
@SuppressWarnings("deprecation")
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateById(T entity) {
@@ -1,26 +1,30 @@
package xiaozhi.common.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.CrudService;
import xiaozhi.common.utils.ConvertUtils;
import org.springframework.beans.BeanUtils;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.CrudService;
import xiaozhi.common.utils.ConvertUtils;
/**
* CRUD基础服务类
* Copyright (c) 人人开源 All rights reserved.
* Website: https://www.renren.io
*/
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T> implements CrudService<T, D> {
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T>
implements CrudService<T, D> {
@SuppressWarnings("unchecked")
protected Class<D> currentDtoClass() {
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
}
@@ -29,8 +33,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
public PageData<D> page(Map<String, Object> params) {
IPage<T> page = baseDao.selectPage(
getPage(params, null, false),
getWrapper(params)
);
getWrapper(params));
return getPageData(page, currentDtoClass());
}
@@ -56,7 +59,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
T entity = ConvertUtils.sourceToTarget(dto, currentModelClass());
insert(entity);
//copy主键值到dto
// copy主键值到dto
BeanUtils.copyProperties(entity, dto);
}
@@ -1,9 +1,9 @@
package xiaozhi.common.user;
import lombok.Data;
import java.io.Serializable;
import lombok.Data;
/**
* 登录用户信息
* Copyright (c) 人人开源 All rights reserved.
@@ -1,12 +1,13 @@
package xiaozhi.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.BeanUtils;
import lombok.extern.slf4j.Slf4j;
/**
* 转换工具类
* Copyright (c) 人人开源 All rights reserved.
@@ -20,7 +21,7 @@ public class ConvertUtils {
}
T targetObject = null;
try {
targetObject = target.newInstance();
targetObject = target.getDeclaredConstructor().newInstance();
BeanUtils.copyProperties(source, targetObject);
} catch (Exception e) {
log.error("convert error ", e);
@@ -34,10 +35,10 @@ public class ConvertUtils {
return null;
}
List targetList = new ArrayList<>(sourceList.size());
List<T> targetList = new ArrayList<>(sourceList.size());
try {
for (Object source : sourceList) {
T targetObject = target.newInstance();
T targetObject = target.getDeclaredConstructor().newInstance();
BeanUtils.copyProperties(source, targetObject);
targetList.add(targetObject);
}
@@ -1,20 +1,21 @@
package xiaozhi.common.utils;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* Http
* Copyright (c) 人人开源 All rights reserved.
@@ -70,15 +71,15 @@ public class HttpContextUtils {
}
public static String getLanguage() {
//默认语言
// 默认语言
String defaultLanguage = "zh-CN";
//request
// request
HttpServletRequest request = getHttpServletRequest();
if (request == null) {
return defaultLanguage;
}
//请求语言
// 请求语言
defaultLanguage = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE);
return defaultLanguage;
@@ -1,8 +1,9 @@
package xiaozhi.common.utils;
import org.apache.commons.lang3.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
* IP地址
@@ -1,13 +1,14 @@
package xiaozhi.common.utils;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
/**
* JSON 工具类
* Copyright (c) 人人开源 All rights reserved.
@@ -59,7 +60,8 @@ public class JsonUtils {
return new ArrayList<>();
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
return objectMapper.readValue(text,
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -1,11 +1,11 @@
package xiaozhi.common.utils;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import xiaozhi.common.exception.ErrorCode;
import java.io.Serializable;
/**
* 响应数据
* Copyright (c) 人人开源 All rights reserved.
@@ -36,7 +36,6 @@ public class Result<T> implements Serializable {
return this;
}
public Result<T> error() {
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
this.msg = MessageUtils.getMessage(this.code);
@@ -1,11 +1,11 @@
package xiaozhi.common.utils;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
/**
* 树节点,所有需要实现树节点的,都需要继承该类
* Copyright (c) 人人开源 All rights reserved.
@@ -1,12 +1,12 @@
package xiaozhi.common.utils;
import xiaozhi.common.validator.AssertUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import xiaozhi.common.validator.AssertUtils;
/**
* 树形结构工具类,如:菜单、部门等
* Copyright (c) 人人开源 All rights reserved.
@@ -17,8 +17,8 @@ public class TreeUtils {
/**
* 根据pid,构建树节点
*/
public static <T extends TreeNode> List<T> build(List<T> treeNodes, Long pid) {
//pid不能为空
public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes, Long pid) {
// pid不能为空
AssertUtils.isNull(pid, "pid");
List<T> treeList = new ArrayList<>();
@@ -34,7 +34,7 @@ public class TreeUtils {
/**
* 查找子节点
*/
private static <T extends TreeNode> T findChildren(List<T> treeNodes, T rootNode) {
private static <T extends TreeNode<T>> T findChildren(List<T> treeNodes, T rootNode) {
for (T treeNode : treeNodes) {
if (rootNode.getId().equals(treeNode.getPid())) {
rootNode.getChildren().add(findChildren(treeNodes, treeNode));
@@ -46,10 +46,10 @@ public class TreeUtils {
/**
* 构建树节点
*/
public static <T extends TreeNode> List<T> build(List<T> treeNodes) {
public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes) {
List<T> result = new ArrayList<>();
//list转map
// list转map
Map<Long, T> nodeMap = new LinkedHashMap<>(treeNodes.size());
for (T treeNode : treeNodes) {
nodeMap.put(treeNode.getId(), treeNode);
@@ -1,14 +1,15 @@
package xiaozhi.common.validator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
/**
* 校验工具类
@@ -73,11 +74,11 @@ public class AssertUtils {
}
}
public static void isMapEmpty(Map map, String... params) {
public static void isMapEmpty(Map<?, ?> map, String... params) {
isMapEmpty(map, ErrorCode.NOT_NULL, params);
}
public static void isMapEmpty(Map map, Integer code, String... params) {
public static void isMapEmpty(Map<?, ?> map, Integer code, String... params) {
if (code == null) {
throw new RenException(ErrorCode.NOT_NULL, "code");
}
@@ -1,16 +1,17 @@
package xiaozhi.common.validator;
import xiaozhi.common.exception.RenException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import java.util.Locale;
import java.util.Set;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator;
import java.util.Locale;
import java.util.Set;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import xiaozhi.common.exception.RenException;
/**
* hibernate-validator校验工具类
@@ -35,7 +36,7 @@ public class ValidatorUtils {
throws RenException {
Locale.setDefault(LocaleContextHolder.getLocale());
Validator validator = Validation.byDefaultProvider().configure().messageInterpolator(
new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource())))
new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource())))
.buildValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
@@ -1,8 +1,9 @@
package xiaozhi.common.xss;
import org.apache.commons.lang3.StringUtils;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import org.apache.commons.lang3.StringUtils;
/**
* SQL过滤
@@ -20,19 +21,20 @@ public class SqlFilter {
if (StringUtils.isBlank(str)) {
return null;
}
//去掉'|"|;|\字符
// 去掉'|"|;|\字符
str = StringUtils.replace(str, "'", "");
str = StringUtils.replace(str, "\"", "");
str = StringUtils.replace(str, ";", "");
str = StringUtils.replace(str, "\\", "");
//转换成小写
// 转换成小写
str = str.toLowerCase();
//非法字符
String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"};
// 非法字符
String[] keywords = { "master", "truncate", "insert", "select", "delete", "update", "declare", "alter",
"drop" };
//判断是否包含非法字符
// 判断是否包含非法字符
for (String keyword : keywords) {
if (str.contains(keyword)) {
throw new RenException(ErrorCode.INVALID_SYMBOL);
@@ -1,6 +1,5 @@
package xiaozhi.common.xss;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -8,6 +7,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.PathMatcher;
import jakarta.servlet.DispatcherType;
/**
* XSS 配置文件
* Copyright (c) 人人开源 All rights reserved.
@@ -1,11 +1,17 @@
package xiaozhi.common.xss;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import java.io.IOException;
import org.springframework.util.PathMatcher;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
/**
* XSS过滤
@@ -38,7 +44,8 @@ public class XssFilter implements Filter {
private boolean shouldNotFilter(HttpServletRequest request) {
// 放行不过滤的URL
return properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath()));
return properties.getExcludeUrls().stream()
.anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath()));
}
@Override
@@ -1,20 +1,20 @@
package xiaozhi.common.xss;
import cn.hutool.core.io.IoUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import cn.hutool.core.io.IoUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
/**
* XSS过滤处理
@@ -29,18 +29,18 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
@Override
public ServletInputStream getInputStream() throws IOException {
//非json类型,直接返回
// 非json类型,直接返回
if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) {
return super.getInputStream();
}
//为空,直接返回
// 为空,直接返回
String json = IoUtil.readUtf8(super.getInputStream());
if (StringUtils.isBlank(json)) {
return super.getInputStream();
}
//xss过滤
// xss过滤
json = xssEncode(json);
final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() {
@@ -1,11 +1,12 @@
package xiaozhi.common.xss;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
/**
* XSS 配置项
* Copyright (c) 人人开源 All rights reserved.
@@ -22,13 +22,15 @@ public class XssUtils extends Safelist {
*/
private static Safelist xssWhitelist() {
return new Safelist()
//支持的标签
// 支持的标签
.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl",
"dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q", "small",
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul",
"dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q",
"small",
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u",
"ul",
"embed", "object", "param", "span")
//支持的标签属性
// 支持的标签属性
.addAttributes("a", "href", "class", "style", "target", "rel", "nofollow")
.addAttributes("blockquote", "cite")
.addAttributes("code", "class", "style")
@@ -44,13 +46,16 @@ public class XssUtils extends Safelist {
.addAttributes("ul", "type", "style")
.addAttributes("pre", "class", "style")
.addAttributes("div", "class", "id", "style")
.addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen", "allowfullscreen",
"quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess", "allownetworking", "type")
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid", "codebase")
.addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen",
"allowfullscreen",
"quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess",
"allownetworking", "type")
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid",
"codebase")
.addAttributes("param", "name", "value")
.addAttributes("span", "class", "style")
//标签属性对应的协议
// 标签属性对应的协议
.addProtocols("a", "href", "ftp", "http", "https", "mailto")
.addProtocols("img", "src", "http", "https")
.addProtocols("blockquote", "cite", "http", "https")
@@ -1,13 +1,28 @@
package xiaozhi.modules.agent.controller;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData;
import xiaozhi.common.user.UserDetail;
@@ -16,20 +31,19 @@ import xiaozhi.common.utils.Result;
import xiaozhi.modules.agent.dto.AgentCreateDTO;
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentTemplateService;
import xiaozhi.modules.security.user.SecurityUser;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Tag(name = "智能体管理")
@AllArgsConstructor
@RestController
@RequestMapping("/agent")
public class AgentController {
private final AgentService agentService;
private final AgentTemplateService agentTemplateService;
@GetMapping("/list")
@Operation(summary = "获取用户智能体列表")
@RequiresPermissions("sys:role:normal")
@@ -38,7 +52,7 @@ public class AgentController {
List<AgentEntity> agents = agentService.getUserAgents(user.getId());
return new Result<List<AgentEntity>>().ok(agents);
}
@GetMapping("/all")
@Operation(summary = "智能体列表(管理员)")
@RequiresPermissions("sys:role:superAdmin")
@@ -51,7 +65,7 @@ public class AgentController {
PageData<AgentEntity> page = agentService.adminAgentList(params);
return new Result<PageData<AgentEntity>>().ok(page);
}
@GetMapping("/{id}")
@Operation(summary = "获取智能体详情")
@RequiresPermissions("sys:role:normal")
@@ -59,35 +73,35 @@ public class AgentController {
AgentEntity agent = agentService.getAgentById(id);
return new Result<AgentEntity>().ok(agent);
}
@PostMapping
@Operation(summary = "创建智能体")
@RequiresPermissions("sys:role:normal")
public Result<Void> save(@RequestBody @Valid AgentCreateDTO dto) {
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
// 设置用户ID和创建者信息
UserDetail user = SecurityUser.getUser();
entity.setUserId(user.getId());
entity.setCreator(user.getId());
entity.setCreatedAt(new Date());
// ID、智能体编码和排序会在Service层自动生成
agentService.insert(entity);
return new Result<>();
}
@PutMapping
@PutMapping("/{id}")
@Operation(summary = "更新智能体")
@RequiresPermissions("sys:role:normal")
public Result<Void> update(@RequestBody @Valid AgentUpdateDTO dto) {
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
// 先查询现有实体
AgentEntity existingEntity = agentService.getAgentById(dto.getId());
AgentEntity existingEntity = agentService.getAgentById(id);
if (existingEntity == null) {
return new Result<Void>().error("智能体不存在");
}
// 只更新提供的非空字段
if (dto.getAgentName() != null) {
existingEntity.setAgentName(dto.getAgentName());
@@ -128,17 +142,17 @@ public class AgentController {
if (dto.getSort() != null) {
existingEntity.setSort(dto.getSort());
}
// 设置更新者信息
UserDetail user = SecurityUser.getUser();
existingEntity.setUpdater(user.getId());
existingEntity.setUpdatedAt(new Date());
agentService.updateById(existingEntity);
return new Result<>();
}
@DeleteMapping("/{id}")
@Operation(summary = "删除智能体")
@RequiresPermissions("sys:role:normal")
@@ -146,4 +160,13 @@ public class AgentController {
agentService.deleteById(id);
return new Result<>();
}
}
@GetMapping("/template")
@Operation(summary = "智能体模板模板列表")
@RequiresPermissions("sys:role:normal")
public Result<List<AgentTemplateEntity>> templateList() {
List<AgentTemplateEntity> list = agentTemplateService
.list(new QueryWrapper<AgentTemplateEntity>().orderByAsc("sort"));
return new Result<List<AgentTemplateEntity>>().ok(list);
}
}
@@ -1,10 +1,11 @@
package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.agent.entity.AgentEntity;
@Mapper
public interface AgentDao extends BaseDao<AgentEntity> {
}
}
@@ -0,0 +1,17 @@
package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
/**
* @author chenerlei
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Mapper
* @createDate 2025-03-22 11:48:18
*/
@Mapper
public interface AgentTemplateDao extends BaseMapper<AgentTemplateEntity> {
}
@@ -1,11 +1,11 @@
package xiaozhi.modules.agent.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serializable;
/**
* 智能体创建DTO
* 专用于新增智能体,不包含id、agentCode和sort字段,这些字段由系统自动生成/设置默认值
@@ -14,38 +14,8 @@ import java.io.Serializable;
@Schema(description = "智能体创建对象")
public class AgentCreateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "智能体名称", example = "客服助手")
@NotBlank(message = "智能体名称不能为空")
private String agentName;
@Schema(description = "语音识别模型标识", example = "asr_model_01")
private String asrModelId;
@Schema(description = "语音活动检测标识", example = "vad_model_01")
private String vadModelId;
@Schema(description = "大语言模型标识", example = "llm_model_01")
private String llmModelId;
@Schema(description = "语音合成模型标识", example = "tts_model_01")
private String ttsModelId;
@Schema(description = "音色标识", example = "voice_01")
private String ttsVoiceId;
@Schema(description = "记忆模型标识", example = "mem_model_01")
private String memModelId;
@Schema(description = "意图模型标识", example = "intent_model_01")
private String intentModelId;
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题")
private String systemPrompt;
@Schema(description = "语言编码", example = "zh_CN")
private String langCode;
@Schema(description = "交互语种", example = "中文")
private String language;
}
}
@@ -1,11 +1,10 @@
package xiaozhi.modules.agent.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 智能体更新DTO
* 专用于更新智能体,id字段是必需的,用于标识要更新的智能体
@@ -16,46 +15,42 @@ import java.io.Serializable;
public class AgentUpdateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "智能体唯一标识", example = "a1b2c3d4e5f6", required = true)
@NotBlank(message = "智能体ID不能为空")
private String id;
@Schema(description = "智能体编码", example = "AGT_1234567890", required = false)
private String agentCode;
@Schema(description = "智能体名称", example = "客服助手", required = false)
private String agentName;
@Schema(description = "语音识别模型标识", example = "asr_model_02", required = false)
private String asrModelId;
@Schema(description = "语音活动检测标识", example = "vad_model_02", required = false)
private String vadModelId;
@Schema(description = "大语言模型标识", example = "llm_model_02", required = false)
private String llmModelId;
@Schema(description = "语音合成模型标识", example = "tts_model_02", required = false)
private String ttsModelId;
@Schema(description = "音色标识", example = "voice_02", required = false)
private String ttsVoiceId;
@Schema(description = "记忆模型标识", example = "mem_model_02", required = false)
private String memModelId;
@Schema(description = "意图模型标识", example = "intent_model_02", required = false)
private String intentModelId;
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false)
private String systemPrompt;
@Schema(description = "语言编码", example = "zh_CN", required = false)
private String langCode;
@Schema(description = "交互语种", example = "中文", required = false)
private String language;
@Schema(description = "排序", example = "1", required = false)
private Integer sort;
}
}
@@ -1,69 +1,74 @@
package xiaozhi.modules.agent.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
@Data
@TableName("ai_agent")
@Schema(description = "智能体信息")
public class AgentEntity {
@TableId(type = IdType.ASSIGN_UUID)
@Schema(description = "智能体唯一标识")
private String id;
@Schema(description = "所属用户ID")
private Long userId;
@Schema(description = "智能体编码")
private String agentCode;
@Schema(description = "智能体名称")
private String agentName;
@Schema(description = "语音识别模型标识")
private String asrModelId;
@Schema(description = "语音活动检测标识")
private String vadModelId;
@Schema(description = "大语言模型标识")
private String llmModelId;
@Schema(description = "语音合成模型标识")
private String ttsModelId;
@Schema(description = "音色标识")
private String ttsVoiceId;
@Schema(description = "记忆模型标识")
private String memModelId;
@Schema(description = "意图模型标识")
private String intentModelId;
@Schema(description = "角色设定参数")
private String systemPrompt;
@Schema(description = "语言编码")
private String langCode;
@Schema(description = "交互语种")
private String language;
@Schema(description = "排序")
private Integer sort;
@Schema(description = "创建者")
private Long creator;
@Schema(description = "创建时间")
private Date createdAt;
@Schema(description = "更新者")
private Long updater;
@Schema(description = "更新时间")
private Date updatedAt;
}
}
@@ -0,0 +1,114 @@
package xiaozhi.modules.agent.entity;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 智能体配置模板表
*
* @TableName ai_agent_template
*/
@TableName(value = "ai_agent_template")
@Data
public class AgentTemplateEntity implements Serializable {
/**
* 智能体唯一标识
*/
@TableId(type = IdType.ASSIGN_UUID)
private String id;
/**
* 智能体编码
*/
private String agentCode;
/**
* 智能体名称
*/
private String agentName;
/**
* 语音识别模型标识
*/
private String asrModelId;
/**
* 语音活动检测标识
*/
private String vadModelId;
/**
* 大语言模型标识
*/
private String llmModelId;
/**
* 语音合成模型标识
*/
private String ttsModelId;
/**
* 音色标识
*/
private String ttsVoiceId;
/**
* 记忆模型标识
*/
private String memModelId;
/**
* 意图模型标识
*/
private String intentModelId;
/**
* 角色设定参数
*/
private String systemPrompt;
/**
* 语言编码
*/
private String langCode;
/**
* 交互语种
*/
private String language;
/**
* 排序权重
*/
private Integer sort;
/**
* 创建者 ID
*/
private Long creator;
/**
* 创建时间
*/
private Date createdAt;
/**
* 更新者 ID
*/
private Long updater;
/**
* 更新时间
*/
private Date updatedAt;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
@@ -1,25 +1,25 @@
package xiaozhi.modules.agent.service;
import java.util.List;
import java.util.Map;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.agent.entity.AgentEntity;
import java.util.List;
import java.util.Map;
public interface AgentService extends BaseService<AgentEntity> {
/**
* 根据用户ID获取智能体列表
*/
List<AgentEntity> getUserAgents(Long userId);
/**
* 管理员获取所有智能体列表(分页)
*/
PageData<AgentEntity> adminAgentList(Map<String, Object> params);
/**
* 获取智能体详情
*/
AgentEntity getAgentById(String id);
}
}
@@ -0,0 +1,14 @@
package xiaozhi.modules.agent.service;
import com.baomidou.mybatisplus.extension.service.IService;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
/**
* @author chenerlei
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service
* @createDate 2025-03-22 11:48:18
*/
public interface AgentTemplateService extends IService<AgentTemplateEntity> {
}
@@ -1,64 +1,65 @@
package xiaozhi.modules.agent.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.stereotype.Service;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.modules.agent.dao.AgentDao;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.service.AgentService;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Service
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
private final AgentDao agentDao;
public AgentServiceImpl(AgentDao agentDao) {
this.agentDao = agentDao;
}
@Override
public List<AgentEntity> getUserAgents(Long userId) {
QueryWrapper<AgentEntity> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", userId);
return agentDao.selectList(wrapper);
}
@Override
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
IPage<AgentEntity> page = agentDao.selectPage(
getPage(params, "sort", true),
new QueryWrapper<>()
);
return new PageData<>(page.getRecords(), page.getTotal());
}
@Override
public AgentEntity getAgentById(String id) {
return agentDao.selectById(id);
}
@Override
public boolean insert(AgentEntity entity) {
// 如果ID为空,自动生成一个UUID作为ID
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
entity.setId(UUID.randomUUID().toString().replace("-", ""));
}
// 如果智能体编码为空,自动生成一个带前缀的编码
if (entity.getAgentCode() == null || entity.getAgentCode().trim().isEmpty()) {
entity.setAgentCode("AGT_" + System.currentTimeMillis());
}
// 如果排序字段为空,设置默认值0
if (entity.getSort() == null) {
entity.setSort(0);
}
return super.insert(entity);
}
}
package xiaozhi.modules.agent.service.impl;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.modules.agent.dao.AgentDao;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.service.AgentService;
@Service
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
private final AgentDao agentDao;
public AgentServiceImpl(AgentDao agentDao) {
this.agentDao = agentDao;
}
@Override
public List<AgentEntity> getUserAgents(Long userId) {
QueryWrapper<AgentEntity> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", userId);
return agentDao.selectList(wrapper);
}
@Override
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
IPage<AgentEntity> page = agentDao.selectPage(
getPage(params, "sort", true),
new QueryWrapper<>());
return new PageData<>(page.getRecords(), page.getTotal());
}
@Override
public AgentEntity getAgentById(String id) {
return agentDao.selectById(id);
}
@Override
public boolean insert(AgentEntity entity) {
// 如果ID为空,自动生成一个UUID作为ID
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
entity.setId(UUID.randomUUID().toString().replace("-", ""));
}
// 如果智能体编码为空,自动生成一个带前缀的编码
if (entity.getAgentCode() == null || entity.getAgentCode().trim().isEmpty()) {
entity.setAgentCode("AGT_" + System.currentTimeMillis());
}
// 如果排序字段为空,设置默认值0
if (entity.getSort() == null) {
entity.setSort(0);
}
return super.insert(entity);
}
}
@@ -0,0 +1,20 @@
package xiaozhi.modules.agent.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import xiaozhi.modules.agent.dao.AgentTemplateDao;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentTemplateService;
/**
* @author chenerlei
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service实现
* @createDate 2025-03-22 11:48:18
*/
@Service
public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateDao, AgentTemplateEntity>
implements AgentTemplateService {
}
@@ -0,0 +1,15 @@
package xiaozhi.modules.agent.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
@Data
@EqualsAndHashCode(callSuper = true)
public class AgentTemplateVO extends AgentTemplateEntity {
// 角色音色
private String ttsModelName;
// 角色模型
private String llmModelName;
}
@@ -1,82 +1,69 @@
package xiaozhi.modules.device.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.constant.Constant;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.page.PageData;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
import xiaozhi.modules.device.dto.DeviceBindDTO;
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.security.user.SecurityUser;
import java.util.List;
import java.util.Map;
@Tag(name = "设备管理")
@AllArgsConstructor
@RestController
@RequestMapping("/device")
public class DeviceController {
private final DeviceService deviceService;
private final RedisUtils redisUtils;
@PostMapping("/bind/{deviceCode}")
@PostMapping("/bind/{agentId}/{deviceCode}")
@Operation(summary = "绑定设备")
@RequiresPermissions("sys:role:normal")
public Result<DeviceEntity> bindDevice(@PathVariable String deviceCode) {
UserDetail user = SecurityUser.getUser();
String deviceHeaders = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode));
if (StringUtils.isBlank(deviceHeaders)) {
return new Result<DeviceEntity>().error(ErrorCode.DEVICE_CAPTCHA_ERROR);
public Result<Void> bindDevice(@PathVariable String agentId, @PathVariable String deviceCode) {
String macAddress = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode));
if (StringUtils.isBlank(macAddress)) {
return new Result<Void>().error(ErrorCode.DEVICE_CAPTCHA_ERROR);
}
DeviceHeaderDTO deviceHeader = JsonUtils.parseObject(deviceHeaders.getBytes(), DeviceHeaderDTO.class);
DeviceEntity device = deviceService.bindDevice(user.getId(), deviceHeader);
return new Result<DeviceEntity>().ok(device);
Long user = SecurityUser.getUser().getId();
DeviceBindDTO deviceBindDTO = new DeviceBindDTO(macAddress, user, agentId);
deviceService.bindDevice(deviceBindDTO);
return new Result<>();
}
@GetMapping("/bind")
@GetMapping("/bind/{agentId}")
@Operation(summary = "获取已绑定设备")
@RequiresPermissions("sys:role:normal")
public Result<List<DeviceEntity>> getUserDevices() {
public Result<List<DeviceEntity>> getUserDevices(@PathVariable String agentId) {
UserDetail user = SecurityUser.getUser();
List<DeviceEntity> devices = deviceService.getUserDevices(user.getId());
List<DeviceEntity> devices = deviceService.getUserDevices(user.getId(), agentId);
return new Result<List<DeviceEntity>>().ok(devices);
}
@PostMapping("/unbind")
@Operation(summary = "解绑设备")
@RequiresPermissions("sys:role:normal")
public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) {
public Result<Void> unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) {
UserDetail user = SecurityUser.getUser();
deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId());
return new Result();
return new Result<Void>();
}
@GetMapping("/all")
@Operation(summary = "设备列表(管理员)")
@RequiresPermissions("sys:role:superAdmin")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
})
public Result<PageData<DeviceEntity>> adminDeviceList(
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<DeviceEntity> page = deviceService.adminDeviceList(params);
return new Result<PageData<DeviceEntity>>().ok(page);
}
}
@@ -0,0 +1,76 @@
package xiaozhi.modules.device.controller;
import java.nio.charset.StandardCharsets;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.device.utils.NetworkUtil;
@Tag(name = "设备管理", description = "OTA 相关接口")
@RestController
@RequiredArgsConstructor
@RequestMapping("/ota")
public class OTAController {
private final DeviceService deviceService;
@Operation(summary = "检查 OTA 版本和设备激活状态")
@PostMapping
public ResponseEntity<String> checkOTAVersion(
@RequestBody DeviceReportReqDTO deviceReportReqDTO,
@Parameter(name = "Device-Id", description = "设备唯一标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Device-Id") String deviceId,
@Parameter(name = "Client-Id", description = "客户端标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Client-Id") String clientId) {
if (StringUtils.isAnyBlank(deviceId, clientId)) {
return createResponse(DeviceReportRespDTO.createError("Device ID is required"));
}
String macAddress = deviceReportReqDTO.getMacAddress();
boolean macAddressValid = NetworkUtil.isMacAddressValid(macAddress);
// 设备Id和Mac地址应是一致的, 并且必须需要application字段
if (!deviceId.equals(macAddress) || !macAddressValid || deviceReportReqDTO.getApplication() == null) {
return createResponse(DeviceReportRespDTO.createError("Invalid OTA request"));
}
return createResponse(deviceService.checkDeviceActive(macAddress, deviceId, clientId, deviceReportReqDTO));
}
@Operation(summary = "设备激活")
@GetMapping("/activation")
public Result<Boolean> deviceActivation(@RequestParam String code) {
return new Result<Boolean>().ok(deviceService.deviceActivation(code));
}
@SneakyThrows
private ResponseEntity<String> createResponse(DeviceReportRespDTO deviceReportRespDTO) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String json = objectMapper.writeValueAsString(deviceReportRespDTO);
byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.contentLength(jsonBytes.length)
.body(json);
}
}
@@ -1,7 +1,9 @@
package xiaozhi.modules.device.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import xiaozhi.modules.device.entity.DeviceEntity;
@Mapper
@@ -0,0 +1,27 @@
package xiaozhi.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 设备绑定的DTO
*
* @author zjy
* @since 2025-3-28
*/
@Data
@AllArgsConstructor
@Schema(description = "设备连接头信息")
public class DeviceBindDTO {
@Schema(description = "mac地址")
private String macAddress;
@Schema(description = "所属用户id")
private Long userId;
@Schema(description = "智能体id")
private String agentId;
}
@@ -1,19 +0,0 @@
package xiaozhi.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "设备连接头信息")
public class DeviceHeaderDTO {
@Schema(description = "设备ID")
private String deviceId;
@Schema(description = "协议版本号")
private Long protocolVersion;
@Schema(description = "认证信息")
private String authorization;
}
@@ -0,0 +1,148 @@
package xiaozhi.modules.device.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
@Setter
@Getter
@Schema(description = "设备固件信息上报求请求体")
public class DeviceReportReqDTO implements Serializable {
private static final long serialVersionUID = 1L;
// region 实体属性
@Schema(description = "板子固件版本号")
private Integer version;
@Schema(description = "闪存大小(单位:字节)")
@JsonProperty("flash_size")
private Integer flashSize;
@Schema(description = "最小空闲堆内存(字节)")
@JsonProperty("minimum_free_heap_size")
private Integer minimumFreeHeapSize;
@Schema(description = "设备 MAC 地址")
@JsonProperty("mac_address")
private String macAddress;
@Schema(description = "设备唯一标识 UUID")
private String uuid;
@Schema(description = "芯片型号名称")
@JsonProperty("chip_model_name")
private String chipModelName;
@Schema(description = "芯片详细信息")
@JsonProperty("chip_info")
private ChipInfo chipInfo;
@Schema(description = "应用程序信息")
private Application application;
@Schema(description = "分区表列表")
@JsonProperty("partition_table")
private List<Partition> partitionTable;
@Schema(description = "当前运行的 OTA 分区信息")
private OtaInfo ota;
@Schema(description = "板子配置信息")
private BoardInfo board;
// endregion
@Getter
@Setter
@Schema(description = "芯片信息")
public static class ChipInfo {
@Schema(description = "芯片模型代码")
private Integer model;
@Schema(description = "核心数")
private Integer cores;
@Schema(description = "硬件修订版本")
private Integer revision;
@Schema(description = "芯片功能标志位")
private Integer features;
}
@Getter
@Setter
@Schema(description = "板子编译信息")
public static class Application {
@Schema(description = "名称")
private String name;
@Schema(description = "应用版本号")
private String version;
@Schema(description = "编译时间(UTC ISO格式)")
@JsonProperty("compile_time")
private String compileTime;
@Schema(description = "ESP-IDF 版本号")
@JsonProperty("idf_version")
private String idfVersion;
@Schema(description = "ELF 文件 SHA256 校验")
@JsonProperty("elf_sha256")
private String elfSha256;
}
@Getter
@Setter
@Schema(description = "分区信息")
public static class Partition {
@Schema(description = "分区标签名")
private String label;
@Schema(description = "分区类型")
private Integer type;
@Schema(description = "子类型")
private Integer subtype;
@Schema(description = "起始地址")
private Integer address;
@Schema(description = "分区大小")
private Integer size;
}
@Getter
@Setter
@Schema(description = "OTA信息")
public static class OtaInfo {
@Schema(description = "当前OTA标签")
private String label;
}
@Getter
@Setter
@Schema(description = "板子连接和网络信息")
public static class BoardInfo {
@Schema(description = "板子类型")
private String type;
@Schema(description = "连接的 Wi-Fi SSID")
private String ssid;
@Schema(description = "Wi-Fi 信号强度(RSSI")
private Integer rssi;
@Schema(description = "Wi-Fi 信道")
private Integer channel;
@Schema(description = "IP 地址")
private String ip;
@Schema(description = "MAC 地址")
private String mac;
}
}
@@ -0,0 +1,61 @@
package xiaozhi.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Data
@Schema(description = "设备OTA检测版本返回体,包含激活码要求")
public class DeviceReportRespDTO {
@Schema(description = "服务器时间")
private ServerTime serverTime;
@Schema(description = "激活码")
private Activation activation;
@Schema(description = "错误信息")
private String error;
@Schema(description = "固件版本信息")
private Firmware firmware;
@Getter
@Setter
public static class Firmware {
@Schema(description = "版本号")
private String version;
@Schema(description = "下载地址")
private String url;
}
public static DeviceReportRespDTO createError(String message) {
DeviceReportRespDTO resp = new DeviceReportRespDTO();
resp.setError(message);
return resp;
}
@Setter
@Getter
public static class Activation {
@Schema(description = "激活码")
private String code;
@Schema(description = "激活码信息: 激活地址")
private String message;
}
@Getter
@Setter
public static class ServerTime {
@Schema(description = "时间戳")
private Long timestamp;
@Schema(description = "时区")
private String timeZone;
@Schema(description = "时区偏移量,单位为分钟")
private Integer timezoneOffset;
}
}
@@ -1,10 +1,11 @@
package xiaozhi.modules.device.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serializable;
/**
* 设备解绑表单
*/
@@ -14,6 +15,6 @@ public class DeviceUnBindDTO implements Serializable {
@Schema(description = "设备ID")
@NotBlank(message = "设备ID不能为空")
private Long deviceId;
private String deviceId;
}
@@ -1,17 +1,26 @@
package xiaozhi.modules.device.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("ai_device")
@Schema(description = "设备信息")
public class DeviceEntity {
@Schema(description = "设备ID")
private Long id;
@TableId(type = IdType.ASSIGN_UUID)
@Schema(description = "ID")
private String id;
@Schema(description = "关联用户ID")
private Long userId;
@@ -40,15 +49,19 @@ public class DeviceEntity {
@Schema(description = "排序")
private Integer sort;
@Schema(description = "创建者")
private Long creator;
@Schema(description = "创建时间")
private Date createDate;
@Schema(description = "更新者")
@TableField(fill = FieldFill.UPDATE)
private Long updater;
@Schema(description = "更新时间")
@TableField(fill = FieldFill.UPDATE)
private Date updateDate;
@Schema(description = "创建者")
@TableField(fill = FieldFill.INSERT)
private Long creator;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createDate;
}
@@ -1,18 +1,42 @@
package xiaozhi.modules.device.service;
import xiaozhi.common.page.PageData;
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
import java.util.List;
import xiaozhi.modules.device.dto.DeviceBindDTO;
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
import xiaozhi.modules.device.entity.DeviceEntity;
import java.util.List;
import java.util.Map;
public interface DeviceService {
DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader);
List<DeviceEntity> getUserDevices(Long userId);
void unbindDevice(Long userId, Long deviceId);
PageData<DeviceEntity> adminDeviceList(Map<String, Object> params);
/**
* 根据Mac地址获取设备信息
*/
DeviceEntity getDeviceById(String macAddress);
/**
* 检查设备是否激活
*/
DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId,
DeviceReportReqDTO deviceReport);
/**
* 绑定设备
*/
DeviceEntity bindDevice(DeviceBindDTO deviceHeader);
/**
* 获取用户设备列表
*/
List<DeviceEntity> getUserDevices(Long userId, String agentId);
/**
* 解绑设备
*/
void unbindDevice(Long userId, String deviceId);
/**
* 设备激活
*/
Boolean deviceActivation(String activationCode);
}
@@ -1,57 +1,208 @@
package xiaozhi.modules.device.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.stereotype.Service;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.modules.device.dao.DeviceDao;
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import cn.hutool.core.util.RandomUtil;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.modules.device.dao.DeviceDao;
import xiaozhi.modules.device.dto.DeviceBindDTO;
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.security.user.SecurityUser;
@Service
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
private final DeviceDao deviceDao;
private final String frontedUrl;
private final RedisTemplate<String, Object> redisTemplate;
// 添加构造函数来初始化 deviceMapper
public DeviceServiceImpl(DeviceDao deviceDao) {
public DeviceServiceImpl(DeviceDao deviceDao,
@Value("${app.fronted-url:http://localhost:8001}") String frontedUrl,
RedisTemplate<String, Object> redisTemplate) {
this.deviceDao = deviceDao;
this.frontedUrl = frontedUrl;
this.redisTemplate = redisTemplate;
}
@Override
public DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader) {
DeviceEntity device = new DeviceEntity();
device.setUserId(userId);
device.setMacAddress(deviceHeader.getDeviceId());
device.setCreateDate(new Date());
deviceDao.insert(device);
return device;
public DeviceEntity getDeviceById(String deviceId) {
LambdaQueryWrapper<DeviceEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeviceEntity::getId, deviceId);
return deviceDao.selectOne(queryWrapper);
}
@Override
public List<DeviceEntity> getUserDevices(Long userId) {
public Boolean deviceActivation(String activationCode) {
if (StringUtils.isBlank(activationCode)) {
throw new RenException("激活码不能为空");
}
String deviceKey = "ota:activation:code:" + activationCode;
Object cacheDeviceId = redisTemplate.opsForValue().get(deviceKey);
if (cacheDeviceId == null) {
throw new RenException("激活码错误");
}
String deviceId = (String) cacheDeviceId;
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
String cacheDeviceKey = String.format("ota:activation:data:%s", safeDeviceId);
Map<Object, Object> cacheMap = redisTemplate.opsForHash().entries(cacheDeviceKey);
if (cacheMap == null) {
throw new RenException("激活码错误");
}
String cachedCode = (String) cacheMap.get("activation_code");
if (!activationCode.equals(cachedCode)) {
throw new RenException("激活码错误");
}
// 检查设备有没有被激活
if (selectById(deviceId) != null) {
throw new RenException("设备已激活");
}
String macAddress = (String) cacheMap.get("mac_address");
String board = (String) cacheMap.get("board");
String appVersion = (String) cacheMap.get("app_version");
UserDetail user = SecurityUser.getUser();
if (user.getId() == null) {
throw new RenException("用户未登录");
}
Date currentTime = new Date();
DeviceEntity deviceEntity = new DeviceEntity();
deviceEntity.setId(deviceId);
deviceEntity.setBoard(board);
deviceEntity.setAppVersion(appVersion);
deviceEntity.setMacAddress(macAddress);
deviceEntity.setUserId(user.getId());
deviceEntity.setCreator(user.getId());
deviceEntity.setCreateDate(currentTime);
deviceEntity.setUpdater(user.getId());
deviceEntity.setUpdateDate(currentTime);
deviceEntity.setLastConnectedAt(currentTime);
deviceDao.insert(deviceEntity);
// 清理redis缓存
redisTemplate.delete(cacheDeviceKey);
redisTemplate.delete(deviceKey);
return true;
}
@Override
public DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId,
DeviceReportReqDTO deviceReport) {
DeviceReportRespDTO response = new DeviceReportRespDTO();
response.setServerTime(buildServerTime());
// todo: 此处是固件信息,目前是针对固件上传上来的版本号再返回回去
// 在未来开发了固件更新功能,需要更换此处代码,
// 或写定时任务定期请求虾哥的OTA,获取最新的版本讯息保存到服务内
DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware();
firmware.setVersion(deviceReport.getApplication().getVersion());
firmware.setUrl("http://localhost:8002/xiaozhi-esp32-api/api/v1/ota/download");
response.setFirmware(firmware);
DeviceEntity deviceById = getDeviceById(deviceId);
if (deviceById != null) { // 如果设备存在,则更新上次连接时间
deviceById.setLastConnectedAt(new Date());
deviceDao.updateById(deviceById);
} else { // 如果设备不存在,则生成激活码
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
String dataKey = String.format("ota:activation:data:%s", safeDeviceId);
Map<Object, Object> cacheMap = redisTemplate.opsForHash().entries(dataKey);
DeviceReportRespDTO.Activation code = new DeviceReportRespDTO.Activation();
if (cacheMap != null && cacheMap.containsKey("activation_code")) {
String cachedCode = (String) cacheMap.get("activation_code");
code.setCode(cachedCode);
code.setMessage(frontedUrl + "\n" + cachedCode);
} else {
String newCode = RandomUtil.randomNumbers(6);
code.setCode(newCode);
code.setMessage(frontedUrl + "\n" + newCode);
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", deviceId);
dataMap.put("mac_address", macAddress);
dataMap.put("board", (deviceReport.getChipModelName() != null) ? deviceReport.getChipModelName()
: (deviceReport.getBoard() != null ? deviceReport.getBoard().getType() : "unknown"));
dataMap.put("app_version", (deviceReport.getApplication() != null)
? deviceReport.getApplication().getVersion()
: null);
dataMap.put("deviceId", deviceId);
dataMap.put("activation_code", newCode);
// 写入主数据 key
redisTemplate.opsForHash().putAll(dataKey, dataMap);
redisTemplate.expire(dataKey, 24, TimeUnit.HOURS);
// 写入反查激活码 key
String codeKey = "ota:activation:code:" + newCode;
redisTemplate.opsForValue().set(codeKey, deviceId, 24, TimeUnit.HOURS);
}
response.setActivation(code);
}
return response;
}
@Override
public DeviceEntity bindDevice(DeviceBindDTO dto) {
// 查看是否已经被绑定
DeviceEntity deviceEntity = baseDao
.selectOne(new LambdaQueryWrapper<DeviceEntity>().eq(DeviceEntity::getMacAddress, dto.getMacAddress()));
if (deviceEntity != null) {
throw new RenException("设备已绑定");
}
deviceEntity = ConvertUtils.sourceToTarget(dto, DeviceEntity.class);
baseDao.insert(deviceEntity);
return deviceEntity;
}
@Override
public List<DeviceEntity> getUserDevices(Long userId, String agentId) {
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", userId);
return deviceDao.selectList(wrapper);
wrapper.eq("agent_id", agentId);
return baseDao.selectList(wrapper);
}
@Override
public void unbindDevice(Long userId, Long deviceId) {
deviceDao.deleteById(deviceId);
public void unbindDevice(Long userId, String deviceId) {
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
wrapper.eq("user_id", userId);
wrapper.eq("id", deviceId);
baseDao.delete(wrapper);
}
@Override
public PageData<DeviceEntity> adminDeviceList(Map<String, Object> params) {
IPage<DeviceEntity> page = deviceDao.selectPage(
getPage(params, "sort", true),
new QueryWrapper<>()
);
return new PageData<>(page.getRecords(), page.getTotal());
private DeviceReportRespDTO.ServerTime buildServerTime() {
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
TimeZone tz = TimeZone.getDefault();
serverTime.setTimestamp(Instant.now().toEpochMilli());
serverTime.setTimeZone(tz.getID());
serverTime.setTimezoneOffset(tz.getOffset(System.currentTimeMillis()) / (60 * 1000));
return serverTime;
}
}
@@ -0,0 +1,34 @@
package xiaozhi.modules.device.utils;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
/**
* 网络工具类
*/
public class NetworkUtil {
/**
* MAC地址正则表达式
*/
private static final Pattern macPattern = Pattern.compile("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
/**
* 判断MAC地址是否合法
*/
public static boolean isMacAddressValid(String mac) {
if (StringUtils.isBlank(mac)) {
return false;
}
// 正则校验格式
if (!macPattern.matcher(mac).matches()) {
return false;
}
// 校验MAC地址是否为单播地址
String normalized = mac.toLowerCase();
String[] parts = normalized.split("[:-]");
int firstByte = Integer.parseInt(parts[0], 16);
return (firstByte & 1) == 0; // 最低位为0表示单播地址,合法
}
}
@@ -0,0 +1,59 @@
package xiaozhi.modules.device.vo;
import lombok.Data;
@Data
public class DeviceOtaVO {
private Activation activation;
private Mqtt mqtt;
private ServerTime server_time;
private Firmware firmware;
private String error;
@Data
public static class Activation {
private String code;
private String message;
public Activation() {
}
public Activation(String code, String message) {
this.code = code;
this.message = message;
}
}
@Data
public class Mqtt {
}
@Data
public static class ServerTime {
private Long timestamp;
private Integer timezone_offset;
public ServerTime() {
}
public ServerTime(Long timestamp, Integer timezone_offset) {
this.timestamp = timestamp;
this.timezone_offset = timezone_offset;
}
}
@Data
public static class Firmware {
private String version;
private String url;
public Firmware() {
}
public Firmware(String version, String url) {
this.version = version;
this.url = url;
}
}
}
@@ -1,2 +0,0 @@
package xiaozhi.modules.model.controller;public class ModelConfigController {
}
@@ -1,20 +1,30 @@
package xiaozhi.modules.model.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
import xiaozhi.modules.model.dto.ModelConfigDTO;
import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.dto.ModelProviderFieldDTO;
import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.model.service.ModelProviderService;
import java.util.List;
@AllArgsConstructor
@RestController
@RequestMapping("/models")
@@ -29,7 +39,7 @@ public class ModelController {
@Operation(summary = "获取所有模型名称")
@RequiresPermissions("sys:role:superAdmin")
public Result<List<String>> getModelNames(@RequestParam String modelType,
@RequestParam(required = false) String modelName) {
@RequestParam(required = false) String modelName) {
List<String> modelNameList = modelConfigService.getModelCodeList(modelType, modelName);
return new Result<List<String>>().ok(modelNameList);
}
@@ -45,62 +55,61 @@ public class ModelController {
@GetMapping("/{modelType}/{provideCode}/fields")
@Operation(summary = "获取模型供应器字段")
@RequiresPermissions("sys:role:superAdmin")
public Result<List<String>> getModelProviderFields(@PathVariable String modelType, @PathVariable String provideCode) {
List<String> fieldList = modelProviderService.getFieldList(modelType, provideCode);
return new Result<List<String>>().ok(fieldList);
public Result<List<ModelProviderFieldDTO>> getModelProviderFields(@PathVariable String modelType,
@PathVariable String provideCode) {
List<ModelProviderFieldDTO> fieldList = modelProviderService.getFieldList(modelType, provideCode);
return new Result<List<ModelProviderFieldDTO>>().ok(fieldList);
}
@GetMapping("/models/list")
@Operation(summary = "获取模型配置列表")
@RequiresPermissions("sys:role:superAdmin")
public Result<PageData<ModelConfigDTO>> getModelConfigList(@RequestParam String modelType,
@RequestParam(required = false) String modelName,
@RequestParam(required = false, defaultValue = "0") Integer page,
@RequestParam(required = false,defaultValue = "10") Integer limit) {
public Result<PageData<ModelConfigDTO>> getModelConfigList(
@RequestParam(required = true) String modelType,
@RequestParam(required = false) String modelName,
@RequestParam(required = true, defaultValue = "0") String page,
@RequestParam(required = true, defaultValue = "10") String limit) {
PageData<ModelConfigDTO> pageList = modelConfigService.getPageList(modelType, modelName, page, limit);
return new Result<PageData<ModelConfigDTO>>().ok(pageList);
}
@PostMapping("/models/{modelType}/{provideCode}")
@Operation(summary = "新增模型配置")
@RequiresPermissions("sys:role:superAdmin")
public Result<ModelConfigDTO> addModelConfig(@PathVariable String modelType,
@PathVariable String provideCode,
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
@PathVariable String provideCode,
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
ModelConfigDTO modelConfigDTO = modelConfigService.add(modelType, provideCode, modelConfigBodyDTO);
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
}
@PutMapping("/models/{modelType}/{provideCode}/{id}")
@Operation(summary = "编辑模型配置")
@RequiresPermissions("sys:role:superAdmin")
public Result<ModelConfigDTO> editModelConfig(@PathVariable String modelType,
@PathVariable String provideCode,
@PathVariable String id,
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
@PathVariable String provideCode,
@PathVariable String id,
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
ModelConfigDTO modelConfigDTO = modelConfigService.edit(modelType, provideCode, id, modelConfigBodyDTO);
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
}
@DeleteMapping("/models/{modelType}/{provideCode}/{id}")
@Operation(summary = "删除模型配置")
@RequiresPermissions("sys:role:superAdmin")
public Result<Void> deleteModelConfig(@PathVariable String modelType, @PathVariable String provideCode, @PathVariable String id) {
public Result<Void> deleteModelConfig(@PathVariable String modelType, @PathVariable String provideCode,
@PathVariable String id) {
modelConfigService.delete(modelType, provideCode, id);
return new Result<>();
}
@GetMapping("/models/{modelName}/voices")
@GetMapping("/models/{modelId}/voices")
@Operation(summary = "获取模型音色")
@RequiresPermissions("sys:role:normal")
public Result<List<String>> getVoiceList(@PathVariable String modelName,
@RequestParam(required = false) String voiceName) {
public Result<List<String>> getVoiceList(@PathVariable String modelId,
@RequestParam(required = false) String voiceName) {
List<String> voiceList = modelConfigService.getVoiceList(modelName, voiceName);
List<String> voiceList = modelConfigService.getVoiceList(modelId, voiceName);
return new Result<List<String>>().ok(voiceList);
}
}
@@ -1,12 +1,13 @@
package xiaozhi.modules.model.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.model.entity.ModelConfigEntity;
import java.util.List;
@Mapper
public interface ModelConfigDao extends BaseDao<ModelConfigEntity> {
@@ -1,14 +1,15 @@
package xiaozhi.modules.model.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.model.entity.ModelProviderEntity;
import java.util.List;
@Mapper
public interface ModelProviderDao extends BaseDao<ModelProviderEntity> {
List<String> getFieldList(@Param("modelType") String modelType, @Param("provideCode") String provideCode);
List<String> getFieldList(@Param("modelType") String modelType, @Param("providerCode") String providerCode);
}
@@ -1,10 +1,10 @@
package xiaozhi.modules.model.dto;
import java.io.Serial;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
@Data
@Schema(description = "模型供应器/商")
public class ModelConfigBodyDTO {
@@ -12,9 +12,9 @@ public class ModelConfigBodyDTO {
@Serial
private static final long serialVersionUID = 1L;
// @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
// private String modelType;
//
// @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
// private String modelType;
//
@Schema(description = "模型编码(如AliLLM、DoubaoTTS)")
private String modelCode;
@@ -1,11 +1,11 @@
package xiaozhi.modules.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "模型供应器/商")
public class ModelConfigDTO implements Serializable {
@@ -14,7 +14,7 @@ public class ModelConfigDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键")
private Long id;
private String id;
@Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
private String modelType;
@@ -1,19 +1,21 @@
package xiaozhi.modules.model.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "模型供应器/商")
public class ModelProviderDTO implements Serializable {
//
// @Schema(description = "主键")
// private Long id;
//
// @Schema(description = "主键")
// private Long id;
@Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
private String modelType;
@@ -31,15 +33,19 @@ public class ModelProviderDTO implements Serializable {
@Schema(description = "排序")
private Integer sort;
@Schema(description = "创建者")
private Long creator;
@Schema(description = "创建时间")
private Date createDate;
@Schema(description = "更新者")
@TableField(fill = FieldFill.UPDATE)
private Long updater;
@Schema(description = "更新时间")
@TableField(fill = FieldFill.UPDATE)
private Date updateDate;
@Schema(description = "创建者")
@TableField(fill = FieldFill.INSERT)
private Long creator;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createDate;
}
@@ -0,0 +1,19 @@
package xiaozhi.modules.model.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "模型供应器字段")
public class ModelProviderFieldDTO implements Serializable {
@Schema(description = "字段名")
private String key;
@Schema(description = "字段标签")
private String label;
@Schema(description = "字段类型")
private String type;
}
@@ -1,16 +1,17 @@
package xiaozhi.modules.model.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
@Data
@TableName("ai_model_config")
@Schema(description = "模型配置表")
@@ -48,15 +49,19 @@ public class ModelConfigEntity {
@Schema(description = "排序")
private Integer sort;
@Schema(description = "创建者")
private Long creator;
@Schema(description = "创建时间")
private Date createDate;
@Schema(description = "更新者")
@TableField(fill = FieldFill.UPDATE)
private Long updater;
@Schema(description = "更新时间")
@TableField(fill = FieldFill.UPDATE)
private Date updateDate;
@Schema(description = "创建者")
@TableField(fill = FieldFill.INSERT)
private Long creator;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createDate;
}
@@ -1,15 +1,16 @@
package xiaozhi.modules.model.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
@Data
@TableName("ai_model_provider")
@Schema(description = "模型供应器表")
@@ -1,16 +1,16 @@
package xiaozhi.modules.model.service;
import java.util.List;
import xiaozhi.common.page.PageData;
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
import xiaozhi.modules.model.dto.ModelConfigDTO;
import java.util.List;
public interface ModelConfigService {
List<String> getModelCodeList(String modelType, String modelName);
PageData<ModelConfigDTO> getPageList(String modelType, String modelName, Integer page, Integer limit);
PageData<ModelConfigDTO> getPageList(String modelType, String modelName, String page, String limit);
ModelConfigDTO add(String modelType, String provideCode, ModelConfigBodyDTO modelConfigBodyDTO);
@@ -1,13 +1,14 @@
package xiaozhi.modules.model.service;
import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity;
import java.util.List;
import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.dto.ModelProviderFieldDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity;
public interface ModelProviderService {
// List<String> getModelNames(String modelType, String modelName);
// List<String> getModelNames(String modelType, String modelName);
List<ModelProviderDTO> getListByModelType(String modelType);
@@ -19,5 +20,5 @@ public interface ModelProviderService {
List<ModelProviderDTO> getList(String modelType, String provideCode);
List<String> getFieldList(String modelType, String provideCode);
List<ModelProviderFieldDTO> getFieldList(String modelType, String provideCode);
}
@@ -1,13 +1,19 @@
package xiaozhi.modules.model.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import cn.hutool.core.collection.CollectionUtil;
import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData;
@@ -22,13 +28,10 @@ import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.model.service.ModelProviderService;
import xiaozhi.modules.timbre.service.TimbreService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@AllArgsConstructor
public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, ModelConfigEntity> implements ModelConfigService {
public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, ModelConfigEntity>
implements ModelConfigService {
private final ModelConfigDao modelConfigDao;
private final ModelProviderService modelProviderService;
@@ -42,7 +45,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
}
@Override
public PageData<ModelConfigDTO> getPageList(String modelType, String modelName, Integer page, Integer limit) {
public PageData<ModelConfigDTO> getPageList(String modelType, String modelName, String page, String limit) {
Map<String, Object> params = new HashMap<String, Object>();
params.put(Constant.PAGE, page);
params.put(Constant.LIMIT, limit);
@@ -50,8 +53,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
getPage(params, "sort", true),
new QueryWrapper<ModelConfigEntity>()
.eq("model_type", modelType)
.like(StringUtils.isNotBlank(modelName), "model_name", modelName)
);
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%"));
return getPageData(modelConfigEntityIPage, ModelConfigDTO.class);
}
@@ -103,22 +105,11 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
throw new RenException("供应器不存在");
}
modelConfigDao.deleteById(Long.getLong(id));
modelConfigDao.deleteById(id);
}
@Override
public List<String> getVoiceList(String modelName, String voiceName) {
QueryWrapper<ModelConfigEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("model_name", StringUtils.isBlank(modelName) ? "" : modelName);
queryWrapper.eq("model_type", "TTS");
List<ModelConfigEntity> modelConfigEntities = modelConfigDao.selectList(queryWrapper);
if (CollectionUtil.isEmpty(modelConfigEntities)) {
logger.warn("没有找到模型配置信息");
return null;
}
ModelConfigEntity modelConfigEntity = modelConfigEntities.get(0);
String id = modelConfigEntity.getId();
return timbreService.getVoiceNames(id, voiceName);
public List<String> getVoiceList(String modelId, String voiceName) {
return timbreService.getVoiceNames(modelId, voiceName);
}
}
@@ -1,21 +1,26 @@
package xiaozhi.modules.model.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.modules.model.dao.ModelProviderDao;
import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity;
import xiaozhi.modules.model.service.ModelProviderService;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.AllArgsConstructor;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.model.dao.ModelProviderDao;
import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.dto.ModelProviderFieldDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity;
import xiaozhi.modules.model.service.ModelProviderService;
@Service
@AllArgsConstructor
public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao, ModelProviderEntity> implements ModelProviderService {
public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao, ModelProviderEntity>
implements ModelProviderService {
private final ModelProviderDao modelProviderDao;
@@ -44,16 +49,22 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
}
@Override
public List<ModelProviderDTO> getList(String modelType, String provideCode) {
public List<ModelProviderDTO> getList(String modelType, String providerCode) {
QueryWrapper<ModelProviderEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("model_type", StringUtils.isBlank(modelType) ? "" : modelType);
queryWrapper.eq("provide_code", StringUtils.isBlank(provideCode) ? "" : provideCode);
queryWrapper.eq("provider_code", StringUtils.isBlank(providerCode) ? "" : providerCode);
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
}
@Override
public List<String> getFieldList(String modelType, String provideCode) {
return modelProviderDao.getFieldList(modelType, provideCode);
public List<ModelProviderFieldDTO> getFieldList(String modelType, String providerCode) {
List<String> modelProviderEntities = modelProviderDao.getFieldList(modelType, providerCode);
if (modelProviderEntities == null || modelProviderEntities.isEmpty()) {
return null;
}
String fields = modelProviderEntities.getFirst();
List<ModelProviderFieldDTO> fieldList = JsonUtils.parseArray(fields, ModelProviderFieldDTO.class);
return fieldList;
}
}
@@ -5,7 +5,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
/**
* Filter配置
* Copyright (c) 人人开源 All rights reserved.
@@ -18,7 +17,7 @@ public class FilterConfig {
public FilterRegistrationBean<DelegatingFilterProxy> shiroFilterRegistration() {
FilterRegistrationBean<DelegatingFilterProxy> registration = new FilterRegistrationBean<>();
registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
//该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
// 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
registration.addInitParameter("targetFilterLifecycle", "true");
registration.setEnabled(true);
registration.setOrder(Integer.MAX_VALUE - 1);
@@ -1,8 +1,9 @@
package xiaozhi.modules.security.config;
import xiaozhi.modules.security.oauth2.Oauth2Filter;
import xiaozhi.modules.security.oauth2.Oauth2Realm;
import jakarta.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
@@ -14,9 +15,9 @@ import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import jakarta.servlet.Filter;
import xiaozhi.modules.security.oauth2.Oauth2Filter;
import xiaozhi.modules.security.oauth2.Oauth2Realm;
/**
* Shiro的配置文件
@@ -53,18 +54,21 @@ public class ShiroConfig {
shiroFilter.setSecurityManager(securityManager);
shiroFilter.setShiroFilterConfiguration(config);
//oauth过滤
// oauth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new Oauth2Filter());
shiroFilter.setFilters(filters);
//添加Shiro的内置过滤器
/*anon:无需认证就可以访问
authc:必须认证了才能让
user:必须拥有,记住我功能,才能访
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问*/
// 添加Shiro的内置过滤器
/*
* anon:无需认证就可以访
* authc:必须认证了才能
* user:必须拥有,记住我功能,才能访问
* perms:拥有某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/ota/**", "anon");
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/v3/api-docs/**", "anon");
@@ -1,9 +1,8 @@
package xiaozhi.modules.security.config;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.util.List;
import java.util.TimeZone;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
@@ -15,8 +14,10 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import java.util.TimeZone;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@@ -45,14 +46,14 @@ public class WebMvcConfig implements WebMvcConfigurer {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
//忽略未知属性
// 忽略未知属性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//日期格式转换
//mapper.setDateFormat(new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN));
// 日期格式转换
// mapper.setDateFormat(new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN));
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
//Long类型转String类型
// Long类型转String类型
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
@@ -1,18 +1,25 @@
package xiaozhi.modules.security.controller;
import java.io.IOException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.TokenDTO;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.Result;
import xiaozhi.common.validator.AssertUtils;
import xiaozhi.common.validator.ValidatorUtils;
import xiaozhi.modules.security.dto.LoginDTO;
import xiaozhi.modules.security.password.PasswordUtils;
import xiaozhi.modules.security.service.CaptchaService;
@@ -22,8 +29,6 @@ import xiaozhi.modules.sys.dto.PasswordDTO;
import xiaozhi.modules.sys.dto.SysUserDTO;
import xiaozhi.modules.sys.service.SysUserService;
import java.io.IOException;
/**
* 登录控制层
*/
@@ -36,14 +41,13 @@ public class LoginController {
private final SysUserTokenService sysUserTokenService;
private final CaptchaService captchaService;
@GetMapping("/captcha")
@Operation(summary = "验证码")
public void captcha(HttpServletResponse response, String uuid) throws IOException {
//uuid不能为空
// uuid不能为空
AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL);
//生成验证码
// 生成验证码
captchaService.create(response, uuid);
}
@@ -101,6 +105,8 @@ public class LoginController {
@PutMapping("/change-password")
@Operation(summary = "修改用户密码")
public Result<?> changePassword(@RequestBody PasswordDTO passwordDTO) {
// 判断非空
ValidatorUtils.validateEntity(passwordDTO);
Long userId = SecurityUser.getUserId();
sysUserTokenService.changePassword(userId, passwordDTO);
return new Result<>();
@@ -1,12 +1,13 @@
package xiaozhi.modules.security.dao;
import java.util.Date;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.security.entity.SysUserTokenEntity;
import java.util.Date;
/**
* 系统用户Token
* Copyright (c) 人人开源 All rights reserved.
@@ -1,11 +1,11 @@
package xiaozhi.modules.security.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serializable;
/**
* 登录表单
*/
@@ -1,13 +1,14 @@
package xiaozhi.modules.security.entity;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 系统用户Token
@@ -1,9 +1,7 @@
package xiaozhi.modules.security.oauth2;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
@@ -11,14 +9,17 @@ import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.utils.HttpContextUtils;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.common.utils.Result;
import java.io.IOException;
/**
* oauth2过滤器
* Copyright (c) 人人开源 All rights reserved.
@@ -30,7 +31,7 @@ public class Oauth2Filter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
// 获取请求token
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
@@ -52,14 +53,9 @@ public class Oauth2Filter extends AuthenticatingFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
// 获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
// TODO 调试接口,临时取消登录限制,需要 token 参数的除外
if (true) {
return true;
}
if (StringUtils.isBlank(token)) {
logger.warn("onAccessDenied:token is empty");
@@ -68,7 +64,7 @@ public class Oauth2Filter extends AuthenticatingFilter {
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
String json = JsonUtils.toJsonString(new Result().error(ErrorCode.UNAUTHORIZED));
String json = JsonUtils.toJsonString(new Result<Void>().error(ErrorCode.UNAUTHORIZED));
httpResponse.getWriter().print(json);
@@ -79,21 +75,21 @@ public class Oauth2Filter extends AuthenticatingFilter {
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,
ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
try {
//处理登录失败的异常
// 处理登录失败的异常
logger.error("onLoginFailure:登录失败!", e);
Throwable throwable = e.getCause() == null ? e : e.getCause();
Result r = new Result().error(ErrorCode.UNAUTHORIZED, throwable.getMessage());
Result<Void> r = new Result<Void>().error(ErrorCode.UNAUTHORIZED, throwable.getMessage());
String json = JsonUtils.toJsonString(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
logger.error("onLoginFailure:登录失败! msg:{}", e1.getMessage(), e1);
}
return false;
@@ -104,7 +100,7 @@ public class Oauth2Filter extends AuthenticatingFilter {
*/
private String getRequestToken(HttpServletRequest httpRequest) {
String token = null;
//从header中获取token
// 从header中获取token
String authorization = httpRequest.getHeader(Constant.AUTHORIZATION);
if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) {
token = authorization.replace("Bearer ", "");
@@ -1,7 +1,15 @@
package xiaozhi.modules.security.oauth2;
import jakarta.annotation.Resource;
import org.apache.shiro.authc.*;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
@@ -10,6 +18,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils;
@@ -19,9 +29,6 @@ import xiaozhi.modules.security.service.ShiroService;
import xiaozhi.modules.sys.entity.SysUserEntity;
import xiaozhi.modules.sys.enums.SuperAdminEnum;
import java.util.HashSet;
import java.util.Set;
/**
* 认证
* Copyright (c) 人人开源 All rights reserved.
@@ -47,7 +54,7 @@ public class Oauth2Realm extends AuthorizingRealm {
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
UserDetail user = (UserDetail) principals.getPrimaryPrincipal();
//用户权限列表
// 用户权限列表
Set<String> permsSet = new HashSet<>();
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
@@ -69,22 +76,22 @@ public class Oauth2Realm extends AuthorizingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
//根据accessToken,查询用户信息
// 根据accessToken,查询用户信息
SysUserTokenEntity tokenEntity = shiroService.getByToken(accessToken);
//token失效
// token失效
if (tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID));
}
//查询用户信息
// 查询用户信息
SysUserEntity userEntity = shiroService.getUser(tokenEntity.getUserId());
//转换成UserDetail对象
// 转换成UserDetail对象
UserDetail userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);
userDetail.setToken(accessToken);
//账号锁定
// 账号锁定
if (userDetail.getStatus() == null) {
logger.error("账号状态异常,status 不能为空");
throw new DisabledAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_DISABLE));
@@ -1,10 +1,10 @@
package xiaozhi.modules.security.oauth2;
import xiaozhi.common.exception.RenException;
import java.security.MessageDigest;
import java.util.UUID;
import xiaozhi.common.exception.RenException;
/**
* 生成token
* Copyright (c) 人人开源 All rights reserved.
@@ -5,22 +5,28 @@ import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
/**
* BCrypt implements OpenBSD-style Blowfish password hashing using the scheme described in
* BCrypt implements OpenBSD-style Blowfish password hashing using the scheme
* described in
* "A Future-Adaptable Password Scheme" by Niels Provos and David Mazieres.
* <p>
* This password hashing system tries to thwart off-line password cracking using a
* computationally-intensive hashing algorithm, based on Bruce Schneier's Blowfish cipher.
* The work factor of the algorithm is parameterised, so it can be increased as computers
* This password hashing system tries to thwart off-line password cracking using
* a
* computationally-intensive hashing algorithm, based on Bruce Schneier's
* Blowfish cipher.
* The work factor of the algorithm is parameterised, so it can be increased as
* computers
* get faster.
* <p>
* Usage is really simple. To hash a password for the first time, call the hashpw method
* Usage is really simple. To hash a password for the first time, call the
* hashpw method
* with a random salt, like this:
* <p>
* <code>
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br>
* </code>
* <p>
* To check whether a plaintext password matches one that has been hashed previously, use
* To check whether a plaintext password matches one that has been hashed
* previously, use
* the checkpw method:
* <p>
* <code>
@@ -30,7 +36,8 @@ import java.security.SecureRandom;
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It does not match");<br>
* </code>
* <p>
* The gensalt() method takes an optional parameter (log_rounds) that determines the
* The gensalt() method takes an optional parameter (log_rounds) that determines
* the
* computational complexity of the hashing:
* <p>
* <code>
@@ -38,7 +45,8 @@ import java.security.SecureRandom;
* String stronger_salt = BCrypt.gensalt(12)<br>
* </code>
* <p>
* The amount of work increases exponentially (2**log_rounds), so each increment is twice
* The amount of work increases exponentially (2**log_rounds), so each increment
* is twice
* as much work. The default log_rounds is 10, and the valid range is 4 to 31.
*
* @author Damien Miller
@@ -51,11 +59,11 @@ public class BCrypt {
// Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule
private static final int P_orig[] = {0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
private static final int P_orig[] = { 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377,
0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b};
private static final int S_orig[] = {0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0x9216d5d9, 0x8979fb1b };
private static final int S_orig[] = { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7,
0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5,
@@ -225,24 +233,24 @@ public class BCrypt {
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76,
0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0,
0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6};
0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 };
// bcrypt IV: "OrpheanBeholderScryDoubt"
static private final int bf_crypt_ciphertext[] = {0x4f727068, 0x65616e42,
0x65686f6c, 0x64657253, 0x63727944, 0x6f756274};
static private final int bf_crypt_ciphertext[] = { 0x4f727068, 0x65616e42,
0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 };
// Table for Base64 encoding
static private final char base64_code[] = {'.', '/', 'A', 'B', 'C', 'D', 'E', 'F',
static private final char base64_code[] = { '.', '/', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
// Table for Base64 decoding
static private final byte index_64[] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
static private final byte index_64[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1};
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1 };
static final int MIN_LOG_ROUNDS = 4;
static final int MAX_LOG_ROUNDS = 31;
// Expanded Blowfish key
@@ -250,7 +258,8 @@ public class BCrypt {
private int S[];
/**
* Encode a byte array using bcrypt's slightly-modified base64 encoding scheme. Note
* Encode a byte array using bcrypt's slightly-modified base64 encoding scheme.
* Note
* that this is <strong>not</strong> compatible with the standard MIME-base64
* encoding.
*
@@ -306,7 +315,8 @@ public class BCrypt {
}
/**
* Decode a string encoded using bcrypt's base64 scheme to a byte array. Note that
* Decode a string encoded using bcrypt's base64 scheme to a byte array. Note
* that
* this is *not* compatible with the standard MIME-base64 encoding.
*
* @param s the string to decode
@@ -365,7 +375,7 @@ public class BCrypt {
int i, n, l = lr[off], r = lr[off + 1];
l ^= P[0];
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2; ) {
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
// Feistel substitution on left word
n = S[(l >> 24) & 0xff];
n += S[0x100 | ((l >> 16) & 0xff)];
@@ -388,7 +398,8 @@ public class BCrypt {
* Cycically extract a word of key material
*
* @param data the string to extract the data from
* @param offp a "pointer" (as a one-entry array) to the current offset into data
* @param offp a "pointer" (as a one-entry array) to the current offset into
* data
* @return the next word of material from data
*/
private static int streamtoword(byte data[], int offp[]) {
@@ -420,8 +431,8 @@ public class BCrypt {
*/
private void key(byte key[]) {
int i;
int koffp[] = {0};
int lr[] = {0, 0};
int koffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++) {
@@ -443,15 +454,16 @@ public class BCrypt {
/**
* Perform the "enhanced key schedule" step described by Provos and Mazieres in
* "A Future-Adaptable Password Scheme" http://www.openbsd.org/papers/bcrypt-paper.ps
* "A Future-Adaptable Password Scheme"
* http://www.openbsd.org/papers/bcrypt-paper.ps
*
* @param data salt information
* @param key password information
*/
private void ekskey(byte data[], byte key[]) {
int i;
int koffp[] = {0}, doffp[] = {0};
int lr[] = {0, 0};
int koffp[] = { 0 }, doffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++) {
@@ -487,7 +499,8 @@ public class BCrypt {
*
* @param password the password to hash
* @param salt the binary salt to hash with the password
* @param log_rounds the binary logarithm of the number of rounds of hashing to apply
* @param log_rounds the binary logarithm of the number of rounds of hashing to
* apply
* @return an array containing the binary hashed password
*/
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds) {
@@ -524,7 +537,8 @@ public class BCrypt {
* Hash a password using the OpenBSD bcrypt scheme
*
* @param password the password to hash
* @param salt the salt to hash with (perhaps generated using BCrypt.gensalt)
* @param salt the salt to hash with (perhaps generated using
* BCrypt.gensalt)
* @return the hashed password
* @throws IllegalArgumentException if invalid salt is passed
*/
@@ -599,8 +613,10 @@ public class BCrypt {
/**
* Generate a salt for use with the BCrypt.hashpw() method
*
* @param log_rounds the log2 of the number of rounds of hashing to apply - the work
* factor therefore increases as 2**log_rounds. Minimum 4, maximum 31.
* @param log_rounds the log2 of the number of rounds of hashing to apply - the
* work
* factor therefore increases as 2**log_rounds. Minimum 4,
* maximum 31.
* @param random an instance of SecureRandom to use
* @return an encoded salt value
*/
@@ -626,8 +642,10 @@ public class BCrypt {
/**
* Generate a salt for use with the BCrypt.hashpw() method
*
* @param log_rounds the log2 of the number of rounds of hashing to apply - the work
* factor therefore increases as 2**log_rounds. Minimum 4, maximum 31.
* @param log_rounds the log2 of the number of rounds of hashing to apply - the
* work
* factor therefore increases as 2**log_rounds. Minimum 4,
* maximum 31.
* @return an encoded salt value
*/
public static String gensalt(int log_rounds) {
@@ -635,7 +653,8 @@ public class BCrypt {
}
/**
* Generate a salt for use with the BCrypt.hashpw() method, selecting a reasonable
* Generate a salt for use with the BCrypt.hashpw() method, selecting a
* reasonable
* default for the number of hashing rounds to apply
*
* @return an encoded salt value
@@ -1,15 +1,18 @@
package xiaozhi.modules.security.password;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.security.SecureRandom;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Implementation of PasswordEncoder that uses the BCrypt strong hashing function. Clients
* can optionally supply a "strength" (a.k.a. log rounds in BCrypt) and a SecureRandom
* instance. The larger the strength parameter the more work will have to be done
* Implementation of PasswordEncoder that uses the BCrypt strong hashing
* function. Clients
* can optionally supply a "strength" (a.k.a. log rounds in BCrypt) and a
* SecureRandom
* instance. The larger the strength parameter the more work will have to be
* done
* (exponentially) to hash the passwords. The default value is 10.
*
* @author Dave Syer
@@ -10,20 +10,23 @@ package xiaozhi.modules.security.password;
public interface PasswordEncoder {
/**
* Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
* Encode the raw password. Generally, a good encoding algorithm applies a SHA-1
* or
* greater hash combined with an 8-byte or greater randomly generated salt.
*/
String encode(CharSequence rawPassword);
/**
* Verify the encoded password obtained from storage matches the submitted raw
* password after it too is encoded. Returns true if the passwords match, false if
* password after it too is encoded. Returns true if the passwords match, false
* if
* they do not. The stored password itself is never decoded.
*
* @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with
* @return true if the raw password, after encoding, matches the encoded password from
* storage
* @return true if the raw password, after encoding, matches the encoded
* password from
* storage
*/
boolean matches(CharSequence rawPassword, String encodedPassword);
@@ -18,19 +18,17 @@ public class PasswordUtils {
return passwordEncoder.encode(str);
}
/**
* 比较密码是否相等
*
* @param str 明文密码
* @param password 加密后密码
* @return true:成功 false:失败
* @return true:成功 false:失败
*/
public static boolean matches(String str, String password) {
return passwordEncoder.matches(str, password);
}
public static void main(String[] args) {
String str = "admin";
String password = encode(str);
@@ -1,9 +1,9 @@
package xiaozhi.modules.security.service;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import jakarta.servlet.http.HttpServletResponse;
/**
* 验证码
* Copyright (c) 人人开源 All rights reserved.
@@ -21,7 +21,7 @@ public interface CaptchaService {
*
* @param uuid uuid
* @param code 验证码
* @return true:成功 false:失败
* @return true:成功 false:失败
*/
boolean validate(String uuid, String code);
}
@@ -1,23 +1,22 @@
package xiaozhi.modules.security.service.impl;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.modules.security.oauth2.Oauth2Realm;
import xiaozhi.modules.security.service.CaptchaService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.modules.security.service.CaptchaService;
/**
* 验证码
@@ -29,10 +28,10 @@ public class CaptchaServiceImpl implements CaptchaService {
@Value("${renren.redis.open}")
private boolean open;
/**
* Local Cache 5分钟过期
* Local Cache 5分钟过期
*/
Cache<String, String> localCache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(5, TimeUnit.MINUTES).build();
private static final Logger logger = LoggerFactory.getLogger(Oauth2Realm.class);
Cache<String, String> localCache = CacheBuilder.newBuilder().maximumSize(1000)
.expireAfterAccess(5, TimeUnit.MINUTES).build();
@Override
public void create(HttpServletResponse response, String uuid) throws IOException {
@@ -41,13 +40,13 @@ public class CaptchaServiceImpl implements CaptchaService {
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
//生成验证码
// 生成验证码
SpecCaptcha captcha = new SpecCaptcha(150, 40);
captcha.setLen(5);
captcha.setCharType(Captcha.TYPE_DEFAULT);
captcha.out(response.getOutputStream());
//保存到缓存
// 保存到缓存
setCache(uuid, captcha.text());
}
@@ -56,10 +55,10 @@ public class CaptchaServiceImpl implements CaptchaService {
if (StringUtils.isBlank(code)) {
return false;
}
//获取验证码
// 获取验证码
String captcha = getCache(uuid);
//效验成功
// 效验成功
if (code.equalsIgnoreCase(captcha)) {
return true;
}
@@ -80,7 +79,7 @@ public class CaptchaServiceImpl implements CaptchaService {
if (open) {
key = RedisKeys.getCaptchaKey(key);
String captcha = (String) redisUtils.get(key);
//删除验证码
// 删除验证码
if (captcha != null) {
redisUtils.delete(key);
}
@@ -89,7 +88,7 @@ public class CaptchaServiceImpl implements CaptchaService {
}
String captcha = localCache.getIfPresent(key);
//删除验证码
// 删除验证码
if (captcha != null) {
localCache.invalidate(key);
}
@@ -1,17 +1,13 @@
package xiaozhi.modules.security.service.impl;
import xiaozhi.common.user.UserDetail;
import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor;
import xiaozhi.modules.security.dao.SysUserTokenDao;
import xiaozhi.modules.security.entity.SysUserTokenEntity;
import xiaozhi.modules.security.service.ShiroService;
import xiaozhi.modules.sys.dao.SysUserDao;
import xiaozhi.modules.sys.entity.SysUserEntity;
import xiaozhi.modules.sys.enums.SuperAdminEnum;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.*;
@AllArgsConstructor
@Service
@@ -19,7 +15,6 @@ public class ShiroServiceImpl implements ShiroService {
private final SysUserDao sysUserDao;
private final SysUserTokenDao sysUserTokenDao;
@Override
public SysUserTokenEntity getByToken(String token) {
return sysUserTokenDao.getByToken(token);
@@ -1,8 +1,11 @@
package xiaozhi.modules.security.service.impl;
import java.util.Date;
import org.springframework.stereotype.Service;
import cn.hutool.core.date.DateUtil;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.TokenDTO;
@@ -17,11 +20,10 @@ import xiaozhi.modules.sys.dto.PasswordDTO;
import xiaozhi.modules.sys.dto.SysUserDTO;
import xiaozhi.modules.sys.service.SysUserService;
import java.util.Date;
@AllArgsConstructor
@Service
public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, SysUserTokenEntity> implements SysUserTokenService {
public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, SysUserTokenEntity>
implements SysUserTokenService {
private final SysUserService sysUserService;
/**
@@ -31,18 +33,18 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, Sy
@Override
public Result<TokenDTO> createToken(Long userId) {
//用户token
// 用户token
String token;
//当前时间
// 当前时间
Date now = new Date();
//过期时间
// 过期时间
Date expireTime = new Date(now.getTime() + EXPIRE * 1000);
//判断是否生成过token
// 判断是否生成过token
SysUserTokenEntity tokenEntity = baseDao.getByUserId(userId);
if (tokenEntity == null) {
//生成一个token
// 生成一个token
token = TokenGenerator.generateValue();
tokenEntity = new SysUserTokenEntity();
@@ -51,12 +53,12 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, Sy
tokenEntity.setUpdateDate(now);
tokenEntity.setExpireDate(expireTime);
//保存token
// 保存token
this.insert(tokenEntity);
} else {
//判断token是否过期
// 判断token是否过期
if (tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
//token过期,重新生成token
// token过期,重新生成token
token = TokenGenerator.generateValue();
} else {
token = tokenEntity.getToken();
@@ -66,7 +68,7 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, Sy
tokenEntity.setUpdateDate(now);
tokenEntity.setExpireDate(expireTime);
//更新token
// 更新token
this.updateById(tokenEntity);
}
@@ -76,7 +78,7 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, Sy
tokenDTO.setToken(token);
tokenDTO.setExpire(EXPIRE);
tokenDTO.setClientHash(clientHash);
return new Result().ok(tokenDTO);
return new Result<TokenDTO>().ok(tokenDTO);
}
@Override
@@ -1,9 +1,10 @@
package xiaozhi.modules.security.user;
import xiaozhi.common.user.UserDetail;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import xiaozhi.common.user.UserDetail;
/**
* Shiro工具类
* Copyright (c) 人人开源 All rights reserved.
@@ -1,13 +1,21 @@
package xiaozhi.modules.admin.contrloler;
package xiaozhi.modules.sys.controller;
import java.util.Map;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.Result;
@@ -16,8 +24,6 @@ import xiaozhi.modules.sys.dto.AdminPageUserDTO;
import xiaozhi.modules.sys.service.SysUserService;
import xiaozhi.modules.sys.vo.AdminPageUserVO;
import java.util.Map;
/**
* 管理员控制层
*
@@ -35,7 +41,7 @@ public class AdminController {
@Operation(summary = "分页查找用户")
@RequiresPermissions("sys:role:superAdmin")
@Parameters({
@Parameter(name = "mobile", description = "用户手机号码", required = true),
@Parameter(name = "mobile", description = "用户手机号码", required = false),
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
})
@@ -64,7 +70,7 @@ public class AdminController {
@Operation(summary = "用户删除")
@RequiresPermissions("sys:role:superAdmin")
public Result<Void> delete(@PathVariable Long id) {
sysUserService.delete(new Long[]{id});
sysUserService.delete(new Long[] { id });
return new Result<>();
}
@@ -72,13 +78,13 @@ public class AdminController {
@Operation(summary = "分页查找设备")
@RequiresPermissions("sys:role:superAdmin")
@Parameters({
@Parameter(name = "keywords", description = "用户手机号码", required = true),
@Parameter(name = "keywords", description = "设备关键词", required = false),
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
})
public Result<Void> pageDevice(
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
//TODO 等设备功能模块写好
return new Result<Void>().error(600,"等设备功能模块写好");
// TODO 等设备功能模块写好
return new Result<Void>().error(600, "等设备功能模块写好");
}
}
@@ -1,17 +0,0 @@
package xiaozhi.modules.sys.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户管理
*/
@AllArgsConstructor
@RestController
@RequestMapping("/sys/user")
@Tag(name = "用户管理")
public class SysUserController {
}

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