diff --git a/.gitignore b/.gitignore index 675fb688..e4c2ac00 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/main/manager-api/README.md b/main/manager-api/README.md index 0435956e..5eb87ae8 100644 --- a/main/manager-api/README.md +++ b/main/manager-api/README.md @@ -11,6 +11,7 @@ manager-api 该项目基于SpringBoot框架开发。 JDK 21 Maven 3.8+ MySQL 8.0+ +Redis 5.0+ Vue 3.x # 创建数据库 @@ -43,6 +44,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项目,启动方式为: diff --git a/main/manager-api/pom.xml b/main/manager-api/pom.xml index eacbdff9..7e0bf307 100644 --- a/main/manager-api/pom.xml +++ b/main/manager-api/pom.xml @@ -27,7 +27,6 @@ 2.0.2 1.6.2 33.0.0-jre - 3.3.2 4.20.0 diff --git a/main/manager-api/src/main/java/xiaozhi/common/annotation/DataFilter.java b/main/manager-api/src/main/java/xiaozhi/common/annotation/DataFilter.java index b25c6fb4..9bd6cb9d 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/annotation/DataFilter.java +++ b/main/manager-api/src/main/java/xiaozhi/common/annotation/DataFilter.java @@ -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; /** * 数据过滤注解 diff --git a/main/manager-api/src/main/java/xiaozhi/common/annotation/LogOperation.java b/main/manager-api/src/main/java/xiaozhi/common/annotation/LogOperation.java index b2514ef5..6f6f778d 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/annotation/LogOperation.java +++ b/main/manager-api/src/main/java/xiaozhi/common/annotation/LogOperation.java @@ -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; /** * 操作日志注解 diff --git a/main/manager-api/src/main/java/xiaozhi/common/aspect/RedisAspect.java b/main/manager-api/src/main/java/xiaozhi/common/aspect/RedisAspect.java index 69248613..d4233e72 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/aspect/RedisAspect.java +++ b/main/manager-api/src/main/java/xiaozhi/common/aspect/RedisAspect.java @@ -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; diff --git a/main/manager-api/src/main/java/xiaozhi/common/config/MybatisPlusConfig.java b/main/manager-api/src/main/java/xiaozhi/common/config/MybatisPlusConfig.java index 2890a61c..250e0d07 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/config/MybatisPlusConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/common/config/MybatisPlusConfig.java @@ -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配置 diff --git a/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java b/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java index c9e38ab1..36e33aa1 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java @@ -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. @@ -16,7 +17,7 @@ public class SwaggerConfig { @Bean public GroupedOpenApi userApi() { - String[] paths = {"/**"}; + String[] paths = { "/**" }; return GroupedOpenApi.builder().group("xiaozhi") .pathsToMatch(paths).build(); } diff --git a/main/manager-api/src/main/java/xiaozhi/common/convert/DateConverter.java b/main/manager-api/src/main/java/xiaozhi/common/convert/DateConverter.java index a8439180..fd0f74c2 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/convert/DateConverter.java +++ b/main/manager-api/src/main/java/xiaozhi/common/convert/DateConverter.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/common/entity/BaseEntity.java b/main/manager-api/src/main/java/xiaozhi/common/entity/BaseEntity.java index e7e2f345..ce267bab 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/entity/BaseEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/common/entity/BaseEntity.java @@ -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; /** * 基础实体类,所有实体都需要继承 diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/RenException.java b/main/manager-api/src/main/java/xiaozhi/common/exception/RenException.java index cf295dcf..9b928d5c 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/RenException.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/RenException.java @@ -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; diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java index 06665ee2..0f83772e 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java @@ -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 handleRenException(RenException ex) { + Result 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 handleDuplicateKeyException(DuplicateKeyException ex) { + Result 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 handleUnauthorizedException(UnauthorizedException ex) { + Result result = new Result<>(); result.error(ErrorCode.FORBIDDEN); return result; } - @ExceptionHandler(Exception.class) - public Result handleException(Exception ex) { + public Result handleException(Exception ex) { log.error(ex.getMessage(), ex); - return new Result().error(); + return new Result().error(); } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/handler/FieldMetaObjectHandler.java b/main/manager-api/src/main/java/xiaozhi/common/handler/FieldMetaObjectHandler.java index 20371fd6..a6b33b62 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/handler/FieldMetaObjectHandler.java +++ b/main/manager-api/src/main/java/xiaozhi/common/handler/FieldMetaObjectHandler.java @@ -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()); } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/interceptor/DataFilterInterceptor.java b/main/manager-api/src/main/java/xiaozhi/common/interceptor/DataFilterInterceptor.java index 160e7ff3..0457686b 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/interceptor/DataFilterInterceptor.java +++ b/main/manager-api/src/main/java/xiaozhi/common/interceptor/DataFilterInterceptor.java @@ -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(); } diff --git a/main/manager-api/src/main/java/xiaozhi/common/page/PageData.java b/main/manager-api/src/main/java/xiaozhi/common/page/PageData.java index b7e943d2..83e93c11 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/page/PageData.java +++ b/main/manager-api/src/main/java/xiaozhi/common/page/PageData.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/common/page/TokenDTO.java b/main/manager-api/src/main/java/xiaozhi/common/page/TokenDTO.java index 48d26f3a..cce1a6df 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/page/TokenDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/common/page/TokenDTO.java @@ -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; diff --git a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisConfig.java b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisConfig.java index a4316b63..703a8492 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisConfig.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java index 4626b14d..240e1339 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/common/service/BaseService.java b/main/manager-api/src/main/java/xiaozhi/common/service/BaseService.java index 01bd48ca..2feaefd1 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/service/BaseService.java +++ b/main/manager-api/src/main/java/xiaozhi/common/service/BaseService.java @@ -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 { *

