Merge branch 'test-manager-api' into manager-api-newdevice

This commit is contained in:
hrz
2025-03-29 18:09:51 +08:00
committed by GitHub
155 changed files with 2654 additions and 958 deletions
+1
View File
@@ -156,3 +156,4 @@ main/manager-web/node_modules
main/xiaozhi-server/models/SenseVoiceSmall/model.pt main/xiaozhi-server/models/SenseVoiceSmall/model.pt
main/xiaozhi-server/models/sherpa-onnx* main/xiaozhi-server/models/sherpa-onnx*
my_wakeup_words.mp3 my_wakeup_words.mp3
main/manager-api/.vscode
+25
View File
@@ -11,6 +11,7 @@ manager-api 该项目基于SpringBoot框架开发。
JDK 21 JDK 21
Maven 3.8+ Maven 3.8+
MySQL 8.0+ MySQL 8.0+
Redis 5.0+
Vue 3.x Vue 3.x
# 创建数据库 # 创建数据库
@@ -43,6 +44,30 @@ spring:
password: 123456 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项目,启动方式为: 本项目为SpringBoot项目,启动方式为:
-1
View File
@@ -27,7 +27,6 @@
<shiro.version>2.0.2</shiro.version> <shiro.version>2.0.2</shiro.version>
<captcha.version>1.6.2</captcha.version> <captcha.version>1.6.2</captcha.version>
<guava.version>33.0.0-jre</guava.version> <guava.version>33.0.0-jre</guava.version>
<easyexcel.version>3.3.2</easyexcel.version>
<liquibase-core.version>4.20.0</liquibase-core.version> <liquibase-core.version>4.20.0</liquibase-core.version>
</properties> </properties>
@@ -1,6 +1,10 @@
package xiaozhi.common.annotation; package xiaozhi.common.annotation;
import java.lang.annotation.*; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** /**
* 数据过滤注解 * 数据过滤注解
@@ -1,6 +1,10 @@
package xiaozhi.common.annotation; package xiaozhi.common.annotation;
import java.lang.annotation.*; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** /**
* 操作日志注解 * 操作日志注解
@@ -1,14 +1,15 @@
package xiaozhi.common.aspect; 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.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
/** /**
* Redis切面处理类 * Redis切面处理类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -19,7 +20,7 @@ import org.springframework.stereotype.Component;
@Component @Component
public class RedisAspect { public class RedisAspect {
/** /**
* 是否开启redis缓存 true开启 false关闭 * 是否开启redis缓存 true开启 false关闭
*/ */
@Value("${renren.redis.open}") @Value("${renren.redis.open}")
private boolean open; private boolean open;
@@ -1,12 +1,14 @@
package xiaozhi.common.config; 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.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import xiaozhi.common.interceptor.DataFilterInterceptor; import xiaozhi.common.interceptor.DataFilterInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** /**
* mybatis-plus配置 * mybatis-plus配置
@@ -1,11 +1,12 @@
package xiaozhi.common.config; 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.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
/** /**
* Swagger配置 * Swagger配置
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -16,7 +17,7 @@ public class SwaggerConfig {
@Bean @Bean
public GroupedOpenApi userApi() { public GroupedOpenApi userApi() {
String[] paths = {"/**"}; String[] paths = { "/**" };
return GroupedOpenApi.builder().group("xiaozhi") return GroupedOpenApi.builder().group("xiaozhi")
.pathsToMatch(paths).build(); .pathsToMatch(paths).build();
} }
@@ -1,17 +1,17 @@
package xiaozhi.common.convert; 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.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; 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. * Copyright (c) 人人开源 All rights reserved.
@@ -1,12 +1,13 @@
package xiaozhi.common.entity; package xiaozhi.common.entity;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable; import lombok.Data;
import java.util.Date;
/** /**
* 基础实体类,所有实体都需要继承 * 基础实体类,所有实体都需要继承
@@ -1,6 +1,5 @@
package xiaozhi.common.exception; package xiaozhi.common.exception;
import xiaozhi.common.utils.MessageUtils; import xiaozhi.common.utils.MessageUtils;
/** /**
@@ -10,7 +9,6 @@ import xiaozhi.common.utils.MessageUtils;
*/ */
public class RenException extends RuntimeException { public class RenException extends RuntimeException {
private int code; private int code;
private String msg; private String msg;
@@ -1,13 +1,13 @@
package xiaozhi.common.exception; package xiaozhi.common.exception;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; 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) @ExceptionHandler(RenException.class)
public Result handleRenException(RenException ex) { public Result<Void> handleRenException(RenException ex) {
Result result = new Result(); Result<Void> result = new Result<>();
result.error(ex.getCode(), ex.getMsg()); result.error(ex.getCode(), ex.getMsg());
return result; return result;
} }
@ExceptionHandler(DuplicateKeyException.class) @ExceptionHandler(DuplicateKeyException.class)
public Result handleDuplicateKeyException(DuplicateKeyException ex) { public Result<Void> handleDuplicateKeyException(DuplicateKeyException ex) {
Result result = new Result(); Result<Void> result = new Result<>();
result.error(ErrorCode.DB_RECORD_EXISTS); result.error(ErrorCode.DB_RECORD_EXISTS);
return result; return result;
} }
@ExceptionHandler(UnauthorizedException.class) @ExceptionHandler(UnauthorizedException.class)
public Result handleUnauthorizedException(UnauthorizedException ex) { public Result<Void> handleUnauthorizedException(UnauthorizedException ex) {
Result result = new Result(); Result<Void> result = new Result<>();
result.error(ErrorCode.FORBIDDEN); result.error(ErrorCode.FORBIDDEN);
return result; return result;
} }
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public Result handleException(Exception ex) { public Result<Void> handleException(Exception ex) {
log.error(ex.getMessage(), ex); log.error(ex.getMessage(), ex);
return new Result().error(); return new Result<Void>().error();
} }
} }
@@ -1,13 +1,15 @@
package xiaozhi.common.handler; package xiaozhi.common.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import java.util.Date;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.user.UserDetail;
import xiaozhi.modules.security.user.SecurityUser;
import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component; 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 UPDATER = "updater";
private final static String DATA_OPERATION = "dataOperation"; private final static String DATA_OPERATION = "dataOperation";
private final static String DEPT_ID = "deptId";
@Override @Override
public void insertFill(MetaObject metaObject) { public void insertFill(MetaObject metaObject) {
UserDetail user = SecurityUser.getUser(); UserDetail user = SecurityUser.getUser();
Date date = new Date(); Date date = new Date();
//创建者 // 创建者
strictInsertFill(metaObject, CREATOR, Long.class, user.getId()); strictInsertFill(metaObject, CREATOR, Long.class, user.getId());
//创建时间 // 创建时间
strictInsertFill(metaObject, CREATE_DATE, Date.class, date); strictInsertFill(metaObject, CREATE_DATE, Date.class, date);
//更新者 // 更新者
strictInsertFill(metaObject, UPDATER, Long.class, user.getId()); strictInsertFill(metaObject, UPDATER, Long.class, user.getId());
//更新时间 // 更新时间
strictInsertFill(metaObject, UPDATE_DATE, Date.class, date); strictInsertFill(metaObject, UPDATE_DATE, Date.class, date);
//数据标识 // 数据标识
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.INSERT.getValue()); strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.INSERT.getValue());
} }
@Override @Override
public void updateFill(MetaObject metaObject) { public void updateFill(MetaObject metaObject) {
//更新者 // 更新者
strictUpdateFill(metaObject, UPDATER, Long.class, SecurityUser.getUserId()); strictUpdateFill(metaObject, UPDATER, Long.class, SecurityUser.getUserId());
//更新时间 // 更新时间
strictUpdateFill(metaObject, UPDATE_DATE, Date.class, new Date()); strictUpdateFill(metaObject, UPDATE_DATE, Date.class, new Date());
//数据标识 // 数据标识
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.UPDATE.getValue()); strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.UPDATE.getValue());
} }
} }
@@ -1,8 +1,17 @@
package xiaozhi.common.interceptor; 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.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import cn.hutool.core.util.StrUtil;
import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue; 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.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select; 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 { public class DataFilterInterceptor implements InnerInterceptor {
@SuppressWarnings("rawtypes")
@Override @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); DataScope scope = getDataScope(parameter);
// 不进行数据过滤 // 不进行数据过滤
if (scope == null || StrUtil.isBlank(scope.getSqlFilter())) { if (scope == null || StrUtil.isBlank(scope.getSqlFilter())) {
@@ -48,7 +52,7 @@ public class DataFilterInterceptor implements InnerInterceptor {
// 判断参数里是否有DataScope对象 // 判断参数里是否有DataScope对象
if (parameter instanceof Map) { if (parameter instanceof Map) {
Map<?, ?> parameterMap = (Map<?, ?>) parameter; Map<?, ?> parameterMap = (Map<?, ?>) parameter;
for (Map.Entry entry : parameterMap.entrySet()) { for (Map.Entry<?, ?> entry : parameterMap.entrySet()) {
if (entry.getValue() != null && entry.getValue() instanceof DataScope) { if (entry.getValue() != null && entry.getValue() instanceof DataScope) {
return (DataScope) entry.getValue(); return (DataScope) entry.getValue();
} }
@@ -1,11 +1,11 @@
package xiaozhi.common.page; package xiaozhi.common.page;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/** /**
* 分页工具类 * 分页工具类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,10 +1,10 @@
package xiaozhi.common.page; package xiaozhi.common.page;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
/** /**
* 令牌信息 * 令牌信息
* *
@@ -14,7 +14,6 @@ import java.io.Serializable;
@Schema(description = "令牌信息") @Schema(description = "令牌信息")
public class TokenDTO implements Serializable { public class TokenDTO implements Serializable {
@Schema(description = "密码") @Schema(description = "密码")
private String token; private String token;
@@ -1,6 +1,5 @@
package xiaozhi.common.redis; package xiaozhi.common.redis;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory; 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.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import jakarta.annotation.Resource;
/** /**
* Redis配置 * Redis配置
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,14 +1,15 @@
package xiaozhi.common.redis; 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.Collection;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; 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工具类 * Redis工具类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,10 +1,10 @@
package xiaozhi.common.service; package xiaozhi.common.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
/** /**
* 基础服务接口,所有Service接口都要继承 * 基础服务接口,所有Service接口都要继承
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -56,7 +56,8 @@ public interface BaseService<T> {
* </p> * </p>
* *
* @param entity 实体对象 * @param entity 实体对象
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper} * @param updateWrapper 实体对象封装操作类
* {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/ */
boolean update(T entity, Wrapper<T> updateWrapper); boolean update(T entity, Wrapper<T> updateWrapper);
@@ -1,11 +1,11 @@
package xiaozhi.common.service; package xiaozhi.common.service;
import xiaozhi.common.page.PageData;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import xiaozhi.common.page.PageData;
/** /**
* CRUD基础服务接口 * CRUD基础服务接口
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,5 +1,18 @@
package xiaozhi.common.service.impl; 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.conditions.Wrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; 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.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper; import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import xiaozhi.common.constant.Constant; import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService; import xiaozhi.common.service.BaseService;
import xiaozhi.common.utils.ConvertUtils; 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都要继承 * 基础服务类,所有Service都要继承
@@ -45,7 +47,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
* @param isAsc 排序方式 * @param isAsc 排序方式
*/ */
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) { protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
//分页参数 // 分页参数
long curPage = 1; long curPage = 1;
long limit = 10; long limit = 10;
@@ -56,17 +58,17 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
limit = Long.parseLong((String) params.get(Constant.LIMIT)); limit = Long.parseLong((String) params.get(Constant.LIMIT));
} }
//分页对象 // 分页对象
Page<T> page = new Page<>(curPage, limit); Page<T> page = new Page<>(curPage, limit);
//分页参数 // 分页参数
params.put(Constant.PAGE, page); params.put(Constant.PAGE, page);
//排序字段 // 排序字段
String orderField = (String) params.get(Constant.ORDER_FIELD); String orderField = (String) params.get(Constant.ORDER_FIELD);
String order = (String) params.get(Constant.ORDER); String order = (String) params.get(Constant.ORDER);
//前端字段排序 // 前端字段排序
if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) { if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) {
if (Constant.ASC.equalsIgnoreCase(order)) { if (Constant.ASC.equalsIgnoreCase(order)) {
return page.addOrder(OrderItem.asc(orderField)); return page.addOrder(OrderItem.asc(orderField));
@@ -75,12 +77,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
} }
} }
//没有排序字段,则不排序 // 没有排序字段,则不排序
if (StringUtils.isBlank(defaultOrderField)) { if (StringUtils.isBlank(defaultOrderField)) {
return page; return page;
} }
//默认排序 // 默认排序
if (isAsc) { if (isAsc) {
page.addOrder(OrderItem.asc(defaultOrderField)); page.addOrder(OrderItem.asc(defaultOrderField));
} else { } else {
@@ -90,13 +92,13 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
return page; return page;
} }
protected <T> PageData<T> getPageData(List<?> list, long total, Class<T> target) { protected <D> PageData<D> getPageData(List<?> list, long total, Class<D> target) {
List<T> targetList = ConvertUtils.sourceToTarget(list, target); List<D> targetList = ConvertUtils.sourceToTarget(list, target);
return new PageData<>(targetList, total); return new PageData<>(targetList, total);
} }
protected <T> PageData<T> getPageData(IPage page, Class<T> target) { protected <D> PageData<D> getPageData(IPage<?> page, Class<D> target) {
return getPageData(page.getRecords(), page.getTotal(), target); return getPageData(page.getRecords(), page.getTotal(), target);
} }
@@ -126,10 +128,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
return SqlHelper.retBool(result); return SqlHelper.retBool(result);
} }
@SuppressWarnings("unchecked")
protected Class<M> currentMapperClass() { protected Class<M> currentMapperClass() {
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0); return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
} }
@SuppressWarnings("unchecked")
@Override @Override
public Class<T> currentModelClass() { public Class<T> currentModelClass() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1); return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
@@ -164,11 +168,11 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
/** /**
* 执行批量操作 * 执行批量操作
*/ */
@SuppressWarnings("deprecation")
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) { protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer); return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer);
} }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean updateById(T entity) { public boolean updateById(T entity) {
@@ -1,26 +1,30 @@
package xiaozhi.common.service.impl; 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.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; 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基础服务类 * CRUD基础服务类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
* Website: https://www.renren.io * Website: https://www.renren.io
*/ */
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T> implements CrudService<T, D> { public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T>
implements CrudService<T, D> {
@SuppressWarnings("unchecked")
protected Class<D> currentDtoClass() { protected Class<D> currentDtoClass() {
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2); return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
} }
@@ -29,8 +33,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
public PageData<D> page(Map<String, Object> params) { public PageData<D> page(Map<String, Object> params) {
IPage<T> page = baseDao.selectPage( IPage<T> page = baseDao.selectPage(
getPage(params, null, false), getPage(params, null, false),
getWrapper(params) getWrapper(params));
);
return getPageData(page, currentDtoClass()); return getPageData(page, currentDtoClass());
} }
@@ -56,7 +59,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
T entity = ConvertUtils.sourceToTarget(dto, currentModelClass()); T entity = ConvertUtils.sourceToTarget(dto, currentModelClass());
insert(entity); insert(entity);
//copy主键值到dto // copy主键值到dto
BeanUtils.copyProperties(entity, dto); BeanUtils.copyProperties(entity, dto);
} }
@@ -1,9 +1,9 @@
package xiaozhi.common.user; package xiaozhi.common.user;
import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import lombok.Data;
/** /**
* 登录用户信息 * 登录用户信息
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,12 +1,13 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.springframework.beans.BeanUtils;
import lombok.extern.slf4j.Slf4j;
/** /**
* 转换工具类 * 转换工具类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -20,7 +21,7 @@ public class ConvertUtils {
} }
T targetObject = null; T targetObject = null;
try { try {
targetObject = target.newInstance(); targetObject = target.getDeclaredConstructor().newInstance();
BeanUtils.copyProperties(source, targetObject); BeanUtils.copyProperties(source, targetObject);
} catch (Exception e) { } catch (Exception e) {
log.error("convert error ", e); log.error("convert error ", e);
@@ -34,10 +35,10 @@ public class ConvertUtils {
return null; return null;
} }
List targetList = new ArrayList<>(sourceList.size()); List<T> targetList = new ArrayList<>(sourceList.size());
try { try {
for (Object source : sourceList) { for (Object source : sourceList) {
T targetObject = target.newInstance(); T targetObject = target.getDeclaredConstructor().newInstance();
BeanUtils.copyProperties(source, targetObject); BeanUtils.copyProperties(source, targetObject);
targetList.add(targetObject); targetList.add(targetObject);
} }
@@ -1,20 +1,21 @@
package xiaozhi.common.utils; 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.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.util.DigestUtils; import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/** /**
* Http * Http
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -70,15 +71,15 @@ public class HttpContextUtils {
} }
public static String getLanguage() { public static String getLanguage() {
//默认语言 // 默认语言
String defaultLanguage = "zh-CN"; String defaultLanguage = "zh-CN";
//request // request
HttpServletRequest request = getHttpServletRequest(); HttpServletRequest request = getHttpServletRequest();
if (request == null) { if (request == null) {
return defaultLanguage; return defaultLanguage;
} }
//请求语言 // 请求语言
defaultLanguage = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE); defaultLanguage = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE);
return defaultLanguage; return defaultLanguage;
@@ -1,8 +1,9 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import org.apache.commons.lang3.StringUtils;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/** /**
* IP地址 * IP地址
@@ -1,13 +1,14 @@
package xiaozhi.common.utils; 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.ArrayList;
import java.util.List; 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 工具类 * JSON 工具类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -59,7 +60,8 @@ public class JsonUtils {
return new ArrayList<>(); return new ArrayList<>();
} }
try { try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz)); return objectMapper.readValue(text,
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -1,11 +1,11 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import java.io.Serializable;
/** /**
* 响应数据 * 响应数据
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -36,7 +36,6 @@ public class Result<T> implements Serializable {
return this; return this;
} }
public Result<T> error() { public Result<T> error() {
this.code = ErrorCode.INTERNAL_SERVER_ERROR; this.code = ErrorCode.INTERNAL_SERVER_ERROR;
this.msg = MessageUtils.getMessage(this.code); this.msg = MessageUtils.getMessage(this.code);
@@ -1,11 +1,11 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Data;
/** /**
* 树节点,所有需要实现树节点的,都需要继承该类 * 树节点,所有需要实现树节点的,都需要继承该类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,12 +1,12 @@
package xiaozhi.common.utils; package xiaozhi.common.utils;
import xiaozhi.common.validator.AssertUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import xiaozhi.common.validator.AssertUtils;
/** /**
* 树形结构工具类,如:菜单、部门等 * 树形结构工具类,如:菜单、部门等
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -17,8 +17,8 @@ public class TreeUtils {
/** /**
* 根据pid,构建树节点 * 根据pid,构建树节点
*/ */
public static <T extends TreeNode> List<T> build(List<T> treeNodes, Long pid) { public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes, Long pid) {
//pid不能为空 // pid不能为空
AssertUtils.isNull(pid, "pid"); AssertUtils.isNull(pid, "pid");
List<T> treeList = new ArrayList<>(); List<T> treeList = new ArrayList<>();
@@ -34,7 +34,7 @@ public class TreeUtils {
/** /**
* 查找子节点 * 查找子节点
*/ */
private static <T extends TreeNode> T findChildren(List<T> treeNodes, T rootNode) { private static <T extends TreeNode<T>> T findChildren(List<T> treeNodes, T rootNode) {
for (T treeNode : treeNodes) { for (T treeNode : treeNodes) {
if (rootNode.getId().equals(treeNode.getPid())) { if (rootNode.getId().equals(treeNode.getPid())) {
rootNode.getChildren().add(findChildren(treeNodes, treeNode)); rootNode.getChildren().add(findChildren(treeNodes, treeNode));
@@ -46,10 +46,10 @@ public class TreeUtils {
/** /**
* 构建树节点 * 构建树节点
*/ */
public static <T extends TreeNode> List<T> build(List<T> treeNodes) { public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes) {
List<T> result = new ArrayList<>(); List<T> result = new ArrayList<>();
//list转map // list转map
Map<Long, T> nodeMap = new LinkedHashMap<>(treeNodes.size()); Map<Long, T> nodeMap = new LinkedHashMap<>(treeNodes.size());
for (T treeNode : treeNodes) { for (T treeNode : treeNodes) {
nodeMap.put(treeNode.getId(), treeNode); nodeMap.put(treeNode.getId(), treeNode);
@@ -1,14 +1,15 @@
package xiaozhi.common.validator; 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.collection.CollUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; 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); 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) { if (code == null) {
throw new RenException(ErrorCode.NOT_NULL, "code"); throw new RenException(ErrorCode.NOT_NULL, "code");
} }
@@ -1,16 +1,17 @@
package xiaozhi.common.validator; package xiaozhi.common.validator;
import xiaozhi.common.exception.RenException; import java.util.Locale;
import jakarta.validation.ConstraintViolation; import java.util.Set;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator; import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator;
import java.util.Locale; import jakarta.validation.ConstraintViolation;
import java.util.Set; import jakarta.validation.Validation;
import jakarta.validation.Validator;
import xiaozhi.common.exception.RenException;
/** /**
* hibernate-validator校验工具类 * hibernate-validator校验工具类
@@ -35,7 +36,7 @@ public class ValidatorUtils {
throws RenException { throws RenException {
Locale.setDefault(LocaleContextHolder.getLocale()); Locale.setDefault(LocaleContextHolder.getLocale());
Validator validator = Validation.byDefaultProvider().configure().messageInterpolator( Validator validator = Validation.byDefaultProvider().configure().messageInterpolator(
new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource()))) new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource())))
.buildValidatorFactory().getValidator(); .buildValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups); Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
@@ -1,8 +1,9 @@
package xiaozhi.common.xss; package xiaozhi.common.xss;
import org.apache.commons.lang3.StringUtils;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import org.apache.commons.lang3.StringUtils;
/** /**
* SQL过滤 * SQL过滤
@@ -20,19 +21,20 @@ public class SqlFilter {
if (StringUtils.isBlank(str)) { if (StringUtils.isBlank(str)) {
return null; return null;
} }
//去掉'|"|;|\字符 // 去掉'|"|;|\字符
str = StringUtils.replace(str, "'", ""); str = StringUtils.replace(str, "'", "");
str = StringUtils.replace(str, "\"", ""); str = StringUtils.replace(str, "\"", "");
str = StringUtils.replace(str, ";", ""); str = StringUtils.replace(str, ";", "");
str = StringUtils.replace(str, "\\", ""); str = StringUtils.replace(str, "\\", "");
//转换成小写 // 转换成小写
str = str.toLowerCase(); 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) { for (String keyword : keywords) {
if (str.contains(keyword)) { if (str.contains(keyword)) {
throw new RenException(ErrorCode.INVALID_SYMBOL); throw new RenException(ErrorCode.INVALID_SYMBOL);
@@ -1,6 +1,5 @@
package xiaozhi.common.xss; package xiaozhi.common.xss;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean; 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.context.annotation.Configuration;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import jakarta.servlet.DispatcherType;
/** /**
* XSS 配置文件 * XSS 配置文件
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,11 +1,17 @@
package xiaozhi.common.xss; package xiaozhi.common.xss;
import jakarta.servlet.*; import java.io.IOException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import org.springframework.util.PathMatcher; 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过滤 * XSS过滤
@@ -38,7 +44,8 @@ public class XssFilter implements Filter {
private boolean shouldNotFilter(HttpServletRequest request) { private boolean shouldNotFilter(HttpServletRequest request) {
// 放行不过滤的URL // 放行不过滤的URL
return properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath())); return properties.getExcludeUrls().stream()
.anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath()));
} }
@Override @Override
@@ -1,20 +1,20 @@
package xiaozhi.common.xss; 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.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; 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过滤处理 * XSS过滤处理
@@ -29,18 +29,18 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
@Override @Override
public ServletInputStream getInputStream() throws IOException { public ServletInputStream getInputStream() throws IOException {
//非json类型,直接返回 // 非json类型,直接返回
if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) { if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) {
return super.getInputStream(); return super.getInputStream();
} }
//为空,直接返回 // 为空,直接返回
String json = IoUtil.readUtf8(super.getInputStream()); String json = IoUtil.readUtf8(super.getInputStream());
if (StringUtils.isBlank(json)) { if (StringUtils.isBlank(json)) {
return super.getInputStream(); return super.getInputStream();
} }
//xss过滤 // xss过滤
json = xssEncode(json); json = xssEncode(json);
final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() { return new ServletInputStream() {
@@ -1,11 +1,12 @@
package xiaozhi.common.xss; package xiaozhi.common.xss;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
/** /**
* XSS 配置项 * XSS 配置项
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -22,13 +22,15 @@ public class XssUtils extends Safelist {
*/ */
private static Safelist xssWhitelist() { private static Safelist xssWhitelist() {
return new Safelist() return new Safelist()
//支持的标签 // 支持的标签
.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", .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", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q",
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul", "small",
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u",
"ul",
"embed", "object", "param", "span") "embed", "object", "param", "span")
//支持的标签属性 // 支持的标签属性
.addAttributes("a", "href", "class", "style", "target", "rel", "nofollow") .addAttributes("a", "href", "class", "style", "target", "rel", "nofollow")
.addAttributes("blockquote", "cite") .addAttributes("blockquote", "cite")
.addAttributes("code", "class", "style") .addAttributes("code", "class", "style")
@@ -44,13 +46,16 @@ public class XssUtils extends Safelist {
.addAttributes("ul", "type", "style") .addAttributes("ul", "type", "style")
.addAttributes("pre", "class", "style") .addAttributes("pre", "class", "style")
.addAttributes("div", "class", "id", "style") .addAttributes("div", "class", "id", "style")
.addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen", "allowfullscreen", .addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen",
"quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess", "allownetworking", "type") "allowfullscreen",
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid", "codebase") "quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess",
"allownetworking", "type")
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid",
"codebase")
.addAttributes("param", "name", "value") .addAttributes("param", "name", "value")
.addAttributes("span", "class", "style") .addAttributes("span", "class", "style")
//标签属性对应的协议 // 标签属性对应的协议
.addProtocols("a", "href", "ftp", "http", "https", "mailto") .addProtocols("a", "href", "ftp", "http", "https", "mailto")
.addProtocols("img", "src", "http", "https") .addProtocols("img", "src", "http", "https")
.addProtocols("blockquote", "cite", "http", "https") .addProtocols("blockquote", "cite", "http", "https")
@@ -1,13 +1,21 @@
package xiaozhi.modules.admin.contrloler; 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.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.constant.Constant; import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.Result; 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.service.SysUserService;
import xiaozhi.modules.sys.vo.AdminPageUserVO; import xiaozhi.modules.sys.vo.AdminPageUserVO;
import java.util.Map;
/** /**
* 管理员控制层 * 管理员控制层
* *
@@ -64,7 +70,7 @@ public class AdminController {
@Operation(summary = "用户删除") @Operation(summary = "用户删除")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
public Result<Void> delete(@PathVariable Long id) { public Result<Void> delete(@PathVariable Long id) {
sysUserService.delete(new Long[]{id}); sysUserService.delete(new Long[] { id });
return new Result<>(); return new Result<>();
} }
@@ -78,7 +84,7 @@ public class AdminController {
}) })
public Result<Void> pageDevice( public Result<Void> pageDevice(
@Parameter(hidden = true) @RequestParam Map<String, Object> params) { @Parameter(hidden = true) @RequestParam Map<String, Object> params) {
//TODO 等设备功能模块写好 // TODO 等设备功能模块写好
return new Result<Void>().error(600,"等设备功能模块写好"); return new Result<Void>().error(600, "等设备功能模块写好");
} }
} }
@@ -1,13 +1,26 @@
package xiaozhi.modules.agent.controller; 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.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.constant.Constant; import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.user.UserDetail; import xiaozhi.common.user.UserDetail;
@@ -19,10 +32,6 @@ import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.security.user.SecurityUser;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Tag(name = "智能体管理") @Tag(name = "智能体管理")
@AllArgsConstructor @AllArgsConstructor
@RestController @RestController
@@ -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<List<AgentTemplateEntity>> templateList() {
List<AgentTemplateEntity> list = agentTemplateService
.list(new QueryWrapper<AgentTemplateEntity>().orderByAsc("sort"));
return new Result<List<AgentTemplateEntity>>().ok(list);
}
}
@@ -1,6 +1,7 @@
package xiaozhi.modules.agent.dao; package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import xiaozhi.common.dao.BaseDao; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentEntity;
@@ -0,0 +1,17 @@
package xiaozhi.modules.agent.dao;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
/**
* @author chenerlei
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Mapper
* @createDate 2025-03-22 11:48:18
*/
@Mapper
public interface AgentTemplateDao extends BaseMapper<AgentTemplateEntity> {
}
@@ -1,11 +1,11 @@
package xiaozhi.modules.agent.dto; package xiaozhi.modules.agent.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
/** /**
* 智能体创建DTO * 智能体创建DTO
* 专用于新增智能体,不包含id、agentCode和sort字段,这些字段由系统自动生成/设置默认值 * 专用于新增智能体,不包含id、agentCode和sort字段,这些字段由系统自动生成/设置默认值
@@ -1,11 +1,11 @@
package xiaozhi.modules.agent.dto; package xiaozhi.modules.agent.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
/** /**
* 智能体更新DTO * 智能体更新DTO
* 专用于更新智能体,id字段是必需的,用于标识要更新的智能体 * 专用于更新智能体,id字段是必需的,用于标识要更新的智能体
@@ -1,11 +1,12 @@
package xiaozhi.modules.agent.entity; package xiaozhi.modules.agent.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.util.Date;
@Data @Data
@TableName("ai_agent") @TableName("ai_agent")
@Schema(description = "智能体信息") @Schema(description = "智能体信息")
@@ -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;
}
@@ -1,12 +1,12 @@
package xiaozhi.modules.agent.service; package xiaozhi.modules.agent.service;
import java.util.List;
import java.util.Map;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService; import xiaozhi.common.service.BaseService;
import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentEntity;
import java.util.List;
import java.util.Map;
public interface AgentService extends BaseService<AgentEntity> { public interface AgentService extends BaseService<AgentEntity> {
/** /**
* 根据用户ID获取智能体列表 * 根据用户ID获取智能体列表
@@ -0,0 +1,14 @@
package xiaozhi.modules.agent.service;
import com.baomidou.mybatisplus.extension.service.IService;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
/**
* @author chenerlei
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service
* @createDate 2025-03-22 11:48:18
*/
public interface AgentTemplateService extends IService<AgentTemplateEntity> {
}
@@ -1,18 +1,20 @@
package xiaozhi.modules.agent.service.impl; 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.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.stereotype.Service;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.modules.agent.dao.AgentDao; import xiaozhi.modules.agent.dao.AgentDao;
import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentService;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Service @Service
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService { public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
private final AgentDao agentDao; private final AgentDao agentDao;
@@ -32,8 +34,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) { public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
IPage<AgentEntity> page = agentDao.selectPage( IPage<AgentEntity> page = agentDao.selectPage(
getPage(params, "sort", true), getPage(params, "sort", true),
new QueryWrapper<>() new QueryWrapper<>());
);
return new PageData<>(page.getRecords(), page.getTotal()); return new PageData<>(page.getRecords(), page.getTotal());
} }
@@ -0,0 +1,20 @@
package xiaozhi.modules.agent.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import xiaozhi.modules.agent.dao.AgentTemplateDao;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentTemplateService;
/**
* @author chenerlei
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service实现
* @createDate 2025-03-22 11:48:18
*/
@Service
public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateDao, AgentTemplateEntity>
implements AgentTemplateService {
}
@@ -0,0 +1,15 @@
package xiaozhi.modules.agent.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
@Data
@EqualsAndHashCode(callSuper = true)
public class AgentTemplateVO extends AgentTemplateEntity {
// 角色音色
private String ttsModelName;
// 角色模型
private String llmModelName;
}
@@ -1,5 +1,18 @@
package xiaozhi.modules.device.controller; 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.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -28,8 +41,7 @@ public class DeviceController {
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
@PostMapping("/bind/{deviceCode}")
@PostMapping("/bind/{agentId}")
@Operation(summary = "绑定设备") @Operation(summary = "绑定设备")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<Void> bindDevice(@PathVariable String agentId,@RequestBody String code) { public Result<Void> bindDevice(@PathVariable String agentId,@RequestBody String code) {
@@ -55,10 +67,10 @@ public class DeviceController {
@PostMapping("/unbind") @PostMapping("/unbind")
@Operation(summary = "解绑设备") @Operation(summary = "解绑设备")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) { public Result<Void> unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) {
UserDetail user = SecurityUser.getUser(); UserDetail user = SecurityUser.getUser();
deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId()); deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId());
return new Result(); return new Result<Void>();
} }
@@ -0,0 +1,76 @@
package xiaozhi.modules.device.controller;
import java.nio.charset.StandardCharsets;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.device.utils.NetworkUtil;
@Tag(name = "设备管理", description = "OTA 相关接口")
@RestController
@RequiredArgsConstructor
@RequestMapping("/ota")
public class OTAController {
private final DeviceService deviceService;
@Operation(summary = "检查 OTA 版本和设备激活状态")
@PostMapping
public ResponseEntity<String> checkOTAVersion(
@RequestBody DeviceReportReqDTO deviceReportReqDTO,
@Parameter(name = "Device-Id", description = "设备唯一标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Device-Id") String deviceId,
@Parameter(name = "Client-Id", description = "客户端标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Client-Id") String clientId) {
if (StringUtils.isAnyBlank(deviceId, clientId)) {
return createResponse(DeviceReportRespDTO.createError("Device ID is required"));
}
String macAddress = deviceReportReqDTO.getMacAddress();
boolean macAddressValid = NetworkUtil.isMacAddressValid(macAddress);
// 设备Id和Mac地址应是一致的, 并且必须需要application字段
if (!deviceId.equals(macAddress) || !macAddressValid || deviceReportReqDTO.getApplication() == null) {
return createResponse(DeviceReportRespDTO.createError("Invalid OTA request"));
}
return createResponse(deviceService.checkDeviceActive(macAddress, deviceId, clientId, deviceReportReqDTO));
}
@Operation(summary = "设备激活")
@GetMapping("/activation")
public Result<Boolean> deviceActivation(@RequestParam String code) {
return new Result<Boolean>().ok(deviceService.deviceActivation(code));
}
@SneakyThrows
private ResponseEntity<String> createResponse(DeviceReportRespDTO deviceReportRespDTO) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String json = objectMapper.writeValueAsString(deviceReportRespDTO);
byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.contentLength(jsonBytes.length)
.body(json);
}
}
@@ -1,7 +1,9 @@
package xiaozhi.modules.device.dao; package xiaozhi.modules.device.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.entity.DeviceEntity;
@Mapper @Mapper
@@ -0,0 +1,148 @@
package xiaozhi.modules.device.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
@Setter
@Getter
@Schema(description = "设备固件信息上报求请求体")
public class DeviceReportReqDTO implements Serializable {
private static final long serialVersionUID = 1L;
// region 实体属性
@Schema(description = "板子固件版本号")
private Integer version;
@Schema(description = "闪存大小(单位:字节)")
@JsonProperty("flash_size")
private Integer flashSize;
@Schema(description = "最小空闲堆内存(字节)")
@JsonProperty("minimum_free_heap_size")
private Integer minimumFreeHeapSize;
@Schema(description = "设备 MAC 地址")
@JsonProperty("mac_address")
private String macAddress;
@Schema(description = "设备唯一标识 UUID")
private String uuid;
@Schema(description = "芯片型号名称")
@JsonProperty("chip_model_name")
private String chipModelName;
@Schema(description = "芯片详细信息")
@JsonProperty("chip_info")
private ChipInfo chipInfo;
@Schema(description = "应用程序信息")
private Application application;
@Schema(description = "分区表列表")
@JsonProperty("partition_table")
private List<Partition> partitionTable;
@Schema(description = "当前运行的 OTA 分区信息")
private OtaInfo ota;
@Schema(description = "板子配置信息")
private BoardInfo board;
// endregion
@Getter
@Setter
@Schema(description = "芯片信息")
public static class ChipInfo {
@Schema(description = "芯片模型代码")
private Integer model;
@Schema(description = "核心数")
private Integer cores;
@Schema(description = "硬件修订版本")
private Integer revision;
@Schema(description = "芯片功能标志位")
private Integer features;
}
@Getter
@Setter
@Schema(description = "板子编译信息")
public static class Application {
@Schema(description = "名称")
private String name;
@Schema(description = "应用版本号")
private String version;
@Schema(description = "编译时间(UTC ISO格式)")
@JsonProperty("compile_time")
private String compileTime;
@Schema(description = "ESP-IDF 版本号")
@JsonProperty("idf_version")
private String idfVersion;
@Schema(description = "ELF 文件 SHA256 校验")
@JsonProperty("elf_sha256")
private String elfSha256;
}
@Getter
@Setter
@Schema(description = "分区信息")
public static class Partition {
@Schema(description = "分区标签名")
private String label;
@Schema(description = "分区类型")
private Integer type;
@Schema(description = "子类型")
private Integer subtype;
@Schema(description = "起始地址")
private Integer address;
@Schema(description = "分区大小")
private Integer size;
}
@Getter
@Setter
@Schema(description = "OTA信息")
public static class OtaInfo {
@Schema(description = "当前OTA标签")
private String label;
}
@Getter
@Setter
@Schema(description = "板子连接和网络信息")
public static class BoardInfo {
@Schema(description = "板子类型")
private String type;
@Schema(description = "连接的 Wi-Fi SSID")
private String ssid;
@Schema(description = "Wi-Fi 信号强度(RSSI")
private Integer rssi;
@Schema(description = "Wi-Fi 信道")
private Integer channel;
@Schema(description = "IP 地址")
private String ip;
@Schema(description = "MAC 地址")
private String mac;
}
}
@@ -0,0 +1,61 @@
package xiaozhi.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Data
@Schema(description = "设备OTA检测版本返回体,包含激活码要求")
public class DeviceReportRespDTO {
@Schema(description = "服务器时间")
private ServerTime serverTime;
@Schema(description = "激活码")
private Activation activation;
@Schema(description = "错误信息")
private String error;
@Schema(description = "固件版本信息")
private Firmware firmware;
@Getter
@Setter
public static class Firmware {
@Schema(description = "版本号")
private String version;
@Schema(description = "下载地址")
private String url;
}
public static DeviceReportRespDTO createError(String message) {
DeviceReportRespDTO resp = new DeviceReportRespDTO();
resp.setError(message);
return resp;
}
@Setter
@Getter
public static class Activation {
@Schema(description = "激活码")
private String code;
@Schema(description = "激活码信息: 激活地址")
private String message;
}
@Getter
@Setter
public static class ServerTime {
@Schema(description = "时间戳")
private Long timestamp;
@Schema(description = "时区")
private String timeZone;
@Schema(description = "时区偏移量,单位为分钟")
private Integer timezoneOffset;
}
}
@@ -1,10 +1,11 @@
package xiaozhi.modules.device.dto; package xiaozhi.modules.device.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
/** /**
* 设备解绑表单 * 设备解绑表单
*/ */
@@ -1,13 +1,14 @@
package xiaozhi.modules.device.entity; package xiaozhi.modules.device.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import xiaozhi.common.entity.BaseEntity; import xiaozhi.common.entity.BaseEntity;
import java.util.Date;
@Data @Data
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
@TableName("ai_device") @TableName("ai_device")
@@ -1,18 +1,49 @@
package xiaozhi.modules.device.service; 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.List;
import java.util.Map; import java.util.Map;
public interface DeviceService { import xiaozhi.common.page.PageData;
void bindDevice(DeviceBindDTO dto); 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 {
/**
* 根据Mac地址获取设备信息
*/
DeviceEntity getDeviceById(String macAddress);
/**
* 检查设备是否激活
*/
DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId,
DeviceReportReqDTO deviceReport);
/**
* 绑定设备
*/
DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader);
/**
* 获取用户设备列表
*/
List<DeviceEntity> getUserDevices(Long userId); List<DeviceEntity> getUserDevices(Long userId);
/**
* 解绑设备
*/
void unbindDevice(Long userId, Long deviceId); void unbindDevice(Long userId, Long deviceId);
/**
* 管理员设备列表
*/
PageData<DeviceEntity> adminDeviceList(Map<String, Object> params); PageData<DeviceEntity> adminDeviceList(Map<String, Object> params);
/**
* 设备激活
*/
Boolean deviceActivation(String activationCode);
} }
@@ -1,21 +1,173 @@
package xiaozhi.modules.device.service.impl; package xiaozhi.modules.device.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import java.time.Instant;
import com.baomidou.mybatisplus.core.metadata.IPage; import java.util.Date;
import org.springframework.stereotype.Service; import java.util.HashMap;
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.util.List; import java.util.List;
import java.util.Map; 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 @Service
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService { public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
private final DeviceDao deviceDao;
private final String frontedUrl;
private final RedisTemplate<String, Object> redisTemplate;
// 添加构造函数来初始化 deviceMapper
public DeviceServiceImpl(DeviceDao deviceDao,
@Value("${app.fronted-url:http://localhost:8001}") String frontedUrl,
RedisTemplate<String, Object> redisTemplate) {
this.deviceDao = deviceDao;
this.frontedUrl = frontedUrl;
this.redisTemplate = redisTemplate;
}
@Override
public DeviceEntity getDeviceById(String deviceId) {
LambdaQueryWrapper<DeviceEntity> 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<Object, Object> cacheMap = redisTemplate.opsForHash().entries(cacheDeviceKey);
if (cacheMap == null) {
throw new RenException("激活码错误");
}
String cachedCode = (String) cacheMap.get("activation_code");
if (!activationCode.equals(cachedCode)) {
throw new RenException("激活码错误");
}
// 检查设备有没有被激活
if (selectById(deviceId) != null) {
throw new RenException("设备已激活");
}
String macAddress = (String) cacheMap.get("mac_address");
String board = (String) cacheMap.get("board");
String appVersion = (String) cacheMap.get("app_version");
UserDetail user = SecurityUser.getUser();
if (user.getId() == null) {
throw new RenException("用户未登录");
}
Date currentTime = new Date();
DeviceEntity deviceEntity = new DeviceEntity();
deviceEntity.setId(deviceId);
deviceEntity.setBoard(board);
deviceEntity.setAppVersion(appVersion);
deviceEntity.setMacAddress(macAddress);
deviceEntity.setUserId(user.getId());
deviceEntity.setCreator(user.getId());
deviceEntity.setCreateDate(currentTime);
deviceEntity.setUpdater(user.getId());
deviceEntity.setUpdateDate(currentTime);
deviceEntity.setLastConnectedAt(currentTime);
deviceDao.insert(deviceEntity);
// 清理redis缓存
redisTemplate.delete(cacheDeviceKey);
redisTemplate.delete(deviceKey);
return true;
}
@Override
public DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId,
DeviceReportReqDTO deviceReport) {
DeviceReportRespDTO response = new DeviceReportRespDTO();
response.setServerTime(buildServerTime());
// todo: 此处是固件信息,目前是针对固件上传上来的版本号再返回回去
// 在未来开发了固件更新功能,需要更换此处代码,
// 或写定时任务定期请求虾哥的OTA,获取最新的版本讯息保存到服务内
DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware();
firmware.setVersion(deviceReport.getApplication().getVersion());
firmware.setUrl("http://localhost:8002/xiaozhi-esp32-api/api/v1/ota/download");
response.setFirmware(firmware);
DeviceEntity deviceById = getDeviceById(deviceId);
if (deviceById != null) { // 如果设备存在,则更新上次连接时间
deviceById.setLastConnectedAt(new Date());
deviceDao.updateById(deviceById);
} else { // 如果设备不存在,则生成激活码
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
String dataKey = String.format("ota:activation:data:%s", safeDeviceId);
Map<Object, Object> cacheMap = redisTemplate.opsForHash().entries(dataKey);
DeviceReportRespDTO.Activation code = new DeviceReportRespDTO.Activation();
if (cacheMap != null && cacheMap.containsKey("activation_code")) {
String cachedCode = (String) cacheMap.get("activation_code");
code.setCode(cachedCode);
code.setMessage(frontedUrl + "\n" + cachedCode);
} else {
String newCode = RandomUtil.randomNumbers(6);
code.setCode(newCode);
code.setMessage(frontedUrl + "\n" + newCode);
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", deviceId);
dataMap.put("mac_address", macAddress);
dataMap.put("board", (deviceReport.getChipModelName() != null) ? deviceReport.getChipModelName()
: (deviceReport.getBoard() != null ? deviceReport.getBoard().getType() : "unknown"));
dataMap.put("app_version", (deviceReport.getApplication() != null)
? deviceReport.getApplication().getVersion()
: null);
dataMap.put("deviceId", deviceId);
dataMap.put("activation_code", newCode);
// 写入主数据 key
redisTemplate.opsForHash().putAll(dataKey, dataMap);
redisTemplate.expire(dataKey, 24, TimeUnit.HOURS);
// 写入反查激活码 key
String codeKey = "ota:activation:code:" + newCode;
redisTemplate.opsForValue().set(codeKey, deviceId, 24, TimeUnit.HOURS);
}
response.setActivation(code);
}
return response;
}
@Override @Override
public void bindDevice(DeviceBindDTO dto) { public void bindDevice(DeviceBindDTO dto) {
DeviceEntity deviceEntity = ConvertUtils.sourceToTarget(dto, DeviceEntity.class); DeviceEntity deviceEntity = ConvertUtils.sourceToTarget(dto, DeviceEntity.class);
@@ -38,9 +190,16 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
public PageData<DeviceEntity> adminDeviceList(Map<String, Object> params) { public PageData<DeviceEntity> adminDeviceList(Map<String, Object> params) {
IPage<DeviceEntity> page = baseDao.selectPage( IPage<DeviceEntity> page = baseDao.selectPage(
getPage(params, "sort", true), getPage(params, "sort", true),
new QueryWrapper<>() new QueryWrapper<>());
);
return new PageData<>(page.getRecords(), page.getTotal()); return new PageData<>(page.getRecords(), page.getTotal());
} }
private DeviceReportRespDTO.ServerTime buildServerTime() {
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
TimeZone tz = TimeZone.getDefault();
serverTime.setTimestamp(Instant.now().toEpochMilli());
serverTime.setTimeZone(tz.getID());
serverTime.setTimezoneOffset(tz.getOffset(System.currentTimeMillis()) / (60 * 1000));
return serverTime;
}
} }
@@ -0,0 +1,34 @@
package xiaozhi.modules.device.utils;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
/**
* 网络工具类
*/
public class NetworkUtil {
/**
* MAC地址正则表达式
*/
private static final Pattern macPattern = Pattern.compile("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
/**
* 判断MAC地址是否合法
*/
public static boolean isMacAddressValid(String mac) {
if (StringUtils.isBlank(mac)) {
return false;
}
// 正则校验格式
if (!macPattern.matcher(mac).matches()) {
return false;
}
// 校验MAC地址是否为单播地址
String normalized = mac.toLowerCase();
String[] parts = normalized.split("[:-]");
int firstByte = Integer.parseInt(parts[0], 16);
return (firstByte & 1) == 0; // 最低位为0表示单播地址,合法
}
}
@@ -0,0 +1,59 @@
package xiaozhi.modules.device.vo;
import lombok.Data;
@Data
public class DeviceOtaVO {
private Activation activation;
private Mqtt mqtt;
private ServerTime server_time;
private Firmware firmware;
private String error;
@Data
public static class Activation {
private String code;
private String message;
public Activation() {
}
public Activation(String code, String message) {
this.code = code;
this.message = message;
}
}
@Data
public class Mqtt {
}
@Data
public static class ServerTime {
private Long timestamp;
private Integer timezone_offset;
public ServerTime() {
}
public ServerTime(Long timestamp, Integer timezone_offset) {
this.timestamp = timestamp;
this.timezone_offset = timezone_offset;
}
}
@Data
public static class Firmware {
private String version;
private String url;
public Firmware() {
}
public Firmware(String version, String url) {
this.version = version;
this.url = url;
}
}
}
@@ -1,2 +0,0 @@
package xiaozhi.modules.model.controller;public class ModelConfigController {
}
@@ -1,10 +1,21 @@
package xiaozhi.modules.model.controller; 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.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.Result; import xiaozhi.common.utils.Result;
import xiaozhi.modules.model.dto.ModelConfigBodyDTO; 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.ModelConfigService;
import xiaozhi.modules.model.service.ModelProviderService; import xiaozhi.modules.model.service.ModelProviderService;
import java.util.List;
@AllArgsConstructor @AllArgsConstructor
@RestController @RestController
@RequestMapping("/models") @RequestMapping("/models")
@@ -29,7 +38,7 @@ public class ModelController {
@Operation(summary = "获取所有模型名称") @Operation(summary = "获取所有模型名称")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
public Result<List<String>> getModelNames(@RequestParam String modelType, public Result<List<String>> getModelNames(@RequestParam String modelType,
@RequestParam(required = false) String modelName) { @RequestParam(required = false) String modelName) {
List<String> modelNameList = modelConfigService.getModelCodeList(modelType, modelName); List<String> modelNameList = modelConfigService.getModelCodeList(modelType, modelName);
return new Result<List<String>>().ok(modelNameList); return new Result<List<String>>().ok(modelNameList);
} }
@@ -45,51 +54,49 @@ public class ModelController {
@GetMapping("/{modelType}/{provideCode}/fields") @GetMapping("/{modelType}/{provideCode}/fields")
@Operation(summary = "获取模型供应器字段") @Operation(summary = "获取模型供应器字段")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
public Result<List<String>> getModelProviderFields(@PathVariable String modelType, @PathVariable String provideCode) { public Result<List<String>> getModelProviderFields(@PathVariable String modelType,
@PathVariable String provideCode) {
List<String> fieldList = modelProviderService.getFieldList(modelType, provideCode); List<String> fieldList = modelProviderService.getFieldList(modelType, provideCode);
return new Result<List<String>>().ok(fieldList); return new Result<List<String>>().ok(fieldList);
} }
@GetMapping("/models/list") @GetMapping("/models/list")
@Operation(summary = "获取模型配置列表") @Operation(summary = "获取模型配置列表")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
public Result<PageData<ModelConfigDTO>> getModelConfigList(@RequestParam String modelType, public Result<PageData<ModelConfigDTO>> getModelConfigList(@RequestParam String modelType,
@RequestParam(required = false) String modelName, @RequestParam(required = false) String modelName,
@RequestParam(required = false, defaultValue = "0") Integer page, @RequestParam(required = false, defaultValue = "0") Integer page,
@RequestParam(required = false,defaultValue = "10") Integer limit) { @RequestParam(required = false, defaultValue = "10") Integer limit) {
PageData<ModelConfigDTO> pageList = modelConfigService.getPageList(modelType, modelName, page, limit); PageData<ModelConfigDTO> pageList = modelConfigService.getPageList(modelType, modelName, page, limit);
return new Result<PageData<ModelConfigDTO>>().ok(pageList); return new Result<PageData<ModelConfigDTO>>().ok(pageList);
} }
@PostMapping("/models/{modelType}/{provideCode}") @PostMapping("/models/{modelType}/{provideCode}")
@Operation(summary = "新增模型配置") @Operation(summary = "新增模型配置")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
public Result<ModelConfigDTO> addModelConfig(@PathVariable String modelType, public Result<ModelConfigDTO> addModelConfig(@PathVariable String modelType,
@PathVariable String provideCode, @PathVariable String provideCode,
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) { @RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
ModelConfigDTO modelConfigDTO = modelConfigService.add(modelType, provideCode, modelConfigBodyDTO); ModelConfigDTO modelConfigDTO = modelConfigService.add(modelType, provideCode, modelConfigBodyDTO);
return new Result<ModelConfigDTO>().ok(modelConfigDTO); return new Result<ModelConfigDTO>().ok(modelConfigDTO);
} }
@PutMapping("/models/{modelType}/{provideCode}/{id}") @PutMapping("/models/{modelType}/{provideCode}/{id}")
@Operation(summary = "编辑模型配置") @Operation(summary = "编辑模型配置")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
public Result<ModelConfigDTO> editModelConfig(@PathVariable String modelType, public Result<ModelConfigDTO> editModelConfig(@PathVariable String modelType,
@PathVariable String provideCode, @PathVariable String provideCode,
@PathVariable String id, @PathVariable String id,
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) { @RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
ModelConfigDTO modelConfigDTO = modelConfigService.edit(modelType, provideCode, id, modelConfigBodyDTO); ModelConfigDTO modelConfigDTO = modelConfigService.edit(modelType, provideCode, id, modelConfigBodyDTO);
return new Result<ModelConfigDTO>().ok(modelConfigDTO); return new Result<ModelConfigDTO>().ok(modelConfigDTO);
} }
@DeleteMapping("/models/{modelType}/{provideCode}/{id}") @DeleteMapping("/models/{modelType}/{provideCode}/{id}")
@Operation(summary = "删除模型配置") @Operation(summary = "删除模型配置")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
public Result<Void> deleteModelConfig(@PathVariable String modelType, @PathVariable String provideCode, @PathVariable String id) { public Result<Void> deleteModelConfig(@PathVariable String modelType, @PathVariable String provideCode,
@PathVariable String id) {
modelConfigService.delete(modelType, provideCode, id); modelConfigService.delete(modelType, provideCode, id);
return new Result<>(); return new Result<>();
} }
@@ -98,7 +105,7 @@ public class ModelController {
@Operation(summary = "获取模型音色") @Operation(summary = "获取模型音色")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
public Result<List<String>> getVoiceList(@PathVariable String modelName, public Result<List<String>> getVoiceList(@PathVariable String modelName,
@RequestParam(required = false) String voiceName) { @RequestParam(required = false) String voiceName) {
List<String> voiceList = modelConfigService.getVoiceList(modelName, voiceName); List<String> voiceList = modelConfigService.getVoiceList(modelName, voiceName);
return new Result<List<String>>().ok(voiceList); return new Result<List<String>>().ok(voiceList);
@@ -1,12 +1,13 @@
package xiaozhi.modules.model.dao; package xiaozhi.modules.model.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import xiaozhi.common.dao.BaseDao; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.model.entity.ModelConfigEntity; import xiaozhi.modules.model.entity.ModelConfigEntity;
import java.util.List;
@Mapper @Mapper
public interface ModelConfigDao extends BaseDao<ModelConfigEntity> { public interface ModelConfigDao extends BaseDao<ModelConfigEntity> {
@@ -1,12 +1,13 @@
package xiaozhi.modules.model.dao; package xiaozhi.modules.model.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import xiaozhi.common.dao.BaseDao; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.model.entity.ModelProviderEntity; import xiaozhi.modules.model.entity.ModelProviderEntity;
import java.util.List;
@Mapper @Mapper
public interface ModelProviderDao extends BaseDao<ModelProviderEntity> { public interface ModelProviderDao extends BaseDao<ModelProviderEntity> {
@@ -1,10 +1,10 @@
package xiaozhi.modules.model.dto; package xiaozhi.modules.model.dto;
import java.io.Serial;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.io.Serial;
@Data @Data
@Schema(description = "模型供应器/商") @Schema(description = "模型供应器/商")
public class ModelConfigBodyDTO { public class ModelConfigBodyDTO {
@@ -12,9 +12,9 @@ public class ModelConfigBodyDTO {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
// @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)") // @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
// private String modelType; // private String modelType;
// //
@Schema(description = "模型编码(如AliLLM、DoubaoTTS)") @Schema(description = "模型编码(如AliLLM、DoubaoTTS)")
private String modelCode; private String modelCode;
@@ -1,11 +1,11 @@
package xiaozhi.modules.model.dto; package xiaozhi.modules.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data @Data
@Schema(description = "模型供应器/商") @Schema(description = "模型供应器/商")
public class ModelConfigDTO implements Serializable { public class ModelConfigDTO implements Serializable {
@@ -1,19 +1,20 @@
package xiaozhi.modules.model.dto; 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.io.Serializable;
import java.util.Date; 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 @Data
@Schema(description = "模型供应器/商") @Schema(description = "模型供应器/商")
public class ModelProviderDTO implements Serializable { public class ModelProviderDTO implements Serializable {
// //
// @Schema(description = "主键") // @Schema(description = "主键")
// private Long id; // private Long id;
@Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)") @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
private String modelType; private String modelType;
@@ -1,16 +1,16 @@
package xiaozhi.modules.model.entity; package xiaozhi.modules.model.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.util.Date;
@Data @Data
@TableName("ai_model_config") @TableName("ai_model_config")
@Schema(description = "模型配置表") @Schema(description = "模型配置表")
@@ -1,15 +1,16 @@
package xiaozhi.modules.model.entity; package xiaozhi.modules.model.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.util.Date;
@Data @Data
@TableName("ai_model_provider") @TableName("ai_model_provider")
@Schema(description = "模型供应器表") @Schema(description = "模型供应器表")
@@ -1,11 +1,11 @@
package xiaozhi.modules.model.service; package xiaozhi.modules.model.service;
import java.util.List;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.modules.model.dto.ModelConfigBodyDTO; import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
import xiaozhi.modules.model.dto.ModelConfigDTO; import xiaozhi.modules.model.dto.ModelConfigDTO;
import java.util.List;
public interface ModelConfigService { public interface ModelConfigService {
List<String> getModelCodeList(String modelType, String modelName); List<String> getModelCodeList(String modelType, String modelName);
@@ -1,13 +1,13 @@
package xiaozhi.modules.model.service; package xiaozhi.modules.model.service;
import java.util.List;
import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity; import xiaozhi.modules.model.entity.ModelProviderEntity;
import java.util.List;
public interface ModelProviderService { public interface ModelProviderService {
// List<String> getModelNames(String modelType, String modelName); // List<String> getModelNames(String modelType, String modelName);
List<ModelProviderDTO> getListByModelType(String modelType); List<ModelProviderDTO> getListByModelType(String modelType);
@@ -1,13 +1,19 @@
package xiaozhi.modules.model.service.impl; package xiaozhi.modules.model.service.impl;
import cn.hutool.core.collection.CollectionUtil; import java.util.HashMap;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import java.util.List;
import com.baomidou.mybatisplus.core.metadata.IPage; import java.util.Map;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; 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.constant.Constant;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
@@ -22,13 +28,10 @@ import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.model.service.ModelProviderService; import xiaozhi.modules.model.service.ModelProviderService;
import xiaozhi.modules.timbre.service.TimbreService; import xiaozhi.modules.timbre.service.TimbreService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service @Service
@AllArgsConstructor @AllArgsConstructor
public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, ModelConfigEntity> implements ModelConfigService { public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, ModelConfigEntity>
implements ModelConfigService {
private final ModelConfigDao modelConfigDao; private final ModelConfigDao modelConfigDao;
private final ModelProviderService modelProviderService; private final ModelProviderService modelProviderService;
@@ -50,8 +53,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
getPage(params, "sort", true), getPage(params, "sort", true),
new QueryWrapper<ModelConfigEntity>() new QueryWrapper<ModelConfigEntity>()
.eq("model_type", modelType) .eq("model_type", modelType)
.like(StringUtils.isNotBlank(modelName), "model_name", modelName) .like(StringUtils.isNotBlank(modelName), "model_name", modelName));
);
return getPageData(modelConfigEntityIPage, ModelConfigDTO.class); return getPageData(modelConfigEntityIPage, ModelConfigDTO.class);
} }
@@ -119,6 +121,6 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
ModelConfigEntity modelConfigEntity = modelConfigEntities.get(0); ModelConfigEntity modelConfigEntity = modelConfigEntities.get(0);
String id = modelConfigEntity.getId(); String id = modelConfigEntity.getId();
return timbreService.getVoiceNames(id, voiceName); return timbreService.getVoiceNames(id, voiceName);
} }
} }
@@ -1,9 +1,13 @@
package xiaozhi.modules.model.service.impl; package xiaozhi.modules.model.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import java.util.List;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.AllArgsConstructor;
import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.modules.model.dao.ModelProviderDao; import xiaozhi.modules.model.dao.ModelProviderDao;
@@ -11,11 +15,10 @@ import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity; import xiaozhi.modules.model.entity.ModelProviderEntity;
import xiaozhi.modules.model.service.ModelProviderService; import xiaozhi.modules.model.service.ModelProviderService;
import java.util.List;
@Service @Service
@AllArgsConstructor @AllArgsConstructor
public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao, ModelProviderEntity> implements ModelProviderService { public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao, ModelProviderEntity>
implements ModelProviderService {
private final ModelProviderDao modelProviderDao; private final ModelProviderDao modelProviderDao;
@@ -5,7 +5,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy; import org.springframework.web.filter.DelegatingFilterProxy;
/** /**
* Filter配置 * Filter配置
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -18,7 +17,7 @@ public class FilterConfig {
public FilterRegistrationBean<DelegatingFilterProxy> shiroFilterRegistration() { public FilterRegistrationBean<DelegatingFilterProxy> shiroFilterRegistration() {
FilterRegistrationBean<DelegatingFilterProxy> registration = new FilterRegistrationBean<>(); FilterRegistrationBean<DelegatingFilterProxy> registration = new FilterRegistrationBean<>();
registration.setFilter(new DelegatingFilterProxy("shiroFilter")); registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
//该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 // 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
registration.addInitParameter("targetFilterLifecycle", "true"); registration.addInitParameter("targetFilterLifecycle", "true");
registration.setEnabled(true); registration.setEnabled(true);
registration.setOrder(Integer.MAX_VALUE - 1); registration.setOrder(Integer.MAX_VALUE - 1);
@@ -1,8 +1,9 @@
package xiaozhi.modules.security.config; package xiaozhi.modules.security.config;
import xiaozhi.modules.security.oauth2.Oauth2Filter; import java.util.HashMap;
import xiaozhi.modules.security.oauth2.Oauth2Realm; import java.util.LinkedHashMap;
import jakarta.servlet.Filter; import java.util.Map;
import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import java.util.HashMap; import jakarta.servlet.Filter;
import java.util.LinkedHashMap; import xiaozhi.modules.security.oauth2.Oauth2Filter;
import java.util.Map; import xiaozhi.modules.security.oauth2.Oauth2Realm;
/** /**
* Shiro的配置文件 * Shiro的配置文件
@@ -53,18 +54,21 @@ public class ShiroConfig {
shiroFilter.setSecurityManager(securityManager); shiroFilter.setSecurityManager(securityManager);
shiroFilter.setShiroFilterConfiguration(config); shiroFilter.setShiroFilterConfiguration(config);
//oauth过滤 // oauth过滤
Map<String, Filter> filters = new HashMap<>(); Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new Oauth2Filter()); filters.put("oauth2", new Oauth2Filter());
shiroFilter.setFilters(filters); shiroFilter.setFilters(filters);
//添加Shiro的内置过滤器 // 添加Shiro的内置过滤器
/*anon:无需认证就可以访问 /*
authc:必须认证了才能让 * anon:无需认证就可以访
user:必须拥有,记住我功能,才能访 * authc:必须认证了才能
perms:拥有对某个资源的权限才能访问 * user:必须拥有,记住我功能,才能访问
role:拥有某个角色权限才能访问*/ * perms:拥有某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>(); Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/ota/**", "anon");
filterMap.put("/webjars/**", "anon"); filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon"); filterMap.put("/druid/**", "anon");
filterMap.put("/v3/api-docs/**", "anon"); filterMap.put("/v3/api-docs/**", "anon");
@@ -1,9 +1,8 @@
package xiaozhi.modules.security.config; package xiaozhi.modules.security.config;
import com.fasterxml.jackson.databind.DeserializationFeature; import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper; import java.util.TimeZone;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.ByteArrayHttpMessageConverter; 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.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List; import com.fasterxml.jackson.databind.DeserializationFeature;
import java.util.TimeZone; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
@Configuration @Configuration
public class WebMvcConfig implements WebMvcConfigurer { public class WebMvcConfig implements WebMvcConfigurer {
@@ -45,14 +46,14 @@ public class WebMvcConfig implements WebMvcConfigurer {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
//忽略未知属性 // 忽略未知属性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 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")); mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
//Long类型转String类型 // Long类型转String类型
SimpleModule simpleModule = new SimpleModule(); SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
@@ -1,12 +1,18 @@
package xiaozhi.modules.security.controller; 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.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor; 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.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.TokenDTO; 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.dto.SysUserDTO;
import xiaozhi.modules.sys.service.SysUserService; import xiaozhi.modules.sys.service.SysUserService;
import java.io.IOException;
/** /**
* 登录控制层 * 登录控制层
*/ */
@@ -36,14 +40,13 @@ public class LoginController {
private final SysUserTokenService sysUserTokenService; private final SysUserTokenService sysUserTokenService;
private final CaptchaService captchaService; private final CaptchaService captchaService;
@GetMapping("/captcha") @GetMapping("/captcha")
@Operation(summary = "验证码") @Operation(summary = "验证码")
public void captcha(HttpServletResponse response, String uuid) throws IOException { public void captcha(HttpServletResponse response, String uuid) throws IOException {
//uuid不能为空 // uuid不能为空
AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL); AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL);
//生成验证码 // 生成验证码
captchaService.create(response, uuid); captchaService.create(response, uuid);
} }
@@ -1,12 +1,13 @@
package xiaozhi.modules.security.dao; package xiaozhi.modules.security.dao;
import java.util.Date;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import xiaozhi.common.dao.BaseDao; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.security.entity.SysUserTokenEntity; import xiaozhi.modules.security.entity.SysUserTokenEntity;
import java.util.Date;
/** /**
* 系统用户Token * 系统用户Token
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,11 +1,11 @@
package xiaozhi.modules.security.dto; package xiaozhi.modules.security.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
/** /**
* 登录表单 * 登录表单
*/ */
@@ -1,13 +1,14 @@
package xiaozhi.modules.security.entity; package xiaozhi.modules.security.entity;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable; import lombok.Data;
import java.util.Date;
/** /**
* 系统用户Token * 系统用户Token
@@ -1,9 +1,7 @@
package xiaozhi.modules.security.oauth2; package xiaozhi.modules.security.oauth2;
import jakarta.servlet.ServletRequest; import java.io.IOException;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod; 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.constant.Constant;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.utils.HttpContextUtils; import xiaozhi.common.utils.HttpContextUtils;
import xiaozhi.common.utils.JsonUtils; import xiaozhi.common.utils.JsonUtils;
import xiaozhi.common.utils.Result; import xiaozhi.common.utils.Result;
import java.io.IOException;
/** /**
* oauth2过滤器 * oauth2过滤器
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -30,7 +31,7 @@ public class Oauth2Filter extends AuthenticatingFilter {
@Override @Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token // 获取请求token
String token = getRequestToken((HttpServletRequest) request); String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) { if (StringUtils.isBlank(token)) {
@@ -52,14 +53,9 @@ public class Oauth2Filter extends AuthenticatingFilter {
@Override @Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401 // 获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request); String token = getRequestToken((HttpServletRequest) request);
// TODO 调试接口,临时取消登录限制,需要 token 参数的除外
if (true) {
return true;
}
if (StringUtils.isBlank(token)) { if (StringUtils.isBlank(token)) {
logger.warn("onAccessDenied:token is empty"); 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-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin()); httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
String json = JsonUtils.toJsonString(new Result().error(ErrorCode.UNAUTHORIZED)); String json = JsonUtils.toJsonString(new Result<Void>().error(ErrorCode.UNAUTHORIZED));
httpResponse.getWriter().print(json); httpResponse.getWriter().print(json);
@@ -79,16 +75,17 @@ public class Oauth2Filter extends AuthenticatingFilter {
} }
@Override @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; HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8"); httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin()); httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
try { try {
//处理登录失败的异常 // 处理登录失败的异常
logger.error("onLoginFailure:登录失败!", e); logger.error("onLoginFailure:登录失败!", e);
Throwable throwable = e.getCause() == null ? e : e.getCause(); Throwable throwable = e.getCause() == null ? e : e.getCause();
Result r = new Result().error(ErrorCode.UNAUTHORIZED, throwable.getMessage()); Result<Void> r = new Result<Void>().error(ErrorCode.UNAUTHORIZED, throwable.getMessage());
String json = JsonUtils.toJsonString(r); String json = JsonUtils.toJsonString(r);
httpResponse.getWriter().print(json); httpResponse.getWriter().print(json);
@@ -104,7 +101,7 @@ public class Oauth2Filter extends AuthenticatingFilter {
*/ */
private String getRequestToken(HttpServletRequest httpRequest) { private String getRequestToken(HttpServletRequest httpRequest) {
String token = null; String token = null;
//从header中获取token // 从header中获取token
String authorization = httpRequest.getHeader(Constant.AUTHORIZATION); String authorization = httpRequest.getHeader(Constant.AUTHORIZATION);
if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) { if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) {
token = authorization.replace("Bearer ", ""); token = authorization.replace("Bearer ", "");
@@ -1,7 +1,15 @@
package xiaozhi.modules.security.oauth2; package xiaozhi.modules.security.oauth2;
import jakarta.annotation.Resource; import java.util.HashSet;
import org.apache.shiro.authc.*; 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.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.realm.AuthorizingRealm;
@@ -10,6 +18,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.user.UserDetail; import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils; 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.entity.SysUserEntity;
import xiaozhi.modules.sys.enums.SuperAdminEnum; import xiaozhi.modules.sys.enums.SuperAdminEnum;
import java.util.HashSet;
import java.util.Set;
/** /**
* 认证 * 认证
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -47,7 +54,7 @@ public class Oauth2Realm extends AuthorizingRealm {
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
UserDetail user = (UserDetail) principals.getPrimaryPrincipal(); UserDetail user = (UserDetail) principals.getPrimaryPrincipal();
//用户权限列表 // 用户权限列表
Set<String> permsSet = new HashSet<>(); Set<String> permsSet = new HashSet<>();
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) { if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
@@ -69,22 +76,22 @@ public class Oauth2Realm extends AuthorizingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal(); String accessToken = (String) token.getPrincipal();
//根据accessToken,查询用户信息 // 根据accessToken,查询用户信息
SysUserTokenEntity tokenEntity = shiroService.getByToken(accessToken); SysUserTokenEntity tokenEntity = shiroService.getByToken(accessToken);
//token失效 // token失效
if (tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) { if (tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID)); throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID));
} }
//查询用户信息 // 查询用户信息
SysUserEntity userEntity = shiroService.getUser(tokenEntity.getUserId()); SysUserEntity userEntity = shiroService.getUser(tokenEntity.getUserId());
//转换成UserDetail对象 // 转换成UserDetail对象
UserDetail userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class); UserDetail userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);
userDetail.setToken(accessToken); userDetail.setToken(accessToken);
//账号锁定 // 账号锁定
if (userDetail.getStatus() == null) { if (userDetail.getStatus() == null) {
logger.error("账号状态异常,status 不能为空"); logger.error("账号状态异常,status 不能为空");
throw new DisabledAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_DISABLE)); throw new DisabledAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_DISABLE));
@@ -1,10 +1,10 @@
package xiaozhi.modules.security.oauth2; package xiaozhi.modules.security.oauth2;
import xiaozhi.common.exception.RenException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.UUID; import java.util.UUID;
import xiaozhi.common.exception.RenException;
/** /**
* 生成token * 生成token
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -5,22 +5,28 @@ import java.io.UnsupportedEncodingException;
import java.security.SecureRandom; 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. * "A Future-Adaptable Password Scheme" by Niels Provos and David Mazieres.
* <p> * <p>
* This password hashing system tries to thwart off-line password cracking using a * This password hashing system tries to thwart off-line password cracking using
* computationally-intensive hashing algorithm, based on Bruce Schneier's Blowfish cipher. * a
* The work factor of the algorithm is parameterised, so it can be increased as computers * 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. * get faster.
* <p> * <p>
* Usage is really simple. To hash a password for the first time, call the hashpw method * Usage is really simple. To hash a password for the first time, call the
* hashpw method
* with a random salt, like this: * with a random salt, like this:
* <p> * <p>
* <code> * <code>
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br> * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br>
* </code> * </code>
* <p> * <p>
* To check whether a plaintext password matches one that has been hashed previously, use * To check whether a plaintext password matches one that has been hashed
* previously, use
* the checkpw method: * the checkpw method:
* <p> * <p>
* <code> * <code>
@@ -30,7 +36,8 @@ import java.security.SecureRandom;
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It does not match");<br> * &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It does not match");<br>
* </code> * </code>
* <p> * <p>
* The gensalt() method takes an optional parameter (log_rounds) that determines the * The gensalt() method takes an optional parameter (log_rounds) that determines
* the
* computational complexity of the hashing: * computational complexity of the hashing:
* <p> * <p>
* <code> * <code>
@@ -38,7 +45,8 @@ import java.security.SecureRandom;
* String stronger_salt = BCrypt.gensalt(12)<br> * String stronger_salt = BCrypt.gensalt(12)<br>
* </code> * </code>
* <p> * <p>
* The amount of work increases exponentially (2**log_rounds), so each increment is twice * The amount of work increases exponentially (2**log_rounds), so each increment
* is twice
* as much work. The default log_rounds is 10, and the valid range is 4 to 31. * as much work. The default log_rounds is 10, and the valid range is 4 to 31.
* *
* @author Damien Miller * @author Damien Miller
@@ -51,11 +59,11 @@ public class BCrypt {
// Blowfish parameters // Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16; private static final int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule // 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, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377,
0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b}; 0x9216d5d9, 0x8979fb1b };
private static final int S_orig[] = {0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, private static final int S_orig[] = { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7,
0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5,
@@ -225,24 +233,24 @@ public class BCrypt {
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76,
0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0,
0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6}; 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 };
// bcrypt IV: "OrpheanBeholderScryDoubt" // bcrypt IV: "OrpheanBeholderScryDoubt"
static private final int bf_crypt_ciphertext[] = {0x4f727068, 0x65616e42, static private final int bf_crypt_ciphertext[] = { 0x4f727068, 0x65616e42,
0x65686f6c, 0x64657253, 0x63727944, 0x6f756274}; 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 };
// Table for Base64 encoding // 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', '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', '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', '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 // 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, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, -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, 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, 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, -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 MIN_LOG_ROUNDS = 4;
static final int MAX_LOG_ROUNDS = 31; static final int MAX_LOG_ROUNDS = 31;
// Expanded Blowfish key // Expanded Blowfish key
@@ -250,7 +258,8 @@ public class BCrypt {
private int S[]; private int S[];
/** /**
* Encode a byte array using bcrypt's slightly-modified base64 encoding scheme. Note * Encode a byte array using bcrypt's slightly-modified base64 encoding scheme.
* Note
* that this is <strong>not</strong> compatible with the standard MIME-base64 * that this is <strong>not</strong> compatible with the standard MIME-base64
* encoding. * 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. * this is *not* compatible with the standard MIME-base64 encoding.
* *
* @param s the string to decode * @param s the string to decode
@@ -365,7 +375,7 @@ public class BCrypt {
int i, n, l = lr[off], r = lr[off + 1]; int i, n, l = lr[off], r = lr[off + 1];
l ^= P[0]; l ^= P[0];
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2; ) { for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
// Feistel substitution on left word // Feistel substitution on left word
n = S[(l >> 24) & 0xff]; n = S[(l >> 24) & 0xff];
n += S[0x100 | ((l >> 16) & 0xff)]; n += S[0x100 | ((l >> 16) & 0xff)];
@@ -388,7 +398,8 @@ public class BCrypt {
* Cycically extract a word of key material * Cycically extract a word of key material
* *
* @param data the string to extract the data from * @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 * @return the next word of material from data
*/ */
private static int streamtoword(byte data[], int offp[]) { private static int streamtoword(byte data[], int offp[]) {
@@ -420,8 +431,8 @@ public class BCrypt {
*/ */
private void key(byte key[]) { private void key(byte key[]) {
int i; int i;
int koffp[] = {0}; int koffp[] = { 0 };
int lr[] = {0, 0}; int lr[] = { 0, 0 };
int plen = P.length, slen = S.length; int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++) { 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 * 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 data salt information
* @param key password information * @param key password information
*/ */
private void ekskey(byte data[], byte key[]) { private void ekskey(byte data[], byte key[]) {
int i; int i;
int koffp[] = {0}, doffp[] = {0}; int koffp[] = { 0 }, doffp[] = { 0 };
int lr[] = {0, 0}; int lr[] = { 0, 0 };
int plen = P.length, slen = S.length; int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++) { for (i = 0; i < plen; i++) {
@@ -487,7 +499,8 @@ public class BCrypt {
* *
* @param password the password to hash * @param password the password to hash
* @param salt the binary salt to hash with the password * @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 * @return an array containing the binary hashed password
*/ */
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds) { 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 * Hash a password using the OpenBSD bcrypt scheme
* *
* @param password the password to hash * @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 * @return the hashed password
* @throws IllegalArgumentException if invalid salt is passed * @throws IllegalArgumentException if invalid salt is passed
*/ */
@@ -599,8 +613,10 @@ public class BCrypt {
/** /**
* Generate a salt for use with the BCrypt.hashpw() method * 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 * @param log_rounds the log2 of the number of rounds of hashing to apply - the
* factor therefore increases as 2**log_rounds. Minimum 4, maximum 31. * work
* factor therefore increases as 2**log_rounds. Minimum 4,
* maximum 31.
* @param random an instance of SecureRandom to use * @param random an instance of SecureRandom to use
* @return an encoded salt value * @return an encoded salt value
*/ */
@@ -626,8 +642,10 @@ public class BCrypt {
/** /**
* Generate a salt for use with the BCrypt.hashpw() method * 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 * @param log_rounds the log2 of the number of rounds of hashing to apply - the
* factor therefore increases as 2**log_rounds. Minimum 4, maximum 31. * work
* factor therefore increases as 2**log_rounds. Minimum 4,
* maximum 31.
* @return an encoded salt value * @return an encoded salt value
*/ */
public static String gensalt(int log_rounds) { 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 * default for the number of hashing rounds to apply
* *
* @return an encoded salt value * @return an encoded salt value
@@ -1,15 +1,18 @@
package xiaozhi.modules.security.password; package xiaozhi.modules.security.password;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.regex.Pattern; 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 * Implementation of PasswordEncoder that uses the BCrypt strong hashing
* can optionally supply a "strength" (a.k.a. log rounds in BCrypt) and a SecureRandom * function. Clients
* instance. The larger the strength parameter the more work will have to be done * 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. * (exponentially) to hash the passwords. The default value is 10.
* *
* @author Dave Syer * @author Dave Syer
@@ -10,20 +10,23 @@ package xiaozhi.modules.security.password;
public interface PasswordEncoder { 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. * greater hash combined with an 8-byte or greater randomly generated salt.
*/ */
String encode(CharSequence rawPassword); String encode(CharSequence rawPassword);
/** /**
* Verify the encoded password obtained from storage matches the submitted raw * 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. * they do not. The stored password itself is never decoded.
* *
* @param rawPassword the raw password to encode and match * @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with * @param encodedPassword the encoded password from storage to compare with
* @return true if the raw password, after encoding, matches the encoded password from * @return true if the raw password, after encoding, matches the encoded
* storage * password from
* storage
*/ */
boolean matches(CharSequence rawPassword, String encodedPassword); boolean matches(CharSequence rawPassword, String encodedPassword);
@@ -18,19 +18,17 @@ public class PasswordUtils {
return passwordEncoder.encode(str); return passwordEncoder.encode(str);
} }
/** /**
* 比较密码是否相等 * 比较密码是否相等
* *
* @param str 明文密码 * @param str 明文密码
* @param password 加密后密码 * @param password 加密后密码
* @return true:成功 false:失败 * @return true:成功 false:失败
*/ */
public static boolean matches(String str, String password) { public static boolean matches(String str, String password) {
return passwordEncoder.matches(str, password); return passwordEncoder.matches(str, password);
} }
public static void main(String[] args) { public static void main(String[] args) {
String str = "admin"; String str = "admin";
String password = encode(str); String password = encode(str);
@@ -1,9 +1,9 @@
package xiaozhi.modules.security.service; package xiaozhi.modules.security.service;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import jakarta.servlet.http.HttpServletResponse;
/** /**
* 验证码 * 验证码
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -21,7 +21,7 @@ public interface CaptchaService {
* *
* @param uuid uuid * @param uuid uuid
* @param code 验证码 * @param code 验证码
* @return true:成功 false:失败 * @return true:成功 false:失败
*/ */
boolean validate(String uuid, String code); boolean validate(String uuid, String code);
} }
@@ -1,23 +1,22 @@
package xiaozhi.modules.security.service.impl; 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.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.wf.captcha.SpecCaptcha; import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha; 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.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import xiaozhi.common.redis.RedisKeys;
import org.springframework.beans.factory.annotation.Value; import xiaozhi.common.redis.RedisUtils;
import org.springframework.stereotype.Service; import xiaozhi.modules.security.service.CaptchaService;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/** /**
* 验证码 * 验证码
@@ -29,10 +28,10 @@ public class CaptchaServiceImpl implements CaptchaService {
@Value("${renren.redis.open}") @Value("${renren.redis.open}")
private boolean open; private boolean open;
/** /**
* Local Cache 5分钟过期 * Local Cache 5分钟过期
*/ */
Cache<String, String> localCache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(5, TimeUnit.MINUTES).build(); Cache<String, String> localCache = CacheBuilder.newBuilder().maximumSize(1000)
private static final Logger logger = LoggerFactory.getLogger(Oauth2Realm.class); .expireAfterAccess(5, TimeUnit.MINUTES).build();
@Override @Override
public void create(HttpServletResponse response, String uuid) throws IOException { 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.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0); response.setDateHeader("Expires", 0);
//生成验证码 // 生成验证码
SpecCaptcha captcha = new SpecCaptcha(150, 40); SpecCaptcha captcha = new SpecCaptcha(150, 40);
captcha.setLen(5); captcha.setLen(5);
captcha.setCharType(Captcha.TYPE_DEFAULT); captcha.setCharType(Captcha.TYPE_DEFAULT);
captcha.out(response.getOutputStream()); captcha.out(response.getOutputStream());
//保存到缓存 // 保存到缓存
setCache(uuid, captcha.text()); setCache(uuid, captcha.text());
} }
@@ -56,10 +55,10 @@ public class CaptchaServiceImpl implements CaptchaService {
if (StringUtils.isBlank(code)) { if (StringUtils.isBlank(code)) {
return false; return false;
} }
//获取验证码 // 获取验证码
String captcha = getCache(uuid); String captcha = getCache(uuid);
//效验成功 // 效验成功
if (code.equalsIgnoreCase(captcha)) { if (code.equalsIgnoreCase(captcha)) {
return true; return true;
} }
@@ -80,7 +79,7 @@ public class CaptchaServiceImpl implements CaptchaService {
if (open) { if (open) {
key = RedisKeys.getCaptchaKey(key); key = RedisKeys.getCaptchaKey(key);
String captcha = (String) redisUtils.get(key); String captcha = (String) redisUtils.get(key);
//删除验证码 // 删除验证码
if (captcha != null) { if (captcha != null) {
redisUtils.delete(key); redisUtils.delete(key);
} }
@@ -89,7 +88,7 @@ public class CaptchaServiceImpl implements CaptchaService {
} }
String captcha = localCache.getIfPresent(key); String captcha = localCache.getIfPresent(key);
//删除验证码 // 删除验证码
if (captcha != null) { if (captcha != null) {
localCache.invalidate(key); localCache.invalidate(key);
} }
@@ -1,17 +1,13 @@
package xiaozhi.modules.security.service.impl; 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.dao.SysUserTokenDao;
import xiaozhi.modules.security.entity.SysUserTokenEntity; import xiaozhi.modules.security.entity.SysUserTokenEntity;
import xiaozhi.modules.security.service.ShiroService; import xiaozhi.modules.security.service.ShiroService;
import xiaozhi.modules.sys.dao.SysUserDao; import xiaozhi.modules.sys.dao.SysUserDao;
import xiaozhi.modules.sys.entity.SysUserEntity; 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 @AllArgsConstructor
@Service @Service
@@ -19,7 +15,6 @@ public class ShiroServiceImpl implements ShiroService {
private final SysUserDao sysUserDao; private final SysUserDao sysUserDao;
private final SysUserTokenDao sysUserTokenDao; private final SysUserTokenDao sysUserTokenDao;
@Override @Override
public SysUserTokenEntity getByToken(String token) { public SysUserTokenEntity getByToken(String token) {
return sysUserTokenDao.getByToken(token); return sysUserTokenDao.getByToken(token);
@@ -1,8 +1,11 @@
package xiaozhi.modules.security.service.impl; package xiaozhi.modules.security.service.impl;
import java.util.Date;
import org.springframework.stereotype.Service;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.TokenDTO; 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.dto.SysUserDTO;
import xiaozhi.modules.sys.service.SysUserService; import xiaozhi.modules.sys.service.SysUserService;
import java.util.Date;
@AllArgsConstructor @AllArgsConstructor
@Service @Service
public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, SysUserTokenEntity> implements SysUserTokenService { public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, SysUserTokenEntity>
implements SysUserTokenService {
private final SysUserService sysUserService; private final SysUserService sysUserService;
/** /**
@@ -31,18 +33,18 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, Sy
@Override @Override
public Result<TokenDTO> createToken(Long userId) { public Result<TokenDTO> createToken(Long userId) {
//用户token // 用户token
String token; String token;
//当前时间 // 当前时间
Date now = new Date(); Date now = new Date();
//过期时间 // 过期时间
Date expireTime = new Date(now.getTime() + EXPIRE * 1000); Date expireTime = new Date(now.getTime() + EXPIRE * 1000);
//判断是否生成过token // 判断是否生成过token
SysUserTokenEntity tokenEntity = baseDao.getByUserId(userId); SysUserTokenEntity tokenEntity = baseDao.getByUserId(userId);
if (tokenEntity == null) { if (tokenEntity == null) {
//生成一个token // 生成一个token
token = TokenGenerator.generateValue(); token = TokenGenerator.generateValue();
tokenEntity = new SysUserTokenEntity(); tokenEntity = new SysUserTokenEntity();
@@ -51,12 +53,12 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, Sy
tokenEntity.setUpdateDate(now); tokenEntity.setUpdateDate(now);
tokenEntity.setExpireDate(expireTime); tokenEntity.setExpireDate(expireTime);
//保存token // 保存token
this.insert(tokenEntity); this.insert(tokenEntity);
} else { } else {
//判断token是否过期 // 判断token是否过期
if (tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) { if (tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
//token过期,重新生成token // token过期,重新生成token
token = TokenGenerator.generateValue(); token = TokenGenerator.generateValue();
} else { } else {
token = tokenEntity.getToken(); token = tokenEntity.getToken();
@@ -66,7 +68,7 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, Sy
tokenEntity.setUpdateDate(now); tokenEntity.setUpdateDate(now);
tokenEntity.setExpireDate(expireTime); tokenEntity.setExpireDate(expireTime);
//更新token // 更新token
this.updateById(tokenEntity); this.updateById(tokenEntity);
} }
@@ -76,7 +78,7 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, Sy
tokenDTO.setToken(token); tokenDTO.setToken(token);
tokenDTO.setExpire(EXPIRE); tokenDTO.setExpire(EXPIRE);
tokenDTO.setClientHash(clientHash); tokenDTO.setClientHash(clientHash);
return new Result().ok(tokenDTO); return new Result<TokenDTO>().ok(tokenDTO);
} }
@Override @Override
@@ -1,9 +1,10 @@
package xiaozhi.modules.security.user; package xiaozhi.modules.security.user;
import xiaozhi.common.user.UserDetail;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import xiaozhi.common.user.UserDetail;
/** /**
* Shiro工具类 * Shiro工具类
* Copyright (c) 人人开源 All rights reserved. * Copyright (c) 人人开源 All rights reserved.
@@ -1,9 +1,10 @@
package xiaozhi.modules.sys.controller; 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 io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/** /**
* 用户管理 * 用户管理
@@ -1,12 +1,13 @@
package xiaozhi.modules.sys.dao; package xiaozhi.modules.sys.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import xiaozhi.common.dao.BaseDao; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.sys.dto.SysDictDataDTO; import xiaozhi.modules.sys.dto.SysDictDataDTO;
import xiaozhi.modules.sys.entity.DictData; import xiaozhi.modules.sys.entity.DictData;
import xiaozhi.modules.sys.entity.SysDictDataEntity; import xiaozhi.modules.sys.entity.SysDictDataEntity;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/** /**
* 字典数据 * 字典数据
@@ -1,11 +1,12 @@
package xiaozhi.modules.sys.dao; package xiaozhi.modules.sys.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import xiaozhi.common.dao.BaseDao; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.sys.entity.DictType; import xiaozhi.modules.sys.entity.DictType;
import xiaozhi.modules.sys.entity.SysDictTypeEntity; import xiaozhi.modules.sys.entity.SysDictTypeEntity;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/** /**
* 字典类型 * 字典类型
@@ -1,11 +1,12 @@
package xiaozhi.modules.sys.dao; package xiaozhi.modules.sys.dao;
import xiaozhi.common.dao.BaseDao; import java.util.List;
import xiaozhi.modules.sys.entity.SysParamsEntity;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.sys.entity.SysParamsEntity;
/** /**
* 参数管理 * 参数管理
@@ -1,6 +1,7 @@
package xiaozhi.modules.sys.dao; package xiaozhi.modules.sys.dao;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import xiaozhi.common.dao.BaseDao; import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.sys.entity.SysUserEntity; import xiaozhi.modules.sys.entity.SysUserEntity;

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