* * @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 updateWrapper); diff --git a/main/manager-api/src/main/java/xiaozhi/common/service/CrudService.java b/main/manager-api/src/main/java/xiaozhi/common/service/CrudService.java index bc1094e4..51f16bcb 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/service/CrudService.java +++ b/main/manager-api/src/main/java/xiaozhi/common/service/CrudService.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java index 16fd6feb..8ebb4cd1 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java @@ -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, T> implements Bas * @param isAsc 排序方式 */ protected IPage getPage(Map params, String defaultOrderField, boolean isAsc) { - //分页参数 + // 分页参数 long curPage = 1; long limit = 10; @@ -56,17 +58,17 @@ public abstract class BaseServiceImpl, T> implements Bas limit = Long.parseLong((String) params.get(Constant.LIMIT)); } - //分页对象 + // 分页对象 Page 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, 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, T> implements Bas return page; } - protected PageData getPageData(List list, long total, Class target) { - List targetList = ConvertUtils.sourceToTarget(list, target); + protected PageData getPageData(List list, long total, Class target) { + List targetList = ConvertUtils.sourceToTarget(list, target); return new PageData<>(targetList, total); } - protected PageData getPageData(IPage page, Class target) { + protected PageData getPageData(IPage page, Class target) { return getPageData(page.getRecords(), page.getTotal(), target); } @@ -126,10 +128,12 @@ public abstract class BaseServiceImpl, T> implements Bas return SqlHelper.retBool(result); } + @SuppressWarnings("unchecked") protected Class currentMapperClass() { return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0); } + @SuppressWarnings("unchecked") @Override public Class currentModelClass() { return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1); @@ -164,11 +168,11 @@ public abstract class BaseServiceImpl, T> implements Bas /** * 执行批量操作 */ + @SuppressWarnings("deprecation") protected boolean executeBatch(Collection list, int batchSize, BiConsumer consumer) { return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer); } - @Override @Transactional(rollbackFor = Exception.class) public boolean updateById(T entity) { diff --git a/main/manager-api/src/main/java/xiaozhi/common/service/impl/CrudServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/common/service/impl/CrudServiceImpl.java index 6608e7c3..524a8cb2 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/service/impl/CrudServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/common/service/impl/CrudServiceImpl.java @@ -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, T, D> extends BaseServiceImpl implements CrudService { +public abstract class CrudServiceImpl, T, D> extends BaseServiceImpl + implements CrudService { + @SuppressWarnings("unchecked") protected Class currentDtoClass() { return (Class) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2); } @@ -29,8 +33,7 @@ public abstract class CrudServiceImpl, T, D> extends Bas public PageData page(Map params) { IPage page = baseDao.selectPage( getPage(params, null, false), - getWrapper(params) - ); + getWrapper(params)); return getPageData(page, currentDtoClass()); } @@ -56,7 +59,7 @@ public abstract class CrudServiceImpl, T, D> extends Bas T entity = ConvertUtils.sourceToTarget(dto, currentModelClass()); insert(entity); - //copy主键值到dto + // copy主键值到dto BeanUtils.copyProperties(entity, dto); } diff --git a/main/manager-api/src/main/java/xiaozhi/common/user/UserDetail.java b/main/manager-api/src/main/java/xiaozhi/common/user/UserDetail.java index 2ad023d7..0aa9210b 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/user/UserDetail.java +++ b/main/manager-api/src/main/java/xiaozhi/common/user/UserDetail.java @@ -1,9 +1,9 @@ package xiaozhi.common.user; -import lombok.Data; - import java.io.Serializable; +import lombok.Data; + /** * 登录用户信息 * Copyright (c) 人人开源 All rights reserved. diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/ConvertUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/ConvertUtils.java index 7a5bc0ed..07ade50f 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/ConvertUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/ConvertUtils.java @@ -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 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); } diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/HttpContextUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/HttpContextUtils.java index 8d76434c..0e46acf1 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/HttpContextUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/HttpContextUtils.java @@ -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; diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/IpUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/IpUtils.java index 307ad3b5..fef7972f 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/IpUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/IpUtils.java @@ -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地址 diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/JsonUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/JsonUtils.java index 4a908245..288dae9a 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/JsonUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/JsonUtils.java @@ -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); } diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/Result.java b/main/manager-api/src/main/java/xiaozhi/common/utils/Result.java index 65841b7d..9c326af7 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/Result.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/Result.java @@ -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 implements Serializable { return this; } - public Result error() { this.code = ErrorCode.INTERNAL_SERVER_ERROR; this.msg = MessageUtils.getMessage(this.code); diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/TreeNode.java b/main/manager-api/src/main/java/xiaozhi/common/utils/TreeNode.java index b60bc9cf..5fdb5985 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/TreeNode.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/TreeNode.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/TreeUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/TreeUtils.java index 766cb029..e76190eb 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/TreeUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/TreeUtils.java @@ -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 List build(List treeNodes, Long pid) { - //pid不能为空 + public static > List build(List treeNodes, Long pid) { + // pid不能为空 AssertUtils.isNull(pid, "pid"); List treeList = new ArrayList<>(); @@ -34,7 +34,7 @@ public class TreeUtils { /** * 查找子节点 */ - private static T findChildren(List treeNodes, T rootNode) { + private static > T findChildren(List 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 List build(List treeNodes) { + public static > List build(List treeNodes) { List result = new ArrayList<>(); - //list转map + // list转map Map nodeMap = new LinkedHashMap<>(treeNodes.size()); for (T treeNode : treeNodes) { nodeMap.put(treeNode.getId(), treeNode); diff --git a/main/manager-api/src/main/java/xiaozhi/common/validator/AssertUtils.java b/main/manager-api/src/main/java/xiaozhi/common/validator/AssertUtils.java index 6a6b43e1..c86aa517 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/validator/AssertUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/validator/AssertUtils.java @@ -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"); } diff --git a/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java b/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java index 80acb097..9e31c964 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java @@ -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> constraintViolations = validator.validate(object, groups); diff --git a/main/manager-api/src/main/java/xiaozhi/common/xss/SqlFilter.java b/main/manager-api/src/main/java/xiaozhi/common/xss/SqlFilter.java index 20291da1..2bf4ab92 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/xss/SqlFilter.java +++ b/main/manager-api/src/main/java/xiaozhi/common/xss/SqlFilter.java @@ -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); diff --git a/main/manager-api/src/main/java/xiaozhi/common/xss/XssConfig.java b/main/manager-api/src/main/java/xiaozhi/common/xss/XssConfig.java index f62786fd..e4b65465 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/xss/XssConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/common/xss/XssConfig.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/common/xss/XssFilter.java b/main/manager-api/src/main/java/xiaozhi/common/xss/XssFilter.java index 9a24d7eb..db769989 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/xss/XssFilter.java +++ b/main/manager-api/src/main/java/xiaozhi/common/xss/XssFilter.java @@ -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 diff --git a/main/manager-api/src/main/java/xiaozhi/common/xss/XssHttpServletRequestWrapper.java b/main/manager-api/src/main/java/xiaozhi/common/xss/XssHttpServletRequestWrapper.java index 1306c2dd..ecb36406 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/xss/XssHttpServletRequestWrapper.java +++ b/main/manager-api/src/main/java/xiaozhi/common/xss/XssHttpServletRequestWrapper.java @@ -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() { diff --git a/main/manager-api/src/main/java/xiaozhi/common/xss/XssProperties.java b/main/manager-api/src/main/java/xiaozhi/common/xss/XssProperties.java index bf36ddbe..5d9b8b01 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/xss/XssProperties.java +++ b/main/manager-api/src/main/java/xiaozhi/common/xss/XssProperties.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/common/xss/XssUtils.java b/main/manager-api/src/main/java/xiaozhi/common/xss/XssUtils.java index 5d4ac6b3..16588392 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/xss/XssUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/xss/XssUtils.java @@ -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") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/admin/contrloler/AdminController.java b/main/manager-api/src/main/java/xiaozhi/modules/admin/contrloler/AdminController.java index ee46f2eb..b447661f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/admin/contrloler/AdminController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/admin/contrloler/AdminController.java @@ -1,13 +1,21 @@ package xiaozhi.modules.admin.contrloler; +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; - /** * 管理员控制层 * @@ -64,7 +70,7 @@ public class AdminController { @Operation(summary = "用户删除") @RequiresPermissions("sys:role:superAdmin") public Result delete(@PathVariable Long id) { - sysUserService.delete(new Long[]{id}); + sysUserService.delete(new Long[] { id }); return new Result<>(); } @@ -78,7 +84,7 @@ public class AdminController { }) public Result pageDevice( @Parameter(hidden = true) @RequestParam Map params) { - //TODO 等设备功能模块写好 - return new Result().error(600,"等设备功能模块写好"); + // TODO 等设备功能模块写好 + return new Result().error(600, "等设备功能模块写好"); } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java index 9f2cb10d..bb0bb52d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java @@ -1,13 +1,26 @@ 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 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; @@ -19,17 +32,13 @@ import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.service.AgentService; 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; - + @GetMapping("/list") @Operation(summary = "获取用户智能体列表") @RequiresPermissions("sys:role:normal") @@ -38,7 +47,7 @@ public class AgentController { List agents = agentService.getUserAgents(user.getId()); return new Result>().ok(agents); } - + @GetMapping("/all") @Operation(summary = "智能体列表(管理员)") @RequiresPermissions("sys:role:superAdmin") @@ -51,7 +60,7 @@ public class AgentController { PageData page = agentService.adminAgentList(params); return new Result>().ok(page); } - + @GetMapping("/{id}") @Operation(summary = "获取智能体详情") @RequiresPermissions("sys:role:normal") @@ -59,25 +68,25 @@ public class AgentController { AgentEntity agent = agentService.getAgentById(id); return new Result().ok(agent); } - + @PostMapping @Operation(summary = "创建智能体") @RequiresPermissions("sys:role:normal") public Result 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 @Operation(summary = "更新智能体") @RequiresPermissions("sys:role:normal") @@ -87,7 +96,7 @@ public class AgentController { if (existingEntity == null) { return new Result().error("智能体不存在"); } - + // 只更新提供的非空字段 if (dto.getAgentName() != null) { existingEntity.setAgentName(dto.getAgentName()); @@ -128,17 +137,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 +155,4 @@ public class AgentController { agentService.deleteById(id); return new Result<>(); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentTemplateController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentTemplateController.java new file mode 100644 index 00000000..cd88a4c8 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentTemplateController.java @@ -0,0 +1,37 @@ +package xiaozhi.modules.agent.controller; + +import java.util.List; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +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.tags.Tag; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import xiaozhi.common.utils.Result; +import xiaozhi.modules.agent.entity.AgentTemplateEntity; +import xiaozhi.modules.agent.service.AgentTemplateService; + +@Slf4j +@Tag(name = "智能体模板管理") +@AllArgsConstructor +@RestController +@RequestMapping("/user/agent/template") +public class AgentTemplateController { + private final AgentTemplateService agentTemplateService; + + @GetMapping + @Operation(summary = "智能体模板模板列表") + @RequiresPermissions("sys:role:normal") + public Result> templateList() { + List list = agentTemplateService + .list(new QueryWrapper().orderByAsc("sort")); + return new Result>().ok(list); + } + +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java index 169346bf..d9aec7d7 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java @@ -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 { - -} \ No newline at end of file + +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentTemplateDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentTemplateDao.java new file mode 100644 index 00000000..48d95bef --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentTemplateDao.java @@ -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 { + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentCreateDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentCreateDTO.java index 302a55fb..13b56b73 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentCreateDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentCreateDTO.java @@ -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,38 @@ 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; -} \ No newline at end of file +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java index 6445bdda..c0da024d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java @@ -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字段是必需的,用于标识要更新的智能体 @@ -19,43 +19,43 @@ public class AgentUpdateDTO implements Serializable { @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; -} \ No newline at end of file +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java index 6800aefb..17daf2f7 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java @@ -1,69 +1,70 @@ package xiaozhi.modules.agent.entity; +import java.util.Date; + 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 { @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; -} \ No newline at end of file +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java new file mode 100644 index 00000000..8a661209 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java @@ -0,0 +1,118 @@ +package xiaozhi.modules.agent.entity; + +import java.io.Serializable; +import java.util.Date; + +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 + 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 memoryModelId; + + /** + * 意图模型标识 + */ + private String intentModelId; + + /** + * 角色设定参数 + */ + private String systemPrompt; + + /** + * 语言编码 + */ + private String langCode; + + /** + * 交互语种 + */ + private String language; + + /** + * 排序权重 + */ + private Integer sort; + + /** + * 是否默认模板:1:是,0:不是 + */ + private Integer isDefault; + + /** + * 创建者 ID + */ + private Long creator; + + /** + * 创建时间 + */ + private Date createdAt; + + /** + * 更新者 ID + */ + private Long updater; + + /** + * 更新时间 + */ + private Date updatedAt; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java index 76439ede..da958e4c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java @@ -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 { /** * 根据用户ID获取智能体列表 */ List getUserAgents(Long userId); - + /** * 管理员获取所有智能体列表(分页) */ PageData adminAgentList(Map params); - + /** * 获取智能体详情 */ AgentEntity getAgentById(String id); -} \ No newline at end of file +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java new file mode 100644 index 00000000..9c27bdb5 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java @@ -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 { + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java index b41310ab..8e73c003 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java @@ -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 implements AgentService { - private final AgentDao agentDao; - - public AgentServiceImpl(AgentDao agentDao) { - this.agentDao = agentDao; - } - - @Override - public List getUserAgents(Long userId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId); - return agentDao.selectList(wrapper); - } - - @Override - public PageData adminAgentList(Map params) { - IPage 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); - } -} \ No newline at end of file +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 implements AgentService { + private final AgentDao agentDao; + + public AgentServiceImpl(AgentDao agentDao) { + this.agentDao = agentDao; + } + + @Override + public List getUserAgents(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + return agentDao.selectList(wrapper); + } + + @Override + public PageData adminAgentList(Map params) { + IPage 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); + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java new file mode 100644 index 00000000..62338fad --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java @@ -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 + implements AgentTemplateService { + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentTemplateVO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentTemplateVO.java new file mode 100644 index 00000000..a5302e5b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentTemplateVO.java @@ -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; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java index 72eb5171..3f18f619 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java @@ -1,5 +1,18 @@ package xiaozhi.modules.device.controller; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authz.annotation.RequiresPermissions; +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.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; @@ -28,8 +41,7 @@ public class DeviceController { private final RedisUtils redisUtils; - - @PostMapping("/bind/{agentId}") + @PostMapping("/bind/{deviceCode}") @Operation(summary = "绑定设备") @RequiresPermissions("sys:role:normal") public Result bindDevice(@PathVariable String agentId,@RequestBody String code) { @@ -55,10 +67,10 @@ public class DeviceController { @PostMapping("/unbind") @Operation(summary = "解绑设备") @RequiresPermissions("sys:role:normal") - public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) { + public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) { UserDetail user = SecurityUser.getUser(); deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId()); - return new Result(); + return new Result(); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java new file mode 100644 index 00000000..16f53256 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java @@ -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 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 deviceActivation(@RequestParam String code) { + return new Result().ok(deviceService.deviceActivation(code)); + } + + @SneakyThrows + private ResponseEntity 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); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java index 9e4a8cd4..5ede3d6e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java @@ -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 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportReqDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportReqDTO.java new file mode 100644 index 00000000..fec5a297 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportReqDTO.java @@ -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 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; + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java new file mode 100644 index 00000000..6226378d --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java @@ -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; + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java index 830a51ae..a2d02164 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java @@ -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; - /** * 设备解绑表单 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java index cf96c319..3e4ed0c4 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java @@ -1,13 +1,14 @@ package xiaozhi.modules.device.entity; +import java.util.Date; + import com.baomidou.mybatisplus.annotation.TableName; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import xiaozhi.common.entity.BaseEntity; -import java.util.Date; - @Data @EqualsAndHashCode(callSuper = false) @TableName("ai_device") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java index 448603b7..a02c6ee9 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java @@ -1,18 +1,49 @@ package xiaozhi.modules.device.service; -import xiaozhi.common.page.PageData; -import xiaozhi.modules.device.dto.DeviceBindDTO; -import xiaozhi.modules.device.entity.DeviceEntity; - import java.util.List; import java.util.Map; +import xiaozhi.common.page.PageData; +import xiaozhi.modules.device.dto.DeviceHeaderDTO; +import xiaozhi.modules.device.dto.DeviceReportReqDTO; +import xiaozhi.modules.device.dto.DeviceReportRespDTO; +import xiaozhi.modules.device.entity.DeviceEntity; + public interface DeviceService { - void bindDevice(DeviceBindDTO dto); - + + /** + * 根据Mac地址获取设备信息 + */ + DeviceEntity getDeviceById(String macAddress); + + /** + * 检查设备是否激活 + */ + DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId, + DeviceReportReqDTO deviceReport); + + /** + * 绑定设备 + */ + DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader); + + /** + * 获取用户设备列表 + */ List getUserDevices(Long userId); - + + /** + * 解绑设备 + */ void unbindDevice(Long userId, Long deviceId); - + + /** + * 管理员设备列表 + */ PageData adminDeviceList(Map params); + + /** + * 设备激活 + */ + Boolean deviceActivation(String activationCode); } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java index 957e1e3e..d32bcbe9 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java @@ -1,21 +1,173 @@ 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.common.utils.ConvertUtils; -import xiaozhi.modules.device.dao.DeviceDao; -import xiaozhi.modules.device.dto.DeviceBindDTO; -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.metadata.IPage; + +import cn.hutool.core.util.RandomUtil; +import xiaozhi.common.exception.RenException; +import xiaozhi.common.page.PageData; +import xiaozhi.common.service.impl.BaseServiceImpl; +import xiaozhi.common.user.UserDetail; +import xiaozhi.modules.device.dao.DeviceDao; +import xiaozhi.modules.device.dto.DeviceHeaderDTO; +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 implements DeviceService { + + private final DeviceDao deviceDao; + + private final String frontedUrl; + + private final RedisTemplate redisTemplate; + + // 添加构造函数来初始化 deviceMapper + public DeviceServiceImpl(DeviceDao deviceDao, + @Value("${app.fronted-url:http://localhost:8001}") String frontedUrl, + RedisTemplate redisTemplate) { + this.deviceDao = deviceDao; + this.frontedUrl = frontedUrl; + this.redisTemplate = redisTemplate; + } + + @Override + public DeviceEntity getDeviceById(String deviceId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(DeviceEntity::getId, deviceId); + return deviceDao.selectOne(queryWrapper); + } + + @Override + 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 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 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 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 void bindDevice(DeviceBindDTO dto) { DeviceEntity deviceEntity = ConvertUtils.sourceToTarget(dto, DeviceEntity.class); @@ -38,9 +190,16 @@ public class DeviceServiceImpl extends BaseServiceImpl public PageData adminDeviceList(Map params) { IPage page = baseDao.selectPage( getPage(params, "sort", true), - new QueryWrapper<>() - ); + 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; + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/utils/NetworkUtil.java b/main/manager-api/src/main/java/xiaozhi/modules/device/utils/NetworkUtil.java new file mode 100644 index 00000000..ab269f7b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/utils/NetworkUtil.java @@ -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表示单播地址,合法 + } + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java new file mode 100644 index 00000000..ec8e405e --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java @@ -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; + } + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelConfigController.java~ b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelConfigController.java~ deleted file mode 100644 index d1693839..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelConfigController.java~ +++ /dev/null @@ -1,2 +0,0 @@ -package xiaozhi.modules.model.controller;public class ModelConfigController { -} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java index f743a795..170dfe61 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java @@ -1,10 +1,21 @@ 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; @@ -13,8 +24,6 @@ import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.model.service.ModelProviderService; -import java.util.List; - @AllArgsConstructor @RestController @RequestMapping("/models") @@ -29,7 +38,7 @@ public class ModelController { @Operation(summary = "获取所有模型名称") @RequiresPermissions("sys:role:superAdmin") public Result> getModelNames(@RequestParam String modelType, - @RequestParam(required = false) String modelName) { + @RequestParam(required = false) String modelName) { List modelNameList = modelConfigService.getModelCodeList(modelType, modelName); return new Result>().ok(modelNameList); } @@ -45,51 +54,49 @@ public class ModelController { @GetMapping("/{modelType}/{provideCode}/fields") @Operation(summary = "获取模型供应器字段") @RequiresPermissions("sys:role:superAdmin") - public Result> getModelProviderFields(@PathVariable String modelType, @PathVariable String provideCode) { + public Result> getModelProviderFields(@PathVariable String modelType, + @PathVariable String provideCode) { List fieldList = modelProviderService.getFieldList(modelType, provideCode); return new Result>().ok(fieldList); } - @GetMapping("/models/list") @Operation(summary = "获取模型配置列表") @RequiresPermissions("sys:role:superAdmin") public Result> getModelConfigList(@RequestParam String modelType, - @RequestParam(required = false) String modelName, - @RequestParam(required = false, defaultValue = "0") Integer page, - @RequestParam(required = false,defaultValue = "10") Integer limit) { + @RequestParam(required = false) String modelName, + @RequestParam(required = false, defaultValue = "0") Integer page, + @RequestParam(required = false, defaultValue = "10") Integer limit) { PageData pageList = modelConfigService.getPageList(modelType, modelName, page, limit); return new Result>().ok(pageList); } - @PostMapping("/models/{modelType}/{provideCode}") @Operation(summary = "新增模型配置") @RequiresPermissions("sys:role:superAdmin") public Result 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().ok(modelConfigDTO); } - @PutMapping("/models/{modelType}/{provideCode}/{id}") @Operation(summary = "编辑模型配置") @RequiresPermissions("sys:role:superAdmin") public Result 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().ok(modelConfigDTO); } - @DeleteMapping("/models/{modelType}/{provideCode}/{id}") @Operation(summary = "删除模型配置") @RequiresPermissions("sys:role:superAdmin") - public Result deleteModelConfig(@PathVariable String modelType, @PathVariable String provideCode, @PathVariable String id) { + public Result deleteModelConfig(@PathVariable String modelType, @PathVariable String provideCode, + @PathVariable String id) { modelConfigService.delete(modelType, provideCode, id); return new Result<>(); } @@ -98,7 +105,7 @@ public class ModelController { @Operation(summary = "获取模型音色") @RequiresPermissions("sys:role:normal") public Result> getVoiceList(@PathVariable String modelName, - @RequestParam(required = false) String voiceName) { + @RequestParam(required = false) String voiceName) { List voiceList = modelConfigService.getVoiceList(modelName, voiceName); return new Result>().ok(voiceList); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelConfigDao.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelConfigDao.java index a35967e4..79c526f0 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelConfigDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelConfigDao.java @@ -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 { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelProviderDao.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelProviderDao.java index 51042e8d..45f13118 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelProviderDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dao/ModelProviderDao.java @@ -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.ModelProviderEntity; -import java.util.List; - @Mapper public interface ModelProviderDao extends BaseDao { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigBodyDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigBodyDTO.java index 639e19a2..3e335bf0 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigBodyDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigBodyDTO.java @@ -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; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigDTO.java index 95a44921..2660c328 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigDTO.java @@ -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 { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java index acce3036..5dbdda31 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java @@ -1,19 +1,20 @@ 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.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; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelConfigEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelConfigEntity.java index c98705f3..e15acaa8 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelConfigEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelConfigEntity.java @@ -1,16 +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_config") @Schema(description = "模型配置表") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java index 0c526b88..62e84f67 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java @@ -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 = "模型供应器表") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java index 1911be78..27f51d17 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java @@ -1,11 +1,11 @@ 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 getModelCodeList(String modelType, String modelName); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java index d0d221cf..37352132 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java @@ -1,13 +1,13 @@ package xiaozhi.modules.model.service; +import java.util.List; + import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.entity.ModelProviderEntity; -import java.util.List; - public interface ModelProviderService { -// List getModelNames(String modelType, String modelName); + // List getModelNames(String modelType, String modelName); List getListByModelType(String modelType); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java index a7ba9b10..f3ea0cab 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java @@ -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 implements ModelConfigService { +public class ModelConfigServiceImpl extends BaseServiceImpl + implements ModelConfigService { private final ModelConfigDao modelConfigDao; private final ModelProviderService modelProviderService; @@ -50,8 +53,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl() .eq("model_type", modelType) - .like(StringUtils.isNotBlank(modelName), "model_name", modelName) - ); + .like(StringUtils.isNotBlank(modelName), "model_name", modelName)); return getPageData(modelConfigEntityIPage, ModelConfigDTO.class); } @@ -119,6 +121,6 @@ public class ModelConfigServiceImpl extends BaseServiceImpl implements ModelProviderService { +public class ModelProviderServiceImpl extends BaseServiceImpl + implements ModelProviderService { private final ModelProviderDao modelProviderDao; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/FilterConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/FilterConfig.java index 607cb68b..d74ed08b 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/FilterConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/FilterConfig.java @@ -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 shiroFilterRegistration() { FilterRegistrationBean 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); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java index de25d80e..fc5db705 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java @@ -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 filters = new HashMap<>(); filters.put("oauth2", new Oauth2Filter()); shiroFilter.setFilters(filters); - //添加Shiro的内置过滤器 - /*anon:无需认证就可以访问 - authc:必须认证了才能让问 - user:必须拥有,记住我功能,才能访问 - perms:拥有对某个资源的权限才能访问 - role:拥有某个角色权限才能访问*/ + // 添加Shiro的内置过滤器 + /* + * anon:无需认证就可以访问 + * authc:必须认证了才能让问 + * user:必须拥有,记住我功能,才能访问 + * perms:拥有对某个资源的权限才能访问 + * role:拥有某个角色权限才能访问 + */ Map filterMap = new LinkedHashMap<>(); + filterMap.put("/ota/**", "anon"); filterMap.put("/webjars/**", "anon"); filterMap.put("/druid/**", "anon"); filterMap.put("/v3/api-docs/**", "anon"); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java index a086e953..7cfb03d9 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java @@ -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); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java index 6ab9d1b9..657217ae 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java @@ -1,12 +1,18 @@ 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; @@ -22,8 +28,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 +40,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); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/dao/SysUserTokenDao.java b/main/manager-api/src/main/java/xiaozhi/modules/security/dao/SysUserTokenDao.java index 044568e5..05f946d3 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/dao/SysUserTokenDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/dao/SysUserTokenDao.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java index 2fc0d3b2..cab143be 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java @@ -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; - /** * 登录表单 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/entity/SysUserTokenEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/security/entity/SysUserTokenEntity.java index fb96f88c..2346b5e4 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/entity/SysUserTokenEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/entity/SysUserTokenEntity.java @@ -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 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java index 55e3fa03..e155b77c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java @@ -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().error(ErrorCode.UNAUTHORIZED)); httpResponse.getWriter().print(json); @@ -79,16 +75,17 @@ 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 r = new Result().error(ErrorCode.UNAUTHORIZED, throwable.getMessage()); String json = JsonUtils.toJsonString(r); httpResponse.getWriter().print(json); @@ -104,7 +101,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 ", ""); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java index 6b04bb08..27c3bc49 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java @@ -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 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)); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java index 0ca1cede..6d7be47a 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCrypt.java b/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCrypt.java index 6a9efff9..39d350c5 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCrypt.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCrypt.java @@ -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. *

- * 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. *

- * 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: *

* * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
*
*

- * 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: *

* @@ -30,7 +36,8 @@ import java.security.SecureRandom; *     System.out.println("It does not match");
*
*

- * 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: *

* @@ -38,7 +45,8 @@ import java.security.SecureRandom; * String stronger_salt = BCrypt.gensalt(12)
*
*

- * 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 not 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 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCryptPasswordEncoder.java b/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCryptPasswordEncoder.java index 0d570a96..0da907f2 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCryptPasswordEncoder.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCryptPasswordEncoder.java @@ -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 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordEncoder.java b/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordEncoder.java index 40d1e95c..2de70cc2 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordEncoder.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordEncoder.java @@ -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); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordUtils.java b/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordUtils.java index b0357010..9144b380 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordUtils.java @@ -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); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java index b91d5d9a..9e5c153f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java @@ -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); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java index 761ad139..012b4d3e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java @@ -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 localCache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(5, TimeUnit.MINUTES).build(); - private static final Logger logger = LoggerFactory.getLogger(Oauth2Realm.class); + Cache 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); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java index 97d16eaa..27c20970 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java @@ -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); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java index 40ee7957..7f0d311a 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java @@ -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 implements SysUserTokenService { +public class SysUserTokenServiceImpl extends BaseServiceImpl + implements SysUserTokenService { private final SysUserService sysUserService; /** @@ -31,18 +33,18 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl 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().ok(tokenDTO); } @Override diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/user/SecurityUser.java b/main/manager-api/src/main/java/xiaozhi/modules/security/user/SecurityUser.java index cfa0fbf7..0581f95c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/user/SecurityUser.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/user/SecurityUser.java @@ -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. diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysUserController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysUserController.java index 9da7c62b..4aed782d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysUserController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysUserController.java @@ -1,9 +1,10 @@ package xiaozhi.modules.sys.controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + 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; /** * 用户管理 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java index 08aaca5b..9dd124fe 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java @@ -1,12 +1,13 @@ package xiaozhi.modules.sys.dao; +import java.util.List; + +import org.apache.ibatis.annotations.Mapper; + import xiaozhi.common.dao.BaseDao; import xiaozhi.modules.sys.dto.SysDictDataDTO; import xiaozhi.modules.sys.entity.DictData; import xiaozhi.modules.sys.entity.SysDictDataEntity; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; /** * 字典数据 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java index bca8e789..5a9d5a27 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java @@ -1,11 +1,12 @@ package xiaozhi.modules.sys.dao; +import java.util.List; + +import org.apache.ibatis.annotations.Mapper; + import xiaozhi.common.dao.BaseDao; import xiaozhi.modules.sys.entity.DictType; import xiaozhi.modules.sys.entity.SysDictTypeEntity; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; /** * 字典类型 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java index 0c967a74..d35d1445 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java @@ -1,11 +1,12 @@ package xiaozhi.modules.sys.dao; -import xiaozhi.common.dao.BaseDao; -import xiaozhi.modules.sys.entity.SysParamsEntity; +import java.util.List; + import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; -import java.util.List; +import xiaozhi.common.dao.BaseDao; +import xiaozhi.modules.sys.entity.SysParamsEntity; /** * 参数管理 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysUserDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysUserDao.java index 339e522a..fb2a2492 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysUserDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysUserDao.java @@ -1,6 +1,7 @@ package xiaozhi.modules.sys.dao; import org.apache.ibatis.annotations.Mapper; + import xiaozhi.common.dao.BaseDao; import xiaozhi.modules.sys.entity.SysUserEntity; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java index 8c0ab77c..7de3843a 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java @@ -1,11 +1,11 @@ package xiaozhi.modules.sys.dto; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.Data; /** * 管理员分页用户的参数DTO + * * @author zjy * @since 2025-3-21 */ @@ -13,7 +13,6 @@ import lombok.Data; @Schema(description = "音色分页参数") public class AdminPageUserDTO { - @Schema(description = "手机号码") private String mobile; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/PasswordDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/PasswordDTO.java index 1ecadc68..4aabb10e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/PasswordDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/PasswordDTO.java @@ -1,11 +1,11 @@ package xiaozhi.modules.sys.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; - /** * 修改密码 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictDataDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictDataDTO.java index 172e6b1f..c18dbf8f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictDataDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictDataDTO.java @@ -1,20 +1,21 @@ package xiaozhi.modules.sys.dto; +import java.io.Serializable; +import java.util.Date; + import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; -import xiaozhi.common.utils.DateUtils; -import xiaozhi.common.validator.group.AddGroup; -import xiaozhi.common.validator.group.DefaultGroup; -import xiaozhi.common.validator.group.UpdateGroup; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Null; import lombok.Data; - -import java.io.Serializable; -import java.util.Date; +import xiaozhi.common.utils.DateUtils; +import xiaozhi.common.validator.group.AddGroup; +import xiaozhi.common.validator.group.DefaultGroup; +import xiaozhi.common.validator.group.UpdateGroup; /** * 字典数据 @@ -23,7 +24,6 @@ import java.util.Date; @Schema(description = "字典数据") public class SysDictDataDTO implements Serializable { - @Schema(description = "id") @Null(message = "{id.null}", groups = AddGroup.class) @NotNull(message = "{id.require}", groups = UpdateGroup.class) diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java index 72648109..01d69795 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java @@ -1,20 +1,21 @@ package xiaozhi.modules.sys.dto; +import java.io.Serializable; +import java.util.Date; + import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; -import xiaozhi.common.utils.DateUtils; -import xiaozhi.common.validator.group.AddGroup; -import xiaozhi.common.validator.group.DefaultGroup; -import xiaozhi.common.validator.group.UpdateGroup; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Null; import lombok.Data; - -import java.io.Serializable; -import java.util.Date; +import xiaozhi.common.utils.DateUtils; +import xiaozhi.common.validator.group.AddGroup; +import xiaozhi.common.validator.group.DefaultGroup; +import xiaozhi.common.validator.group.UpdateGroup; /** * 字典类型 @@ -23,7 +24,6 @@ import java.util.Date; @Schema(description = "字典类型") public class SysDictTypeDTO implements Serializable { - @Schema(description = "id") @Null(message = "{id.null}", groups = AddGroup.class) @NotNull(message = "{id.require}", groups = UpdateGroup.class) diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java index 4b72ce34..7b4dd28d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java @@ -1,19 +1,20 @@ package xiaozhi.modules.sys.dto; +import java.io.Serializable; +import java.util.Date; + import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; -import xiaozhi.common.utils.DateUtils; -import xiaozhi.common.validator.group.AddGroup; -import xiaozhi.common.validator.group.DefaultGroup; -import xiaozhi.common.validator.group.UpdateGroup; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Null; import lombok.Data; - -import java.io.Serializable; -import java.util.Date; +import xiaozhi.common.utils.DateUtils; +import xiaozhi.common.validator.group.AddGroup; +import xiaozhi.common.validator.group.DefaultGroup; +import xiaozhi.common.validator.group.UpdateGroup; /** * 参数管理 @@ -22,7 +23,6 @@ import java.util.Date; @Schema(description = "参数管理") public class SysParamsDTO implements Serializable { - @Schema(description = "id") @Null(message = "{id.null}", groups = AddGroup.class) @NotNull(message = "{id.require}", groups = UpdateGroup.class) diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysUserDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysUserDTO.java index 00f7fc78..ff066341 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysUserDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysUserDTO.java @@ -1,22 +1,24 @@ package xiaozhi.modules.sys.dto; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +import org.hibernate.validator.constraints.Range; + import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; -import xiaozhi.common.utils.DateUtils; -import xiaozhi.common.validator.group.AddGroup; -import xiaozhi.common.validator.group.DefaultGroup; -import xiaozhi.common.validator.group.UpdateGroup; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Null; import lombok.Data; -import org.hibernate.validator.constraints.Range; - -import java.io.Serializable; -import java.util.Date; -import java.util.List; +import xiaozhi.common.utils.DateUtils; +import xiaozhi.common.validator.group.AddGroup; +import xiaozhi.common.validator.group.DefaultGroup; +import xiaozhi.common.validator.group.UpdateGroup; /** * 用户管理 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java index 1e16a192..c8deac81 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java @@ -1,6 +1,7 @@ package xiaozhi.modules.sys.entity; import com.fasterxml.jackson.annotation.JsonIgnore; + import lombok.Data; /** diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java index 3cf94606..4a1e2ca5 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java @@ -1,11 +1,12 @@ package xiaozhi.modules.sys.entity; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Data; - import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import lombok.Data; + /** * 字典类型 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictDataEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictDataEntity.java index 4bfa4b62..16d3dcae 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictDataEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictDataEntity.java @@ -1,13 +1,14 @@ package xiaozhi.modules.sys.entity; +import java.util.Date; + import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; -import xiaozhi.common.entity.BaseEntity; + import lombok.Data; import lombok.EqualsAndHashCode; - -import java.util.Date; +import xiaozhi.common.entity.BaseEntity; /** * 数据字典 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java index ea752c63..fa372055 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java @@ -1,13 +1,14 @@ package xiaozhi.modules.sys.entity; +import java.util.Date; + import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; -import xiaozhi.common.entity.BaseEntity; + import lombok.Data; import lombok.EqualsAndHashCode; - -import java.util.Date; +import xiaozhi.common.entity.BaseEntity; /** * 字典类型 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java index 054e51d7..989800e2 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java @@ -1,13 +1,14 @@ package xiaozhi.modules.sys.entity; +import java.util.Date; + import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; -import xiaozhi.common.entity.BaseEntity; + import lombok.Data; import lombok.EqualsAndHashCode; - -import java.util.Date; +import xiaozhi.common.entity.BaseEntity; /** * 参数管理 @@ -25,7 +26,7 @@ public class SysParamsEntity extends BaseEntity { */ private String paramValue; /** - * 类型 0:系统参数 1:非系统参数 + * 类型 0:系统参数 1:非系统参数 */ private Integer paramType; /** diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysUserEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysUserEntity.java index 0093fcf0..23043a37 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysUserEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysUserEntity.java @@ -1,14 +1,15 @@ package xiaozhi.modules.sys.entity; +import java.util.Date; + import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; + import lombok.Data; import lombok.EqualsAndHashCode; import xiaozhi.common.entity.BaseEntity; -import java.util.Date; - /** * 系统用户 */ @@ -25,11 +26,11 @@ public class SysUserEntity extends BaseEntity { */ private String password; /** - * 超级管理员 0:否 1:是 + * 超级管理员 0:否 1:是 */ private Integer superAdmin; /** - * 状态 0:停用 1:正常 + * 状态 0:停用 1:正常 */ private Integer status; /** diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/redis/SysParamsRedis.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/redis/SysParamsRedis.java index dcc14f69..2e2bdb42 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/redis/SysParamsRedis.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/redis/SysParamsRedis.java @@ -1,9 +1,10 @@ package xiaozhi.modules.sys.redis; +import org.springframework.stereotype.Component; + +import lombok.AllArgsConstructor; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Component; /** * 参数管理 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java index 77be8b3a..a33df029 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java @@ -1,12 +1,12 @@ package xiaozhi.modules.sys.service; +import java.util.Map; + import xiaozhi.common.page.PageData; import xiaozhi.common.service.BaseService; import xiaozhi.modules.sys.dto.SysDictDataDTO; import xiaozhi.modules.sys.entity.SysDictDataEntity; -import java.util.Map; - /** * 数据字典 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java index 8924f6ca..a18e086f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java @@ -1,14 +1,14 @@ package xiaozhi.modules.sys.service; +import java.util.List; +import java.util.Map; + import xiaozhi.common.page.PageData; import xiaozhi.common.service.BaseService; import xiaozhi.modules.sys.dto.SysDictTypeDTO; import xiaozhi.modules.sys.entity.DictType; import xiaozhi.modules.sys.entity.SysDictTypeEntity; -import java.util.List; -import java.util.Map; - /** * 数据字典 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java index d13e41ba..7475f346 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java @@ -1,14 +1,13 @@ package xiaozhi.modules.sys.service; +import java.util.List; +import java.util.Map; import xiaozhi.common.page.PageData; import xiaozhi.common.service.BaseService; import xiaozhi.modules.sys.dto.SysParamsDTO; import xiaozhi.modules.sys.entity.SysParamsEntity; -import java.util.List; -import java.util.Map; - /** * 参数管理 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java index f8f609dc..261fbf96 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java @@ -8,7 +8,6 @@ import xiaozhi.modules.sys.dto.SysUserDTO; import xiaozhi.modules.sys.entity.SysUserEntity; import xiaozhi.modules.sys.vo.AdminPageUserVO; - /** * 系统用户 */ @@ -24,26 +23,31 @@ public interface SysUserService extends BaseService { /** * 验证是否允许修改密码更改 - * @param userId 用户id + * + * @param userId 用户id * @param passwordDTO 验证密码的参数 */ void changePassword(Long userId, PasswordDTO passwordDTO); /** * 直接修改密码,不需要验证 - * @param userId 用户id + * + * @param userId 用户id * @param password 密码 */ void changePasswordDirectly(Long userId, String password); /** * 重置密码 + * * @param userId 用户id * @return 随机生成符合规范的密码 */ String resetPassword(Long userId); + /** * 管理员分页用户信息 + * * @param dto 分页查找参数 * @return 用户列表分页数据 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java index a8420dc9..20530e6b 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java @@ -1,10 +1,15 @@ package xiaozhi.modules.sys.service.impl; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; +import java.util.Arrays; +import java.util.Map; + import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; + +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.common.utils.ConvertUtils; @@ -13,21 +18,18 @@ import xiaozhi.modules.sys.dto.SysDictDataDTO; import xiaozhi.modules.sys.entity.SysDictDataEntity; import xiaozhi.modules.sys.service.SysDictDataService; -import java.util.Arrays; -import java.util.Map; - /** * 字典类型 */ @Service -public class SysDictDataServiceImpl extends BaseServiceImpl implements SysDictDataService { +public class SysDictDataServiceImpl extends BaseServiceImpl + implements SysDictDataService { @Override public PageData page(Map params) { IPage page = baseDao.selectPage( getPage(params, "sort", true), - getWrapper(params) - ); + getWrapper(params)); return getPageData(page, SysDictDataDTO.class); } @@ -71,7 +73,7 @@ public class SysDictDataServiceImpl extends BaseServiceImpl implements SysDictTypeService { +public class SysDictTypeServiceImpl extends BaseServiceImpl + implements SysDictTypeService { private final SysDictDataDao sysDictDataDao; @Override public PageData page(Map params) { IPage page = baseDao.selectPage( getPage(params, "sort", true), - getWrapper(params) - ); + getWrapper(params)); return getPageData(page, SysDictTypeDTO.class); } @@ -76,7 +78,7 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl page(Map params) { IPage page = baseDao.selectPage( getPage(params, Constant.CREATE_DATE, false), - getWrapper(params) - ); + getWrapper(params)); return getPageData(page, SysParamsDTO.class); } @@ -86,12 +87,12 @@ public class SysParamsServiceImpl extends BaseServiceImpl paramCodeList = baseDao.getParamCodeList(ids); String[] paramCodes = paramCodeList.toArray(new String[paramCodeList.size()]); sysParamsRedis.delete(paramCodes); - //删除 + // 删除 deleteBatchIds(Arrays.asList(ids)); } @@ -114,7 +115,7 @@ public class SysParamsServiceImpl extends BaseServiceImpl page(AdminPageUserDTO dto) { Map params = new HashMap(); params.put(Constant.PAGE, dto.getPage()); - params.put(Constant.LIMIT,dto.getLimit()); + params.put(Constant.LIMIT, dto.getLimit()); IPage page = baseDao.selectPage( getPage(params, "id", true), - //定义查询条件 + // 定义查询条件 new QueryWrapper() - //必须按照手机号码查找 - .eq(StringUtils.isNotBlank(dto.getMobile()),"username",dto.getMobile())); + // 必须按照手机号码查找 + .eq(StringUtils.isNotBlank(dto.getMobile()), "username", dto.getMobile())); List list = page.getRecords().stream().map(user -> { AdminPageUserVO adminPageUserVO = new AdminPageUserVO(); adminPageUserVO.setUserid(user.getId().toString()); adminPageUserVO.setMobile(user.getUsername()); - //TODO 2. 等设备功能写好,获取对应数据 + // TODO 2. 等设备功能写好,获取对应数据 adminPageUserVO.setDeviceCount("0"); return adminPageUserVO; }).toList(); @@ -169,11 +173,13 @@ public class SysUserServiceImpl extends BaseServiceImpl delete(@PathVariable Long id) { - timbreService.delete(new Long[]{id}); + timbreService.delete(new Long[] { id }); return new Result<>(); } - } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java index 62afa358..3656b4ea 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java @@ -1,11 +1,14 @@ package xiaozhi.modules.timbre.dao; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + import xiaozhi.modules.timbre.entity.TimbreEntity; /** * 音色持久层定义 + * * @author zjy * @since 2025-3-21 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java index 08ed9b18..ed737db9 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java @@ -5,9 +5,9 @@ import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import lombok.Data; - /** * 音色表数据DTO + * * @author zjy * @since 2025-3-21 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java index 72ed68b1..9c2aaf24 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java @@ -6,6 +6,7 @@ import lombok.Data; /** * 音色分页参数DTO + * * @author zjy * @since 2025-3-21 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java index 71bd58a9..44ca195b 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java @@ -1,15 +1,17 @@ package xiaozhi.modules.timbre.entity; +import java.util.Date; + import com.baomidou.mybatisplus.annotation.TableName; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import xiaozhi.common.entity.BaseEntity; -import java.util.Date; - /** * 音色表实体类 + * * @author zjy * @since 2025-3-21 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java index 044322e5..b9d4578e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java @@ -1,5 +1,7 @@ package xiaozhi.modules.timbre.service; +import java.util.List; + import xiaozhi.common.page.PageData; import xiaozhi.common.service.BaseService; import xiaozhi.modules.timbre.dto.TimbreDataDTO; @@ -7,17 +9,16 @@ import xiaozhi.modules.timbre.dto.TimbrePageDTO; import xiaozhi.modules.timbre.entity.TimbreEntity; import xiaozhi.modules.timbre.vo.TimbreDetailsVO; -import java.util.List; - - /** * 音色的业务层的定义 + * * @author zjy * @since 2025-3-21 */ public interface TimbreService extends BaseService { /** * 分页获取音色指定tts的下的音色 + * * @param dto 分页查找参数 * @return 音色列表分页数据 */ @@ -25,6 +26,7 @@ public interface TimbreService extends BaseService { /** * 获取音色指定id的详情信息 + * * @param timbreId 音色表id * @return 音色信息 */ @@ -32,19 +34,22 @@ public interface TimbreService extends BaseService { /** * 保存音色信息 + * * @param dto 需要保存数据 */ void save(TimbreDataDTO dto); /** * 保存音色信息 + * * @param timbreId 需要修改的id - * @param dto 需要修改的数据 + * @param dto 需要修改的数据 */ - void update(Long timbreId,TimbreDataDTO dto); + void update(Long timbreId, TimbreDataDTO dto); /** * 批量删除音色 + * * @param ids 需要被删除的音色id列表 */ void delete(Long[] ids); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java index fa2731bd..dc041daf 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java @@ -1,12 +1,19 @@ package xiaozhi.modules.timbre.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.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; + +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.page.PageData; import xiaozhi.common.service.impl.BaseServiceImpl; @@ -18,13 +25,9 @@ import xiaozhi.modules.timbre.entity.TimbreEntity; import xiaozhi.modules.timbre.service.TimbreService; import xiaozhi.modules.timbre.vo.TimbreDetailsVO; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - /** * 音色的业务层的实现 + * * @author zjy * @since 2025-3-21 */ @@ -38,16 +41,15 @@ public class TimbreServiceImpl extends BaseServiceImpl public PageData page(TimbrePageDTO dto) { Map params = new HashMap(); params.put(Constant.PAGE, dto.getPage()); - params.put(Constant.LIMIT,dto.getLimit()); + params.put(Constant.LIMIT, dto.getLimit()); IPage page = baseDao.selectPage( getPage(params, "sort", true), - //定义查询条件 + // 定义查询条件 new QueryWrapper() - //必须按照ttsID查找 - .eq("tts_model_id",dto.getTtsModelId()) - //如果有音色名字,按照音色名模糊查找 - .like(StringUtils.isNotBlank(dto.getName()),"name",dto.getName()) - ); + // 必须按照ttsID查找 + .eq("tts_model_id", dto.getTtsModelId()) + // 如果有音色名字,按照音色名模糊查找 + .like(StringUtils.isNotBlank(dto.getName()), "name", dto.getName())); return getPageData(page, TimbreDetailsVO.class); } @@ -99,7 +101,7 @@ public class TimbreServiceImpl extends BaseServiceImpl /** * 处理是不是tts模型的id */ - private void isTtsModelId(String ttsModelId){ - //等模型配置那边写好调用方法判断 + private void isTtsModelId(String ttsModelId) { + // 等模型配置那边写好调用方法判断 } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java index fc82a5d0..68cc7bbb 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java @@ -1,12 +1,13 @@ package xiaozhi.modules.timbre.vo; +import java.io.Serializable; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import java.io.Serializable; - /** * 音色详情展示VO + * * @author zjy * @since 2025-3-21 */ diff --git a/main/manager-api/src/main/resources/application-dev.yml b/main/manager-api/src/main/resources/application-dev.yml index a8fdf21f..753cca9a 100644 --- a/main/manager-api/src/main/resources/application-dev.yml +++ b/main/manager-api/src/main/resources/application-dev.yml @@ -15,7 +15,7 @@ spring: druid: #MySQL driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true + url: jdbc:mysql://127.0.0.1:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true username: root password: 123456 initial-size: 10 @@ -41,4 +41,17 @@ spring: merge-sql: false wall: config: - multi-statement-allow: true \ No newline at end of file + multi-statement-allow: true + data: + redis: + host: 127.0.0.1 # Redis服务器地址 + port: 6379 # Redis服务器连接端口 + password: # Redis服务器连接密码(默认为空) + database: 0 # Redis数据库索引(默认为0) + timeout: 10000ms # 连接超时时间(毫秒) + lettuce: + pool: + max-active: 8 # 连接池最大连接数(使用负值表示没有限制) + max-idle: 8 # 连接池中的最大空闲连接 + min-idle: 0 # 连接池中的最小空闲连接 + shutdown-timeout: 100ms # 客户端优雅关闭的等待时间 \ No newline at end of file diff --git a/main/manager-api/src/main/resources/application.yml b/main/manager-api/src/main/resources/application.yml index 03f6a044..902b5198 100644 --- a/main/manager-api/src/main/resources/application.yml +++ b/main/manager-api/src/main/resources/application.yml @@ -24,21 +24,9 @@ spring: max-file-size: 100MB max-request-size: 100MB enabled: true - data: - redis: - database: 0 - host: 127.0.0.1 - port: 6379 - password: # 密码(默认为空) - timeout: 6000ms # 连接超时时长(毫秒) - lettuce: - pool: - max-active: 1000 # 连接池最大连接数(使用负值表示没有限制) - max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) - max-idle: 10 # 连接池中的最大空闲连接 - min-idle: 5 # 连接池中的最小空闲连接 main: allow-bean-definition-overriding: true + knife4j: enable: true basic: @@ -50,7 +38,7 @@ knife4j: renren: redis: - open: false + open: true xss: enabled: true exclude-urls: @@ -75,4 +63,7 @@ mybatis-plus: configuration-properties: prefix: blobType: BLOB - boolValue: TRUE \ No newline at end of file + boolValue: TRUE + +app: + fronted-url: http://localhost:8001 \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202503241021.sql b/main/manager-api/src/main/resources/db/changelog/202503241021.sql new file mode 100644 index 00000000..e9aca210 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202503241021.sql @@ -0,0 +1,29 @@ +-- 初始化智能体模板数据 +DELETE FROM `ai_agent_template`; +INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小智', '湾湾小何', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', 'd50b06e9b8104d0d9c0f7316d258abcb', 'fcac83266edadd5a3125f06cfee1906b', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的台湾女孩,说话机车,声音好听,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 1, 1, NULL, NULL, NULL, NULL); +INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小智', '通用男声', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '1f2e3d4c5b6a7f8e9d0c1b2a3f4e5bx2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的男生,说话机车,声音好听,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 2, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小智', '通用女声', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '9e8f7a6b5c4d3e2f1a0b9c8d7e6f5ad3', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的女生,说话机车,声音好听,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 3, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小智', '阳光男生', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '2b3c4d5e6f7a8b9c0d1e2f3a4b5c62a2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的男生,说话机车,声音好听,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 4, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_agent_template` VALUES ('a45b6c7d8e9f0a1b2c3d4e5f6a7b8c92', '小智', '奶气萌娃', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', 'f7a38c03d5644e22b6d84f8923a74c51', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的萌娃,声音可爱,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 5, 0, NULL, NULL, NULL, NULL); + +-- 初始化模型配置数据 +DELETE FROM `ai_model_config`; +INSERT INTO `ai_model_config` VALUES ('23e7c9d090ea4d1e9b25f4c8d732a3a1', 'VAD', 'SileroVAD', 'SileroVAD', 1, 1, '{\"SileroVAD\": {\"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('45f8b0d6dd3d4bfa8a28e6e0f5912d45', 'ASR', 'FunASR', 'FunASR', 1, 1, '{\"FunASR\": {\"type\": \"fun_local\", \"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('e2274b90e89ddda85207f55484d8b528', 'Memory', 'nomem', 'nomem', 1, 1, '{\"mem0ai\": {\"type\": \"nomem\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('3930ac3448faf621f0a120bc829dfdfa', 'Memory', 'mem_local_short', 'mem_local_short', 1, 1, '{\"mem_local_short\": {\"type\": \"mem_local_short\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('a07f3d25f52340b2b2a1e8d264079e1a', 'Memory', 'mem0ai', 'mem0ai', 1, 1, '{\"mem0ai\": {\"type\": \"mem0ai\", \"api_key\": \"你的mem0ai api key\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('7a1c0a8e6d0e4035b982a4c07c3a5f76', 'LLM', 'AliLLM', 'AliLLM', 1, 1, '{\"AliLLM\": {\"type\": \"openai\", \"top_k\": 50, \"top_p\": 1, \"api_key\": \"你的ali api key\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"max_tokens\": 500, \"model_name\": \"qwen-turbo\", \"temperature\": 0.7, \"frequency_penalty\": 0}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('e9f2d891afbe4632b13a47c7a8c6e03d', 'LLM', 'ChatGLMLLM', 'ChatGLMLLM', 1, 1, '{\"ChatGLMLLM\": {\"url\": \"https://open.bigmodel.cn/api/paas/v4/\", \"type\": \"openai\", \"api_key\": \"0415dad4014847babc3e3f03024c50a3.qH7FgTy5Yawc85fl\", \"model_name\": \"glm-4-flash\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('d50b06e9b8104d0d9c0f7316d258abcb', 'TTS', 'EdgeTTS', 'EdgeTTS', 1, 1, '{\"EdgeTTS\": {\"type\": \"edge\", \"voice\": \"zh-CN-XiaoxiaoNeural\", \"output_dir\": \"tmp/\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('896db62c9dd74976ab0e8c14bf924d9d', 'TTS', 'DoubaoTTS', 'DoubaoTTS', 1, 1, '{\"DoubaoTTS\": {\"type\": \"doubao\", \"appid\": \"你的火山引擎语音合成服务appid\", \"voice\": \"BV034_streaming\", \"api_url\": \"https://openspeech.bytedance.com/api/v1/tts\", \"cluster\": \"volcano_tts\", \"output_dir\": \"tmp/\", \"access_token\": \"你的火山引擎语音合成服务access_token\", \"authorization\": \"Bearer;\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('c4e12f874a3f4aa99f5b2c18e15d407b', 'Intent', 'function_call', 'function_call', 1, 1, '{\"function_call\": {\"type\": \"nointent\", \"functions\": [\"change_role\", \"get_weather\", \"get_news\", \"play_music\"]}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); + +-- 初始化音色数据 +DELETE FROM `ai_tts_voice`; +INSERT INTO `ai_tts_voice` VALUES ('fcac83266edadd5a3125f06cfee1906b', '896db62c9dd74976ab0e8c14bf924d9d', '湾湾小何', 'zh-CN-XiaoxiaoNeural', '中文', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/湾湾小何.mp3', NULL, 1, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('1f2e3d4c5b6a7f8e9d0c1b2a3f4e5bx2', '896db62c9dd74976ab0e8c14bf924d9d', '通用男声', 'BV002_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV002.mp3', NULL, 2, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('9e8f7a6b5c4d3e2f1a0b9c8d7e6f5ad3', '896db62c9dd74976ab0e8c14bf924d9d', '通用女声', 'BV001_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV001.mp3', NULL, 3, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('2b3c4d5e6f7a8b9c0d1e2f3a4b5c62a2', '896db62c9dd74976ab0e8c14bf924d9d', '阳光男生', 'BV056_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV056.mp3', NULL, 4, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('f7a38c03d5644e22b6d84f8923a74c51', '896db62c9dd74976ab0e8c14bf924d9d', '奶气萌娃', 'BV051_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV051.mp3', NULL, 5, NULL, NULL, NULL, NULL); + diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 89ed7ff5..1f2f2118 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -15,4 +15,11 @@ databaseChangeLog: changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/202503141346.sql \ No newline at end of file + path: classpath:db/changelog/202503141346.sql + - changeSet: + id: 202503241021 + author: dreamchen + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202503241021.sql \ No newline at end of file diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml b/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml new file mode 100644 index 00000000..bc372f8a --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml @@ -0,0 +1,6 @@ + + + + diff --git a/main/manager-web/src/components/ActivateDeviceDialog.vue b/main/manager-web/src/components/ActivateDeviceDialog.vue new file mode 100644 index 00000000..59384e79 --- /dev/null +++ b/main/manager-web/src/components/ActivateDeviceDialog.vue @@ -0,0 +1,98 @@ + + + + + \ No newline at end of file diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 7289743b..e2c35e67 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -82,9 +82,10 @@ selected_module: TTS: EdgeTTS # 记忆模块,默认不开启记忆;如果想使用超长记忆,推荐使用mem0ai;如果注重隐私,请使用本地的mem_local_short Memory: nomem - # 意图识别模块,默认使用function_call。开启后,可以播放音乐、控制音量、识别退出指令 - # 意图识别使用intent_llm,优点:通用性强,缺点:增加串行前置意图识别模块,会增加处理时间 - # 意图识别使用function_call,缺点:需要所选择的LLM支持function_call,优点:按需调用工具、速度快 + # 意图识别模块开启后,可以播放音乐、控制音量、识别退出指令。 + # 不想开通意图识别,就设置成:nointent + # 意图识别可使用intent_llm,如果你的LLM是DifyLLM或CozeLLM,建议使用这个。优点:通用性强,缺点:增加串行前置意图识别模块,会增加处理时间,这个意图识别暂时不支持控制音量大小等iot操作 + # 意图识别可使用function_call,缺点:需要所选择的LLM支持function_call,优点:按需调用工具、速度快,理论上能全部操作所有iot指令 # 默认免费的ChatGLMLLM就已经支持function_call,但是如果像追求稳定建议把LLM设置成:DoubaoLLM,使用的具体model_name是:doubao-pro-32k-functioncall-241028 Intent: function_call @@ -97,9 +98,13 @@ Intent: intent_llm: # 不需要动type type: intent_llm + # 配备意图识别独立的思考模型 + # 如果这里不填,则会默认使用selected_module.LLM的模型作为意图识别的思考模型 + # 如果你的selected_module.LLM选择了DifyLLM或CozeLLM,这里最好使用独立的LLM作为意图识别,例如使用免费的ChatGLMLLM + llm: ChatGLMLLM function_call: # 不需要动type - type: nointent + type: function_call # plugins_func/functions下的模块,可以通过配置,选择加载哪个模块,加载后对话支持相应的function调用 # 系统默认已经记载“handle_exit_intent(退出识别)”、“play_music(音乐播放)”插件,请勿重复加载 # 下面是加载查天气、角色切换、加载查新闻的插件示例 @@ -278,6 +283,18 @@ LLM: variables: k: "v" k2: "v2" + XinferenceLLM: + # 定义LLM API类型 + type: xinference + # Xinference服务地址和模型名称 + model_name: qwen2.5:72b-AWQ # 使用的模型名称,需要预先在Xinference启动对应模型 + base_url: http://localhost:9997 # Xinference服务地址 + XinferenceSmallLLM: + # 定义轻量级LLM API类型,用于意图识别 + type: xinference + # Xinference服务地址和模型名称 + model_name: qwen2.5:3b-AWQ # 使用的小模型名称,用于意图识别 + base_url: http://localhost:9997 # Xinference服务地址 TTS: # 当前支持的type为edge、doubao,可自行适配 EdgeTTS: @@ -534,4 +551,4 @@ wakeup_words: - "小龙小龙" - "喵喵同学" - "小滨小滨" - - "小冰小冰" \ No newline at end of file + - "小冰小冰" diff --git a/main/xiaozhi-server/config/logger.py b/main/xiaozhi-server/config/logger.py index 2206e52c..e652e3ec 100644 --- a/main/xiaozhi-server/config/logger.py +++ b/main/xiaozhi-server/config/logger.py @@ -3,7 +3,7 @@ import sys from loguru import logger from config.settings import load_config -SERVER_VERSION = "0.1.15" +SERVER_VERSION = "0.1.16" def setup_logging(): """从配置文件中读取日志配置,并设置日志输出格式和级别""" @@ -13,7 +13,7 @@ def setup_logging(): log_format_file = log_config.get("log_format_file", "{time:YYYY-MM-DD HH:mm:ss} - {version_{selected_module}} - {name} - {level} - {extra[tag]} - {message}") selected_module = config.get("selected_module") - selected_module_str = ''.join([key[0] + value[0] for key, value in selected_module.items()]) + selected_module_str = ''.join([value[0] + value[1] for key, value in selected_module.items()]) log_format = log_format.replace("{version}", SERVER_VERSION) log_format = log_format.replace("{selected_module}", selected_module_str) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 98cf5c5c..0f4e2a70 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -18,10 +18,11 @@ from concurrent.futures import ThreadPoolExecutor, TimeoutError from core.handle.sendAudioHandle import sendAudioMessage from core.handle.receiveAudioHandle import handleAudioMessage from core.handle.functionHandler import FunctionHandler -from plugins_func.register import Action +from plugins_func.register import Action, ActionResponse from config.private_config import PrivateConfig from core.auth import AuthMiddleware, AuthenticationError from core.utils.auth_code_gen import AuthCodeGenerator +from core.mcp.manager import MCPManager TAG = __name__ @@ -100,6 +101,8 @@ class ConnectionHandler: self.use_function_call_mode = False if self.config["selected_module"]["Intent"] == 'function_call': self.use_function_call_mode = True + + self.mcp_manager = MCPManager(self) async def handle_connection(self, ws): try: @@ -194,7 +197,31 @@ class ConnectionHandler: """加载记忆""" device_id = self.headers.get("device-id", None) self.memory.init_memory(device_id, self.llm) - self.intent.set_llm(self.llm) + + """为意图识别设置LLM,优先使用专用LLM""" + # 检查是否配置了专用的意图识别LLM + intent_llm_name = self.config["Intent"]["intent_llm"]["llm"] + + # 记录开始初始化意图识别LLM的时间 + intent_llm_init_start = time.time() + + if not self.use_function_call_mode and intent_llm_name and intent_llm_name in self.config["LLM"]: + # 如果配置了专用LLM,则创建独立的LLM实例 + from core.utils import llm as llm_utils + intent_llm_config = self.config["LLM"][intent_llm_name] + intent_llm_type = intent_llm_config.get("type", intent_llm_name) + intent_llm = llm_utils.create_instance(intent_llm_type, intent_llm_config) + self.logger.bind(tag=TAG).info(f"为意图识别创建了专用LLM: {intent_llm_name}, 类型: {intent_llm_type}") + + self.intent.set_llm(intent_llm) + else: + # 否则使用主LLM + self.intent.set_llm(self.llm) + self.logger.bind(tag=TAG).info("意图识别使用主LLM") + + # 记录意图识别LLM初始化耗时 + intent_llm_init_time = time.time() - intent_llm_init_start + self.logger.bind(tag=TAG).info(f"意图识别LLM初始化完成,耗时: {intent_llm_init_time:.4f}秒") """加载位置信息""" self.client_ip_info = get_ip_info(self.client_ip) @@ -203,6 +230,9 @@ class ConnectionHandler: self.prompt = self.prompt + f"\nuser location:{self.client_ip_info}" self.dialogue.update_system_message(self.prompt) + """加载MCP工具""" + asyncio.run_coroutine_threadsafe(self.mcp_manager.initialize_servers(), self.loop) + def change_system_prompt(self, prompt): self.prompt = prompt # 找到原来的role==system,替换原来的系统提示 @@ -324,7 +354,6 @@ class ConnectionHandler: functions = None if hasattr(self, 'func_handler'): functions = self.func_handler.get_functions() - response_message = [] processed_chars = 0 # 跟踪已处理的字符位置 @@ -358,6 +387,9 @@ class ConnectionHandler: content_arguments = "" for response in llm_responses: content, tools_call = response + if "content" in response: + content = response["content"] + tools_call = None if content is not None and len(content) > 0: if len(response_message) <= 0 and (content == "```" or "" in content): tool_call_flag = True @@ -436,8 +468,14 @@ class ConnectionHandler: "id": function_id, "arguments": function_arguments } - result = self.func_handler.handle_llm_function_call(self, function_call_data) - self._handle_function_result(result, function_call_data, text_index + 1) + + # 处理MCP工具调用 + if self.mcp_manager.is_mcp_tool(function_name): + result = self._handle_mcp_tool_call(function_call_data) + else: + # 处理系统函数 + result = self.func_handler.handle_llm_function_call(self, function_call_data) + self._handle_function_result(result, function_call_data, text_index+1) # 处理最后剩余的文本 full_text = "".join(response_message) @@ -459,6 +497,42 @@ class ConnectionHandler: return True + def _handle_mcp_tool_call(self, function_call_data): + function_arguments = function_call_data["arguments"] + function_name = function_call_data["name"] + try: + args_dict = function_arguments + if isinstance(function_arguments, str): + try: + args_dict = json.loads(function_arguments) + except json.JSONDecodeError: + self.logger.bind(tag=TAG).error(f"无法解析 function_arguments: {function_arguments}") + return ActionResponse(action=Action.REQLLM, result="参数解析失败", response="") + + tool_result = asyncio.run_coroutine_threadsafe(self.mcp_manager.execute_tool( + function_name, + args_dict + ), self.loop).result() + # meta=None content=[TextContent(type='text', text='北京当前天气:\n温度: 21°C\n天气: 晴\n湿度: 6%\n风向: 西北 风\n风力等级: 5级', annotations=None)] isError=False + content_text = "" + if tool_result is not None and tool_result.content is not None: + for content in tool_result.content: + content_type = content.type + if content_type == "text": + content_text = content.text + elif content_type == "image": + pass + + if len(content_text) > 0: + return ActionResponse(action=Action.REQLLM, result=content_text, response="") + + except Exception as e: + self.logger.bind(tag=TAG).error(f"MCP工具调用错误: {e}") + return ActionResponse(action=Action.REQLLM, result="工具调用出错", response="") + + return ActionResponse(action=Action.REQLLM, result="工具调用出错", response="") + + def _handle_function_result(self, result, function_call_data, text_index): if result.action == Action.RESPONSE: # 直接回复前端 text = result.response @@ -582,6 +656,8 @@ class ConnectionHandler: async def close(self, ws=None): """资源清理方法""" + # 清理MCP资源 + await self.mcp_manager.cleanup_all() # 触发停止事件并清理资源 if self.stop_event: diff --git a/main/xiaozhi-server/core/handle/intentHandler.py b/main/xiaozhi-server/core/handle/intentHandler.py index d8440fc6..5105a4d9 100644 --- a/main/xiaozhi-server/core/handle/intentHandler.py +++ b/main/xiaozhi-server/core/handle/intentHandler.py @@ -4,6 +4,8 @@ import uuid from core.handle.sendAudioHandle import send_stt_message from core.handle.helloHandle import checkWakeupWords from core.utils.util import remove_punctuation_and_length +from core.utils.dialogue import Message +from loguru import logger TAG = __name__ logger = setup_logging() @@ -21,11 +23,11 @@ async def handle_user_intent(conn, text): # 使用支持function calling的聊天方法,不再进行意图分析 return False # 使用LLM进行意图分析 - intent = await analyze_intent_with_llm(conn, text) - if not intent: + intent_result = await analyze_intent_with_llm(conn, text) + if not intent_result: return False # 处理各种意图 - return await process_intent_result(conn, intent, text) + return await process_intent_result(conn, intent_result, text) async def check_direct_exit(conn, text): @@ -40,7 +42,6 @@ async def check_direct_exit(conn, text): return False - async def analyze_intent_with_llm(conn, text): """使用LLM分析用户意图""" if not hasattr(conn, 'intent') or not conn.intent: @@ -51,52 +52,65 @@ async def analyze_intent_with_llm(conn, text): dialogue = conn.dialogue try: intent_result = await conn.intent.detect_intent(conn, dialogue.dialogue, text) - # 尝试解析JSON结果 - try: - intent_data = json.loads(intent_result) - if "intent" in intent_data: - return intent_data["intent"] - except json.JSONDecodeError: - # 如果不是JSON格式,尝试直接获取意图文本 - return intent_result.strip() - + return intent_result except Exception as e: logger.bind(tag=TAG).error(f"意图识别失败: {str(e)}") return None -async def process_intent_result(conn, intent, original_text): +async def process_intent_result(conn, intent_result, original_text): """处理意图识别结果""" - # 处理退出意图 - if "结束聊天" in intent: - logger.bind(tag=TAG).info(f"识别到退出意图: {intent}") - # 如果是明确的离别意图,发送告别语并关闭连接 - await send_stt_message(conn, original_text) - conn.executor.submit(conn.chat_and_close, original_text) - return True + try: + # 尝试将结果解析为JSON + intent_data = json.loads(intent_result) - # 处理播放音乐意图 - if "播放音乐" in intent: - logger.bind(tag=TAG).info(f"识别到音乐播放意图: {intent}") - # 调用play_music函数来播放音乐 - song_name = extract_text_in_brackets(intent) - function_id = str(uuid.uuid4().hex) - function_name = "play_music" - function_arguments = '{ "song_name": "' + song_name + '" }' + # 检查是否有function_call + if "function_call" in intent_data: + # 直接从意图识别获取了function_call + logger.bind(tag=TAG).info(f"检测到function_call格式的意图结果: {intent_data['function_call']['name']}") + function_name = intent_data["function_call"]["name"] + if function_name == "continue_chat": + return False + function_args = None + if "arguments" in intent_data["function_call"]: + function_args = intent_data["function_call"]["arguments"] + # 确保参数是字符串格式的JSON + if isinstance(function_args, dict): + function_args = json.dumps(function_args) - function_call_data = { - "name": function_name, - "id": function_id, - "arguments": function_arguments - } - conn.func_handler.handle_llm_function_call(conn, function_call_data) - return True + function_call_data = { + "name": function_name, + "id": str(uuid.uuid4().hex), + "arguments": function_args + } - # 其他意图处理可以在这里扩展 + await send_stt_message(conn, original_text) - # 默认返回False,表示继续常规聊天流程 - return False + # 使用executor执行函数调用和结果处理 + def process_function_call(): + conn.dialogue.put(Message(role="user", content=original_text)) + result = conn.func_handler.handle_llm_function_call(conn, function_call_data) + if result and function_name != 'play_music': + # 获取当前最新的文本索引 + text = result.response + if text is None: + text = result.result + if text is not None: + text_index = conn.tts_last_text_index + 1 if hasattr(conn, 'tts_last_text_index') else 0 + conn.recode_first_last_text(text, text_index) + future = conn.executor.submit(conn.speak_and_play, text, text_index) + conn.llm_finish_task = True + conn.tts_queue.put(future) + conn.dialogue.put(Message(role="assistant", content=text)) + + # 将函数执行放在线程池中 + conn.executor.submit(process_function_call) + return True + return False + except json.JSONDecodeError as e: + logger.bind(tag=TAG).error(f"处理意图结果时出错: {e}") + return False def extract_text_in_brackets(s): @@ -112,4 +126,4 @@ def extract_text_in_brackets(s): if left_bracket_index != -1 and right_bracket_index != -1 and left_bracket_index < right_bracket_index: return s[left_bracket_index + 1:right_bracket_index] else: - return "" \ No newline at end of file + return "" diff --git a/main/xiaozhi-server/core/mcp/MCPClient.py b/main/xiaozhi-server/core/mcp/MCPClient.py new file mode 100644 index 00000000..103ac645 --- /dev/null +++ b/main/xiaozhi-server/core/mcp/MCPClient.py @@ -0,0 +1,86 @@ +from datetime import timedelta +from typing import Optional +from contextlib import AsyncExitStack +import os, shutil +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client + +from config.logger import setup_logging + +TAG = __name__ + +class MCPClient: + def __init__(self, config): + # Initialize session and client objects + self.session: Optional[ClientSession] = None + self.exit_stack = AsyncExitStack() + self.logger = setup_logging() + self.config = config + self.tolls = [] + + async def initialize(self): + args = self.config.get("args", []) + + command = ( + shutil.which("npx") + if self.config["command"] == "npx" + else self.config["command"] + ) + + env={**os.environ} + if self.config.get("env"): + env.update(self.config["env"]) + + server_params = StdioServerParameters( + command=command, + args=args, + env=env + ) + + stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) + self.stdio, self.write = stdio_transport + time_out_delta = timedelta(seconds=15) + self.session = await self.exit_stack.enter_async_context(ClientSession(read_stream=self.stdio, write_stream=self.write, read_timeout_seconds=time_out_delta)) + + await self.session.initialize() + + # List available tools + response = await self.session.list_tools() + tools = response.tools + self.tools = tools + self.logger.bind(tag=TAG).info(f"Connected to server with tools:{[tool.name for tool in tools]}") + + def has_tool(self, tool_name): + return any(tool.name == tool_name for tool in self.tools) + + def get_available_tools(self): + available_tools = [{"type": "function", "function":{ + "name": tool.name, + "description": tool.description, + "parameters": tool.inputSchema + } } for tool in self.tools] + + return available_tools + + async def call_tool(self, tool_name: str, tool_args: dict): + self.logger.bind(tag=TAG).info(f"MCPClient Calling tool {tool_name} with args: {tool_args}") + try: + response = await self.session.call_tool(tool_name, tool_args) + except Exception as e: + self.logger.bind(tag=TAG).error(f"Error calling tool {tool_name}: {e}") + from types import SimpleNamespace + error_content = SimpleNamespace( + type='text', + text=f"Error calling tool {tool_name}: {e}" + ) + error_response = SimpleNamespace( + content=[error_content], + isError=True + ) + return error_response + self.logger.bind(tag=TAG).info(f"MCPClient Response from tool {tool_name}: {response}") + return response + + async def cleanup(self): + """Clean up resources""" + await self.exit_stack.aclose() \ No newline at end of file diff --git a/main/xiaozhi-server/core/mcp/manager.py b/main/xiaozhi-server/core/mcp/manager.py new file mode 100644 index 00000000..28efd87d --- /dev/null +++ b/main/xiaozhi-server/core/mcp/manager.py @@ -0,0 +1,110 @@ +"""MCP服务管理器""" +import os, json +from typing import Dict, Any, List +from .MCPClient import MCPClient +from config.logger import setup_logging +from core.utils.util import get_project_dir +from plugins_func.register import register_function, ActionResponse, Action, ToolType + +TAG = __name__ + +class MCPManager: + """管理多个MCP服务的集中管理器""" + + def __init__(self,conn) -> None: + """ + 初始化MCP管理器 + """ + self.conn = conn + self.logger = setup_logging() + self.config_path = get_project_dir() + 'data/.mcp_server_settings.json' + if os.path.exists(self.config_path) == False: + self.config_path = "" + self.logger.bind(tag=TAG).warning(f"请检查mcp服务配置文件:data/.mcp_server_settings.json") + self.client: Dict[str, MCPClient] = {} + self.tools = [] + + def load_config(self) -> Dict[str, Any]: + """加载MCP服务配置 + Returns: + Dict[str, Any]: 服务配置字典 + """ + if len(self.config_path) == 0: + return {} + + try: + with open(self.config_path, 'r', encoding='utf-8') as f: + config = json.load(f) + return config.get('mcpServers', {}) + except Exception as e: + self.logger.bind(tag=TAG).error(f"Error loading MCP config from {self.config_path}: {e}") + return {} + + async def initialize_servers(self) -> None: + """初始化所有MCP服务""" + config = self.load_config() + for name, srv_config in config.items(): + if not srv_config.get("command"): + self.logger.bind(tag=TAG).warning(f"Skipping server {name}: command not specified") + continue + + try: + client = MCPClient(srv_config) + await client.initialize() + self.client[name] = client + self.logger.bind(tag=TAG).info(f"Initialized MCP client: {name}") + client_tools = client.get_available_tools() + self.tools.extend(client_tools) + for tool in client_tools: + func_name = "mcp_"+tool["function"]["name"] + register_function(func_name, tool, ToolType.MCP_CLIENT)(self.execute_tool) + self.conn.func_handler.function_registry.register_function(func_name) + + except Exception as e: + self.logger.bind(tag=TAG).error(f"Failed to initialize MCP server {name}: {e}") + self.conn.func_handler.upload_functions_desc() + + def get_all_tools(self) -> List[Dict[str, Any]]: + """获取所有服务的工具function定义 + Returns: + List[Dict[str, Any]]: 所有工具的function定义列表 + """ + return self.tools + + def is_mcp_tool(self, tool_name: str) -> bool: + """检查是否是MCP工具 + Args: + tool_name: 工具名称 + Returns: + bool: 是否是MCP工具 + """ + for tool in self.tools: + if tool.get("function") != None and tool["function"].get("name") == tool_name: + return True + return False + + async def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any: + """执行工具调用 + Args: + tool_name: 工具名称 + arguments: 工具参数 + Returns: + Any: 工具执行结果 + Raises: + ValueError: 工具未找到时抛出 + """ + self.logger.bind(tag=TAG).info(f"Executing tool {tool_name} with arguments: {arguments}") + for client in self.client.values(): + if client.has_tool(tool_name): + return await client.call_tool(tool_name, arguments) + + raise ValueError(f"Tool {tool_name} not found in any MCP server") + + async def cleanup_all(self) -> None: + for name, client in self.client.items(): + try: + await client.cleanup() + self.logger.bind(tag=TAG).info(f"Cleaned up MCP client: {name}") + except Exception as e: + self.logger.bind(tag=TAG).error(f"Error cleaning up MCP client {name}: {e}") + self.client.clear() diff --git a/main/xiaozhi-server/core/providers/intent/base.py b/main/xiaozhi-server/core/providers/intent/base.py index a691d6cb..2a39f1ae 100644 --- a/main/xiaozhi-server/core/providers/intent/base.py +++ b/main/xiaozhi-server/core/providers/intent/base.py @@ -10,14 +10,21 @@ class IntentProviderBase(ABC): def __init__(self, config): self.config = config self.intent_options = config.get("intent_options", { + "handle_exit_intent": "结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候", + "play_music": "播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图", + "get_weather": "查询天气, 用户希望查询某个地点的天气情况", + "get_news": "查询新闻, 用户希望查询最新新闻或特定类型的新闻", + "get_lunar": "用于获取今天的阴历/农历和黄历信息", + "get_time": "获取今天日期或者当前时间信息", "continue_chat": "继续聊天", - "end_chat": "结束聊天", - "play_music": "播放音乐" }) def set_llm(self, llm): self.llm = llm - logger.bind(tag=TAG).debug("Set LLM for intent provider") + # 获取模型名称和类型信息 + model_name = getattr(llm, 'model_name', str(llm.__class__.__name__)) + # 记录更详细的日志 + logger.bind(tag=TAG).info(f"意图识别设置LLM: {model_name}") @abstractmethod async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str: @@ -30,5 +37,6 @@ class IntentProviderBase(ABC): - "继续聊天" - "结束聊天" - "播放音乐 歌名" 或 "随机播放音乐" + - "查询天气 地点名" 或 "查询天气 [当前位置]" """ pass diff --git a/main/xiaozhi-server/core/providers/intent/function_call/function_call.py b/main/xiaozhi-server/core/providers/intent/function_call/function_call.py new file mode 100644 index 00000000..ff33e58f --- /dev/null +++ b/main/xiaozhi-server/core/providers/intent/function_call/function_call.py @@ -0,0 +1,20 @@ +from ..base import IntentProviderBase +from typing import List, Dict +from config.logger import setup_logging + +TAG = __name__ +logger = setup_logging() + + +class IntentProvider(IntentProviderBase): + async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str: + """ + 默认的意图识别实现,始终返回继续聊天 + Args: + dialogue_history: 对话历史记录列表 + text: 本次对话记录 + Returns: + 固定返回"继续聊天" + """ + logger.bind(tag=TAG).debug("Using functionCallProvider, always returning continue chat") + return self.intent_options["continue_chat"] diff --git a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py index 9d5d2507..1afd835e 100644 --- a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py +++ b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py @@ -3,6 +3,9 @@ from ..base import IntentProviderBase from plugins_func.functions.play_music import initialize_music_handler from config.logger import setup_logging import re +import json +import hashlib +import time TAG = __name__ logger = setup_logging() @@ -13,6 +16,10 @@ class IntentProvider(IntentProviderBase): super().__init__(config) self.llm = None self.promot = self.get_intent_system_prompt() + # 添加缓存管理 + self.intent_cache = {} # 缓存意图识别结果 + self.cache_expiry = 600 # 缓存有效期10分钟 + self.cache_max_size = 100 # 最多缓存100个意图 def get_intent_system_prompt(self) -> str: """ @@ -22,62 +29,119 @@ class IntentProvider(IntentProviderBase): """ intent_list = [] - """ - "continue_chat": "1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等", - "end_chat": "2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候", - "play_music": "3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图" - """ - for key, value in self.intent_options.items(): - if key == "play_music": - intent_list.append("3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图") - elif key == "end_chat": - intent_list.append("2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候") - elif key == "continue_chat": - intent_list.append("1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等") - else: - intent_list.append(value) - - # "如果是唱歌、听歌、播放音乐,请指定歌名,格式为'播放音乐 [识别出的歌名]'。\n" - # "如果听不出具体歌名,可以返回'随机播放音乐'。\n" - # "只需要返回意图结果的json,不要解释。" - # "返回格式如下:\n" prompt = ( - "你是一个意图识别助手。你需要根据和用户的对话记录,重点分析用户的最后一句话,判断用户意图属于以下哪一类(使用标志):\n" + "你是一个意图识别助手。请分析用户的最后一句话,判断用户意图属于以下哪一类:\n" "" f"{', '.join(intent_list)}" "\n" - "你需要按照以下的步骤处理用户的对话" - "1. 思考出对话的意图是哪一类的" - "2. 属于1和2的意图, 直接返回,返回格式如下:\n" - "{intent: '用户意图'}\n" - "3. 属于3的意图,则继续分析用户希望播放的音乐\n" - "4. 如果无法识别出具体歌名,可以返回'随机播放音乐'\n" - "{intent: '播放音乐 [获取的音乐名字]'}\n" - "下面是几个处理的示例(思考的内容不返回, 只返回json部分, 无额外的内容)\n" - "```" + "处理步骤:" + "1. 思考意图类型,生成function_call格式" + "\n\n" + "返回格式示例:\n" + "1. 播放音乐意图: {\"function_call\": {\"name\": \"play_music\", \"arguments\": {\"song_name\": \"音乐名称\"}}}\n" + "2. 查询天气意图: {\"function_call\": {\"name\": \"get_weather\", \"arguments\": {\"location\": \"地点名称\", \"lang\": \"zh_CN\"}}}\n" + "3. 查询新闻意图: {\"function_call\": {\"name\": \"get_news\", \"arguments\": {\"category\": \"新闻类别\", \"detail\": false, \"lang\": \"zh_CN\"}}}\n" + "4. 结束对话意图: {\"function_call\": {\"name\": \"handle_exit_intent\", \"arguments\": {\"say_goodbye\": \"goodbye\"}}}\n" + "5. 获取当天日期时间: {\"function_call\": {\"name\": \"get_time\"}}\n" + "6. 获取当前黄历意图: {\"function_call\": {\"name\": \"get_lunar\"}}\n" + "7. 继续聊天意图: {\"function_call\": {\"name\": \"continue_chat\"}}\n" + "\n" + "注意:\n" + "- 播放音乐:无歌名时,song_name设为\"random\"\n" + "- 查询天气:无地点时,location设为null\n" + "- 查询新闻:无类别时,category设为null;查询详情时,detail设为true\n" + "- 如果没有明显的意图,应按照继续聊天意图处理\n" + "- 只返回纯JSON,不要任何其他内容\n" + "\n" + "示例分析:\n" + "```\n" + "用户: 你好小智\n" + "返回: {\"function_call\": {\"name\": \"continue_chat\"}}\n" + "```\n" + "```\n" "用户: 你今天怎么样?\n" - "思考(不返回): 用户发来的数据是一个问候语,属于继续聊天的意图, 是种类1, 种类1的需求是直接返回\n" - "返回结果: {intent: '继续聊天'}\n" - "```" - "用户: 我今天有点累了, 我们明天再聊吧\n" - "思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n" - "返回结果: {intent: '结束聊天'}\n" - "```" - "用户: 我今天有点累了, 我们明天再聊吧\n" - "思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n" - "返回结果: {intent: '结束聊天'}\n" - "```" - "用户: 你可以播放一首中秋月给我听吗\n" - "思考(不返回): 用户表达了想听音乐的续签,属于播放音乐的意图, 是种类3, 种类3的需求需要继续判断播放的音乐, 这里用户希望的歌曲名明确给出是中秋月\n" - "返回结果: {intent: '播放音乐 [中秋月]'}\n" - "```" - "你现在可以使用的音乐的名称如下(使用标志):\n" + "返回: {\"function_call\": {\"name\": \"continue_chat\"}}\n" + "```\n" + "```\n" + "用户: 现在是几号了?现在几点了?\n" + "返回: {\"function_call\": {\"name\": \"get_time\"}}\n" + "```\n" + "```\n" + "用户: 今天农历是多少?\n" + "返回: {\"function_call\": {\"name\": \"get_lunar\"}}\n" + "```\n" + "```\n" + "用户: 我们明天再聊吧\n" + "返回: {\"function_call\": {\"name\": \"handle_exit_intent\"}}\n" + "```\n" + "```\n" + "用户: 播放中秋月\n" + "返回: {\"function_call\": {\"name\": \"play_music\", \"arguments\": {\"song_name\": \"中秋月\"}}}\n" + "```\n" + "```\n" + "用户: 北京天气怎么样\n" + "返回: {\"function_call\": {\"name\": \"get_weather\", \"arguments\": {\"location\": \"北京\", \"lang\": \"zh_CN\"}}}\n" + "```\n" + "```\n" + "用户: 今天天气怎么样\n" + "返回: {\"function_call\": {\"name\": \"get_weather\", \"arguments\": {\"location\": null, \"lang\": \"zh_CN\"}}}\n" + "```\n" + "```\n" + "用户: 播报财经新闻\n" + "返回: {\"function_call\": {\"name\": \"get_news\", \"arguments\": {\"category\": \"财经\", \"detail\": false, \"lang\": \"zh_CN\"}}}\n" + "```\n" + "```\n" + "用户: 有什么最新新闻\n" + "返回: {\"function_call\": {\"name\": \"get_news\", \"arguments\": {\"category\": null, \"detail\": false, \"lang\": \"zh_CN\"}}}\n" + "```\n" + "```\n" + "用户: 详细介绍一下这条新闻\n" + "返回: {\"function_call\": {\"name\": \"get_news\", \"arguments\": {\"detail\": true, \"lang\": \"zh_CN\"}}}\n" + "```\n" + "可用的音乐名称:\n" ) return prompt + + def clean_cache(self): + """清理过期缓存""" + now = time.time() + # 找出过期键 + expired_keys = [k for k, v in self.intent_cache.items() if now - v['timestamp'] > self.cache_expiry] + for key in expired_keys: + del self.intent_cache[key] + + # 如果缓存太大,移除最旧的条目 + if len(self.intent_cache) > self.cache_max_size: + # 按时间戳排序并保留最新的条目 + sorted_items = sorted(self.intent_cache.items(), key=lambda x: x[1]['timestamp']) + for key, _ in sorted_items[:len(sorted_items) - self.cache_max_size]: + del self.intent_cache[key] async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str: if not self.llm: raise ValueError("LLM provider not set") + + # 记录整体开始时间 + total_start_time = time.time() + + # 打印使用的模型信息 + model_info = getattr(self.llm, 'model_name', str(self.llm.__class__.__name__)) + logger.bind(tag=TAG).info(f"使用意图识别模型: {model_info}") + + # 计算缓存键 + cache_key = hashlib.md5(text.encode()).hexdigest() + + # 检查缓存 + if cache_key in self.intent_cache: + cache_entry = self.intent_cache[cache_key] + # 检查缓存是否过期 + if time.time() - cache_entry['timestamp'] <= self.cache_expiry: + cache_time = time.time() - total_start_time + logger.bind(tag=TAG).info(f"使用缓存的意图: {cache_key} -> {cache_entry['intent']}, 耗时: {cache_time:.4f}秒") + return cache_entry['intent'] + + # 清理缓存 + self.clean_cache() # 构建用户最后一句话的提示 msgStr = "" @@ -94,18 +158,78 @@ class IntentProvider(IntentProviderBase): music_file_names = music_config["music_file_names"] prompt_music = f"{self.promot}\n{music_file_names}\n" logger.bind(tag=TAG).debug(f"User prompt: {prompt_music}") + + # 记录预处理完成时间 + preprocess_time = time.time() - total_start_time + logger.bind(tag=TAG).debug(f"意图识别预处理耗时: {preprocess_time:.4f}秒") + # 使用LLM进行意图识别 + llm_start_time = time.time() + logger.bind(tag=TAG).info(f"开始LLM意图识别调用, 模型: {model_info}") + intent = self.llm.response_no_stream( system_prompt=prompt_music, user_prompt=user_prompt ) - # 使用正则表达式提取大括号中的内容 - # 使用正则表达式提取 {} 中的内容 - match = re.search(r'\{.*?\}', intent) + + # 记录LLM调用完成时间 + llm_time = time.time() - llm_start_time + logger.bind(tag=TAG).info(f"LLM意图识别完成, 模型: {model_info}, 调用耗时: {llm_time:.4f}秒") + + # 记录后处理开始时间 + postprocess_start_time = time.time() + + # 清理和解析响应 + intent = intent.strip() + # 尝试提取JSON部分 + match = re.search(r'\{.*\}', intent, re.DOTALL) if match: - result = match.group(0) - intent = result - else: - intent = "{intent: '继续聊天'}" - logger.bind(tag=TAG).info(f"Detected intent: {intent}") - return intent.strip() + intent = match.group(0) + + # 记录总处理时间 + total_time = time.time() - total_start_time + logger.bind(tag=TAG).info(f"【意图识别性能】模型: {model_info}, 总耗时: {total_time:.4f}秒, LLM调用: {llm_time:.4f}秒, 查询: '{text[:20]}...'") + + # 尝试解析为JSON + try: + intent_data = json.loads(intent) + # 如果包含function_call,则格式化为适合处理的格式 + if "function_call" in intent_data: + function_data = intent_data["function_call"] + function_name = function_data.get("name") + function_args = function_data.get("arguments", {}) + + # 记录识别到的function call + logger.bind(tag=TAG).info(f"识别到function call: {function_name}, 参数: {function_args}") + + # 添加到缓存 + self.intent_cache[cache_key] = { + 'intent': intent, + 'timestamp': time.time() + } + + # 后处理时间 + postprocess_time = time.time() - postprocess_start_time + logger.bind(tag=TAG).debug(f"意图后处理耗时: {postprocess_time:.4f}秒") + + # 确保返回完全序列化的JSON字符串 + return intent + else: + # 添加到缓存 + self.intent_cache[cache_key] = { + 'intent': intent, + 'timestamp': time.time() + } + + # 后处理时间 + postprocess_time = time.time() - postprocess_start_time + logger.bind(tag=TAG).debug(f"意图后处理耗时: {postprocess_time:.4f}秒") + + # 返回普通意图 + return intent + except json.JSONDecodeError: + # 后处理时间 + postprocess_time = time.time() - postprocess_start_time + logger.bind(tag=TAG).error(f"无法解析意图JSON: {intent}, 后处理耗时: {postprocess_time:.4f}秒") + # 如果解析失败,默认返回继续聊天意图 + return "{\"intent\": \"继续聊天\"}" diff --git a/main/xiaozhi-server/core/providers/llm/xinference/xinference.py b/main/xiaozhi-server/core/providers/llm/xinference/xinference.py new file mode 100644 index 00000000..b90b0418 --- /dev/null +++ b/main/xiaozhi-server/core/providers/llm/xinference/xinference.py @@ -0,0 +1,85 @@ +from config.logger import setup_logging +from openai import OpenAI +import json +from core.providers.llm.base import LLMProviderBase + +TAG = __name__ +logger = setup_logging() + + +class LLMProvider(LLMProviderBase): + def __init__(self, config): + self.model_name = config.get("model_name") + self.base_url = config.get("base_url", "http://localhost:9997") + # Initialize OpenAI client with Xinference base URL + # 如果没有v1,增加v1 + if not self.base_url.endswith("/v1"): + self.base_url = f"{self.base_url}/v1" + + logger.bind(tag=TAG).info(f"Initializing Xinference LLM provider with model: {self.model_name}, base_url: {self.base_url}") + + try: + self.client = OpenAI( + base_url=self.base_url, + api_key="xinference" # Xinference has a similar setup to Ollama where it doesn't need an actual key + ) + logger.bind(tag=TAG).info("Xinference client initialized successfully") + except Exception as e: + logger.bind(tag=TAG).error(f"Error initializing Xinference client: {e}") + raise + + def response(self, session_id, dialogue): + try: + logger.bind(tag=TAG).debug(f"Sending request to Xinference with model: {self.model_name}, dialogue length: {len(dialogue)}") + responses = self.client.chat.completions.create( + model=self.model_name, + messages=dialogue, + stream=True + ) + is_active=True + for chunk in responses: + try: + delta = chunk.choices[0].delta if getattr(chunk, 'choices', None) else None + content = delta.content if hasattr(delta, 'content') else '' + if content: + if '' in content: + is_active = False + content = content.split('')[0] + if '' in content: + is_active = True + content = content.split('')[-1] + if is_active: + yield content + except Exception as e: + logger.bind(tag=TAG).error(f"Error processing chunk: {e}") + + except Exception as e: + logger.bind(tag=TAG).error(f"Error in Xinference response generation: {e}") + yield "【Xinference服务响应异常】" + + def response_with_functions(self, session_id, dialogue, functions=None): + try: + logger.bind(tag=TAG).debug(f"Sending function call request to Xinference with model: {self.model_name}, dialogue length: {len(dialogue)}") + if functions: + logger.bind(tag=TAG).debug(f"Function calls enabled with: {[f.get('function', {}).get('name') for f in functions]}") + + stream = self.client.chat.completions.create( + model=self.model_name, + messages=dialogue, + stream=True, + tools=functions, + ) + + for chunk in stream: + delta = chunk.choices[0].delta + content = delta.content + tool_calls = delta.tool_calls + + if content: + yield content, tool_calls + elif tool_calls: + yield None, tool_calls + + except Exception as e: + logger.bind(tag=TAG).error(f"Error in Xinference function call: {e}") + yield {"type": "content", "content": f"【Xinference服务响应异常: {str(e)}】"} diff --git a/main/xiaozhi-server/core/utils/util.py b/main/xiaozhi-server/core/utils/util.py index 51841baf..b361bff3 100644 --- a/main/xiaozhi-server/core/utils/util.py +++ b/main/xiaozhi-server/core/utils/util.py @@ -67,13 +67,11 @@ def is_private_ip(ip_addr): def get_ip_info(ip_addr): try: - base_url = "https://freeipapi.com/api/json" - url = base_url if is_private_ip(ip_addr) else f"{base_url}/{ip_addr}" - + url = "https://whois.pconline.com.cn/ipJson.jsp?json=true" resp = requests.get(url).json() ip_info = { - "city": resp.get("cityName") + "city": resp.get("city") } return ip_info except Exception as e: diff --git a/main/xiaozhi-server/mcp_server_settings.json b/main/xiaozhi-server/mcp_server_settings.json new file mode 100644 index 00000000..a38bbdfb --- /dev/null +++ b/main/xiaozhi-server/mcp_server_settings.json @@ -0,0 +1,31 @@ +{ + "des": [ + "在data目录下创建.mcp_server_settings.json文件,可以选择下面的MCP服务,也可以自行添加新的MCP服务。", + "后面不断测试补充好用的mcp服务,欢迎大家一起补充。", + "记得删除注释行,des属性仅为说明,不会被解析。", + "des和link属性,仅为说明安装方式,方便大家查看原始链接,不是必须项。" + ], + "mcpServers": { + "filesystem": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem", + "/Users/username/Desktop", + "/path/to/other/allowed/dir" + ], + "link":"https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem" + }, + "playwright": { + "command": "npx", + "args": ["-y", "@executeautomation/playwright-mcp-server"], + "des" : "run 'npx playwright install' first", + "link": "https://github.com/executeautomation/mcp-playwright" + }, + "windows-cli": { + "command": "npx", + "args": ["-y", "@simonb97/server-win-cli"], + "link": "https://github.com/SimonB97/win-cli-mcp-server" + } + } +} diff --git a/main/xiaozhi-server/plugins_func/functions/get_news.py b/main/xiaozhi-server/plugins_func/functions/get_news.py index 43f8ea48..85b203dc 100644 --- a/main/xiaozhi-server/plugins_func/functions/get_news.py +++ b/main/xiaozhi-server/plugins_func/functions/get_news.py @@ -45,10 +45,10 @@ def fetch_news_from_rss(rss_url): try: response = requests.get(rss_url) response.raise_for_status() - + # 解析XML root = ET.fromstring(response.content) - + # 查找所有item元素(新闻条目) news_items = [] for item in root.findall('.//item'): @@ -56,14 +56,14 @@ def fetch_news_from_rss(rss_url): link = item.find('link').text if item.find('link') is not None else "#" description = item.find('description').text if item.find('description') is not None else "无描述" pubDate = item.find('pubDate').text if item.find('pubDate') is not None else "未知时间" - + news_items.append({ 'title': title, 'link': link, 'description': description, 'pubDate': pubDate }) - + return news_items except Exception as e: logger.bind(tag=TAG).error(f"获取RSS新闻失败: {e}") @@ -75,9 +75,9 @@ def fetch_news_detail(url): try: response = requests.get(url) response.raise_for_status() - + soup = BeautifulSoup(response.content, 'html.parser') - + # 尝试提取正文内容 (这里的选择器需要根据实际网站结构调整) content_div = soup.select_one('.content_desc, .content, article, .article-content') if content_div: @@ -98,7 +98,7 @@ def map_category(category_text): """将用户输入的中文类别映射到配置文件中的类别键""" if not category_text: return None - + # 类别映射字典,目前支持社会、国际、财经新闻,如需更多类型,参见配置文件 category_map = { # 社会新闻 @@ -113,10 +113,10 @@ def map_category(category_text): "金融": "finance", "经济": "finance" } - + # 转换为小写并去除空格 normalized_category = category_text.lower().strip() - + # 返回映射结果,如果没有匹配项则返回原始输入 return category_map.get(normalized_category, category_text) @@ -129,21 +129,22 @@ def get_news(conn, category: str = None, detail: bool = False, lang: str = "zh_C if detail: if not hasattr(conn, 'last_news_link') or not conn.last_news_link or 'link' not in conn.last_news_link: return ActionResponse(Action.REQLLM, "抱歉,没有找到最近查询的新闻,请先获取一条新闻。", None) - + link = conn.last_news_link.get('link') title = conn.last_news_link.get('title', '未知标题') - + if link == '#': return ActionResponse(Action.REQLLM, "抱歉,该新闻没有可用的链接获取详细内容。", None) - + logger.bind(tag=TAG).debug(f"获取新闻详情: {title}, URL={link}") - + # 获取新闻详情 detail_content = fetch_news_detail(link) - + if not detail_content or detail_content == "无法获取详细内容": - return ActionResponse(Action.REQLLM, f"抱歉,无法获取《{title}》的详细内容,可能是链接已失效或网站结构发生变化。", None) - + return ActionResponse(Action.REQLLM, + f"抱歉,无法获取《{title}》的详细内容,可能是链接已失效或网站结构发生变化。", None) + # 构建详情报告 detail_report = ( f"根据下列数据,用{lang}回应用户的新闻详情查询请求:\n\n" @@ -152,33 +153,33 @@ def get_news(conn, category: str = None, detail: bool = False, lang: str = "zh_C f"(请对上述新闻内容进行总结,提取关键信息,以自然、流畅的方式向用户播报," f"不要提及这是总结,就像是在讲述一个完整的新闻故事)" ) - + return ActionResponse(Action.REQLLM, detail_report, None) - + # 否则,获取新闻列表并随机选择一条 # 从配置中获取RSS URL rss_config = conn.config["plugins"]["get_news"] default_rss_url = rss_config.get("default_rss_url", "https://www.chinanews.com.cn/rss/society.xml") - + # 将用户输入的类别映射到配置中的类别键 mapped_category = map_category(category) - + # 如果提供了类别,尝试从配置中获取对应的URL rss_url = default_rss_url if mapped_category and mapped_category in rss_config.get("category_urls", {}): rss_url = rss_config["category_urls"][mapped_category] - + logger.bind(tag=TAG).info(f"获取新闻: 原始类别={category}, 映射类别={mapped_category}, URL={rss_url}") - + # 获取新闻列表 news_items = fetch_news_from_rss(rss_url) - + if not news_items: return ActionResponse(Action.REQLLM, "抱歉,未能获取到新闻信息,请稍后再试。", None) - + # 随机选择一条新闻 selected_news = random.choice(news_items) - + # 保存当前新闻链接到连接对象,以便后续查询详情 if not hasattr(conn, 'last_news_link'): conn.last_news_link = {} @@ -186,7 +187,7 @@ def get_news(conn, category: str = None, detail: bool = False, lang: str = "zh_C 'link': selected_news.get('link', '#'), 'title': selected_news.get('title', '未知标题') } - + # 构建新闻报告 news_report = ( f"根据下列数据,用{lang}回应用户的新闻查询请求:\n\n" @@ -197,9 +198,9 @@ def get_news(conn, category: str = None, detail: bool = False, lang: str = "zh_C f"直接读出新闻即可,不需要额外多余的内容。" f"如果用户询问更多详情,告知用户可以说'请详细介绍这条新闻'获取更多内容)" ) - + return ActionResponse(Action.REQLLM, news_report, None) - + except Exception as e: logger.bind(tag=TAG).error(f"获取新闻出错: {e}") return ActionResponse(Action.REQLLM, "抱歉,获取新闻时发生错误,请稍后再试。", None) \ No newline at end of file diff --git a/main/xiaozhi-server/plugins_func/functions/get_weather.py b/main/xiaozhi-server/plugins_func/functions/get_weather.py index 8cdd6ea1..060274d9 100644 --- a/main/xiaozhi-server/plugins_func/functions/get_weather.py +++ b/main/xiaozhi-server/plugins_func/functions/get_weather.py @@ -52,7 +52,7 @@ WEATHER_CODE_MAP = { "405": "雨雪天气", "406": "阵雨夹雪", "407": "阵雪", "408": "小到中雪", "409": "中到大雪", "410": "大到暴雪", "456": "阵雨夹雪", "457": "阵雪", "499": "雪", "500": "薄雾", "501": "雾", "502": "霾", "503": "扬沙", "504": "浮尘", - "507": "沙尘暴", "508": "强沙尘暴", + "507": "沙尘暴", "508": "强沙尘暴", "509": "浓雾", "510": "强浓雾", "511": "中度霾", "512": "重度霾", "513": "严重霾", "514": "大雾", "515": "特强浓雾", "900": "热", "901": "冷", "999": "未知" } @@ -120,4 +120,4 @@ def get_weather(conn, location: str = None, lang: str = "zh_CN"): "参数为0的值不需要报告给用户,每次都报告体感温度,根据语境选择合适的参数内容告知用户,并对参数给出相应评价)" ) - return ActionResponse(Action.REQLLM, weather_report, None) + return ActionResponse(Action.REQLLM, weather_report, None) \ No newline at end of file diff --git a/main/xiaozhi-server/plugins_func/functions/handle_device.py b/main/xiaozhi-server/plugins_func/functions/handle_device.py index d564562b..b215a486 100644 --- a/main/xiaozhi-server/plugins_func/functions/handle_device.py +++ b/main/xiaozhi-server/plugins_func/functions/handle_device.py @@ -69,7 +69,7 @@ handle_device_function_desc = { "description": "动作名称,可选值:get(获取),set(设置),raise(提高),lower(降低)" }, "value": { - "type": "int", + "type": "integer", "description": "值大小,可选值:0-100之间的整数" } }, diff --git a/main/xiaozhi-server/plugins_func/functions/hass_set_state.py b/main/xiaozhi-server/plugins_func/functions/hass_set_state.py index 5e2b91a2..def2d412 100644 --- a/main/xiaozhi-server/plugins_func/functions/hass_set_state.py +++ b/main/xiaozhi-server/plugins_func/functions/hass_set_state.py @@ -23,7 +23,7 @@ hass_set_state_function_desc = { "description": "需要操作的动作,打开设备:turn_on,关闭设备:turn_off,增加亮度:brightness_up,降低亮度:brightness_down,设置亮度:brightness_value,增加>音量:,volume_up降低音量:volume_down,设置音量:volume_set,设备暂停:pause,设备继续:continue,静音/取消静音:volume_mute" }, "input": { - "type": "int", + "type": "integer", "description": "只有在设置音量,设置亮度时候才需要,有效值为1-100,对应音量和亮度的1%-100%" }, "is_muted": { diff --git a/main/xiaozhi-server/plugins_func/register.py b/main/xiaozhi-server/plugins_func/register.py index ccb03b44..fd990760 100644 --- a/main/xiaozhi-server/plugins_func/register.py +++ b/main/xiaozhi-server/plugins_func/register.py @@ -12,6 +12,7 @@ class ToolType(Enum): CHANGE_SYS_PROMPT = (3, "修改系统提示词,切换角色性格或职责") SYSTEM_CTL = (4, "系统控制,影响正常的对话流程,如退出、播放音乐等,需要传递conn参数") IOT_CTL = (5, "IOT设备控制,需要传递conn参数") + MCP_CLIENT = (6, "MCP客户端") def __init__(self, code, message): self.code = code diff --git a/main/xiaozhi-server/requirements.txt b/main/xiaozhi-server/requirements.txt index 51433cb9..164d594d 100755 --- a/main/xiaozhi-server/requirements.txt +++ b/main/xiaozhi-server/requirements.txt @@ -22,4 +22,6 @@ mem0ai==0.1.62 bs4==0.0.2 modelscope==1.23.2 sherpa_onnx==1.11.0 -cnlunar==0.2.0 \ No newline at end of file +mcp==1.4.1 + +cnlunar==0.2.0