Web 模块开发文档
1. 模块概述
1.1 模块简介
BeyondSoft Web Spring3 Starter 是一个基于 Spring Boot 3.x 的 Web 开发模块,提供了一系列通用功能,包括统一响应处理、权限控制、数据权限、时间处理、序列化配置等。
模块功能:
- 统一响应格式处理
- 权限拦截器
- 数据权限控制
- 时间处理工具
- 序列化配置
- 文件上传助手
- 自动查询构建器
- JWT 认证支持
- 远程调用支持
1.2 模块架构
beyondsoft-web-spring3-starter/
├── annotation/ # 注解定义
│ ├── OriginalResponse.java # 原始响应注解
│ ├── QueryCondition.java # 查询条件注解
│ ├── TimeRange.java # 时间范围注解
│ └── TimeRangeArray.java # 时间范围数组注解
├── config/ # 自动配置
│ └── BeyondSoftWebAutoConfig.java # Web 自动配置类
├── controller/ # 基础控制器
│ ├── BaseController.java # 通用控制器基类
│ └── FlexBaseController.java # MyBatis-Flex 控制器基类
├── datapermission/ # 数据权限
│ ├── handler/ # 处理器
│ ├── model/ # 模型
│ └── parser/ # 解析器
├── decorator/ # 装饰器
│ └── AsyncTaskDecorator.java # 异步任务装饰器
├── deserializer/ # 反序列化器
│ ├── CustomBooleanDeserializer.java
│ ├── CustomLocalDateTimeDeserializer.java
│ └── QuotedStringToNullDeserializer.java
├── enums/ # 枚举
│ └── QueryType.java # 查询类型枚举
├── filter/ # 过滤器
│ └── LoggingFilter.java # 日志过滤器
├── handler/ # 处理器
│ └── UnifiedResponseHandler.java # 统一响应处理器
├── interceptor/ # 拦截器
│ ├── DataPermissionInterceptor.java # 数据权限拦截器
│ ├── DefaultOrderByIdInterceptor.java # 默认ID排序拦截器
│ ├── PermissionInterceptor.java # 权限拦截器
│ ├── ThirdPartRequestInterceptor.java # 第三方请求拦截器
│ └── UsernameFillInterceptor.java # 用户名填充拦截器
├── model/ # 模型
│ └── QueryCondition.java # 查询条件模型
├── properties/ # 配置属性
│ └── BeyondSoftWebProperties.java # Web 配置属性
├── utils/ # 工具类
│ ├── AutoQueryBuilder.java # 自动查询构建器
│ ├── FileUploadHelper.java # 文件上传助手
│ ├── LocalDateTimeUtils.java # 时间工具类
│ └── TokenUtils.java # 令牌工具类1.3 核心特性
- 统一响应处理: 提供统一的响应格式,支持成功和失败的统一返回
- 权限控制: 基于 Sa-Token 的登录和权限拦截
- 数据权限: 基于 SQL 拦截器的数据权限控制
- 时间处理: 支持多种时间格式的自动转换
- 序列化配置: 长整型精度丢失问题处理
- 自动查询构建: 基于注解的自动查询条件构建
- 文件上传: 统一的文件上传处理
- 第三方集成: 支持第三方请求的拦截和处理
- 异步任务支持: 支持异步任务中的用户上下文传递
2. 核心功能
2.1 统一响应处理
2.1.1 统一响应处理器
核心功能:
- 实现 ResponseBodyAdvice 接口
- 自动包装返回值为统一格式
- 支持 @OriginalResponse 注解跳过包装
- 提供统一的成功/失败响应格式
响应格式:
{
"code": 200,
"message": "success",
"data": {},
"timestamp": "2023-10-01T10:00:00"
}2.1.2 原始响应注解
功能说明:
- 用于标注接口方法或类,跳过统一响应包装
- 适用于需要返回原始格式的接口
使用示例:
@OriginalResponse
@GetMapping("/raw-data")
public List<User> getRawData() {
return userService.list();
}2.2 权限控制
2.2.1 权限拦截器
核心流程:
- 从配置文件加载权限映射规则
- 根据当前请求的 URL 和 HTTP 方法匹配权限标识符
- 验证当前登录用户是否拥有对应权限
- 超级管理员用户(权限标识为 ":😗")自动放行
- 白名单路径直接放行
权限配置格式:
mappings:
"GET /user/list": "user:list"
"POST /user/create": "user:create"
"/user/*": "user:view" # 传统风格,匹配所有HTTP方法2.2.2 Sa-Token 集成
配置: 通过 StpInterface 实现角色和权限获取逻辑
- 从 Session 中获取用户的角色和权限列表
- 支持登录状态检查和权限校验
- 支持 JWT 认证
2.3 数据权限
2.3.1 数据权限拦截器
核心流程:
- 拦截 MyBatis 查询操作
- 解析 SQL 语句,提取表名
- 根据表名查找对应的数据权限规则
- 构建数据权限过滤条件
- 将过滤条件注入到原 SQL 中
- 超级管理员用户不受数据权限限制
功能特点:
- 对业务代码无侵入
- 基于 YAML 配置,灵活可扩展
- 使用 Caffeine 缓存规则,性能优化
- 支持 JOIN 查询场景
2.3.2 数据权限处理器
功能接口:
buildDataPermissionCondition(): 构建数据权限过滤条件isSuperAdmin(): 检查是否超级管理员getUserId(): 获取用户ID
默认实现: DefaultDataPermissionHandler
2.4 时间处理
2.4.1 时间格式化配置
配置内容:
- LocalDateTime: "yyyy-MM-dd HH:mm:ss"
- LocalDate: "yyyy-MM-dd"
- LocalTime: "HH:mm:ss"
自定义反序列化器:
- CustomLocalDateTimeDeserializer: 支持多种时间格式
- LocalTimeDeserializer/LocalDateDeserializer: 标准格式反序列化
2.4.2 时间工具类
功能:
- 支持多种时间格式的解析和转换
- 提供常用的时间操作方法
2.5 序列化配置
2.5.1 长整型精度处理
配置: 将 Long 类型序列化为字符串,避免前端精度丢失
相关类:
- ToStringSerializer: 长整型序列化器
- Jackson2ObjectMapperBuilderCustomizer: 自定义 ObjectMapper 配置
2.5.2 自定义反序列化器
Boolean 反序列化器:
- CustomBooleanDeserializer: 支持多种布尔值格式
字符串反序列化器:
- QuotedStringToNullDeserializer: 空字符串转为 null
2.6 查询构建
2.6.1 查询条件注解
功能:
- 标注 VO 字段的查询类型和对应的数据库字段
- 支持多种查询类型(EQUAL, LIKE, BETWEEN, IN 等)
查询类型:
public enum QueryType {
EQUAL, // 等于
NOT_EQUAL, // 不等于
LIKE, // 模糊查询
LIKE_LEFT, // 左模糊
LIKE_RIGHT, // 右模糊
BETWEEN, // 范围查询
IN, // IN 查询
GT, // 大于
GE, // 大于等于
LT, // 小于
LE, // 小于等于
IS_NOT_NULL,// 不为空
IS_NULL // 为空
}使用示例:
public class UserQueryVO {
@QueryCondition(type = QueryType.LIKE)
private String username; // 对应数据库字段 username
@QueryCondition(type = QueryType.EQUAL, column = "user_age")
private Integer age; // 对应数据库字段 user_age
@QueryCondition(type = QueryType.BETWEEN)
private Integer[] ageRange;
}2.6.2 时间范围查询注解
功能:
- 标注时间范围查询字段
- 支持开始时间和结束时间
使用示例:
public class UserQueryVO {
@TimeRange(column = "create_time", isStart = true)
private LocalDateTime startTime;
@TimeRange(column = "create_time", isStart = false)
private LocalDateTime endTime;
}2.6.3 自动查询构建器
功能:
- 根据注解自动构建查询条件
- 支持多种查询类型
- 支持时间范围查询
2.7 文件上传
2.7.1 文件上传助手
功能:
- 统一的文件上传处理
- 支持多种文件类型
- 提供文件上传路径配置
2.8 远程调用
2.8.1 WebClient 配置
功能:
- 配置 WebClient 支持远程调用
- 支持 SSL 配置(跳过证书验证)
- 集成日志记录
2.8.2 HttpServiceProxyFactory
功能:
- 基于接口的远程调用代理
- 简化远程调用的实现
2.9 异步任务装饰器
2.9.1 AsyncTaskDecorator
核心功能:
- 在主线程捕获当前登录用户ID
- 在主线程捕获第三方调用信息(App-Key、Nonce)
- 在异步线程中设置用户上下文
- 异步任务执行完成后自动清理 ThreadLocal
- 避免异步任务中用户信息丢失问题
- 同时支持 Sa-Token 用户认证和第三方调用认证两种场景
工作原理:
- 在主线程执行
decorate方法时,同时尝试获取两种认证信息:- 通过 Sa-Token 获取当前登录用户ID(普通用户场景)
- 通过 HttpServletRequest 获取
App-Key和Nonce(第三方调用场景)
- 返回一个装饰后的 Runnable,在异步线程执行前设置用户上下文到 ThreadLocal
- 异步任务执行完成后,在 finally 块中清理 ThreadLocal,防止内存泄漏
使用场景:
- Spring 异步任务 (
@Async) - 线程池任务执行
- 需要在异步任务中获取当前用户信息的场景
- 第三方API调用触发的异步任务(如异步日志记录、异步通知等)
配置示例:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("async-");
// 设置任务装饰器,传递用户上下文
executor.setTaskDecorator(new AsyncTaskDecorator());
executor.initialize();
return executor;
}
}使用示例:
@Service
public class UserService {
@Async
public void processUserData() {
// 在异步方法中可以获取用户ID
ThreadShareDataDTO threadData = ThreadShareDataDTO.getCurrentThreadInstance();
Long userId = threadData.getCurrentUserId();
// 执行业务逻辑
log.info("异步处理用户数据,用户ID: {}", userId);
}
}
// 第三方调用场景
@Service
public class ThirdPartyService {
@Async
public void logThirdPartyRequest(String requestData) {
// 在异步方法中可以获取第三方信息
ThreadShareDataDTO threadData = ThreadShareDataDTO.getCurrentThreadInstance();
String appKey = threadData.getThirdPartyAppKey();
String nonce = threadData.getThirdPartyNonce();
// 执行业务逻辑
log.info("异步记录第三方请求,AppKey: {}, Nonce: {}, Data: {}",
appKey, nonce, requestData);
}
}注意事项:
- 必须在配置线程池时设置
TaskDecorator - Sa-Token 场景:只有通过 Sa-Token 认证的请求才能获取到用户ID
- 第三方调用场景:只有包含
App-Key和Nonce请求头的第三方请求才能获取到相应信息 - 非 Web 上下文(如定时任务)中,用户ID 和第三方信息均为 null
- 装饰器会自动清理 ThreadLocal,无需手动清理
- 两种认证场景互不干扰,装饰器会自动识别当前请求类型
3. 数据模型
3.1 查询条件模型
public class QueryCondition {
private QueryType queryType; // 查询类型
private String column; // 数据库字段名
private Object value; // 查询值
private Object[] rangeValues; // 范围查询值
private boolean ignoreEmpty; // 是否忽略空值
}3.2 时间范围模型
public class TimeRangeCondition {
private String column; // 时间字段名
private LocalDateTime start; // 开始时间
private LocalDateTime end; // 结束时间
private boolean isStart; // 是否开始时间
}4. 拦截器
4.1 数据权限拦截器
核心流程:
- 拦截 MyBatis 查询操作
- 解析 SQL 提取表名和别名
- 根据表名查找数据权限规则
- 检查是否超级管理员
- 构建数据权限过滤条件
- 注入过滤条件到原 SQL
- 创建新的 MappedStatement 执行修改后的 SQL
缓存策略:
- 规则缓存:使用 Caffeine 缓存数据权限规则,过期时间 30 分钟
- 表规则缓存:缓存表名到规则的映射,过期时间 30 分钟
4.2 权限拦截器
核心流程:
- 检查权限校验是否启用
- 检查请求路径是否在白名单中
- 校验用户登录状态
- 检查是否超级管理员
- 根据 URL 和 HTTP 方法匹配权限标识符
- 校验用户是否拥有对应权限
匹配优先级:
- 精确匹配: METHOD::URL
- 传统匹配: ALL::URL
- 模式匹配: METHOD::URL_PATTERN
- 模式匹配: ALL::URL_PATTERN
4.3 用户名填充拦截器
核心流程:
- 拦截 MyBatis 结果集处理
- 遍历查询结果
- 检查对象是否为 FlexBaseEntity 实例
- 获取 createBy 和 updateBy 字段值
- 从缓存中获取对应用户名
- 设置 createByUsername 和 updateByUsername 字段
4.4 第三方请求拦截器
核心流程:
- 拦截第三方请求
- 验证请求参数完整性
- 验证请求时效性(防止超时请求)
- 验证签名有效性
- 防止重放攻击(Nonce去重)
- 记录请求日志
4.4.1 请求参数
第三方调用必须在 HTTP 请求头中包含以下参数:
| 请求头名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
| App-Key | String | 是 | 第三方应用的唯一标识,由系统分配 |
| Timestamp | Long | 是 | 请求时间戳(毫秒),用于验证请求时效性 |
| Nonce | String | 是 | 随机字符串,长度必须为32位,用于防止重放攻击 |
| Sign | String | 是 | 请求签名,使用 MD5 算法生成 |
4.4.2 签名算法
签名生成步骤:
获取必要参数:
nonce: 32位随机字符串timestamp: 当前时间戳(毫秒)appSecret: 应用密钥(由系统分配,需保密)
拼接签名字符串:
signString = nonce + timestamp + appSecret计算 MD5 签名:
sign = MD5(signString).toLowerCase()
签名示例:
import cn.hutool.crypto.digest.MD5;
public class SignatureExample {
public static void main(String[] args) {
String appKey = "your-app-key";
String appSecret = "your-app-secret";
String nonce = "12345678901234567890123456789012"; // 32位随机字符串
long timestamp = System.currentTimeMillis();
// 拼接签名字符串
String signString = nonce + timestamp + appSecret;
// 计算MD5签名
String sign = MD5.create().digestHex(signString);
System.out.println("App-Key: " + appKey);
System.out.println("Timestamp: " + timestamp);
System.out.println("Nonce: " + nonce);
System.out.println("Sign: " + sign);
}
}4.4.3 验证流程
服务端验证步骤:
参数完整性验证:
- 检查
App-Key、Timestamp、Nonce、Sign是否存在 - 验证
Nonce长度是否为 32 位
- 检查
时效性验证:
- 计算当前时间与请求时间的差值
- 超过配置的超时时间(默认5000ms)则拒绝请求
- 防止旧请求被重放
签名验证:
- 根据
App-Key从 Redis 获取对应的App-Secret - 使用相同算法计算签名:
MD5(nonce + timestamp + appSecret) - 比对计算结果与请求中的签名是否一致
- 根据
重放攻击防护:
- 检查 Redis 中是否已存在相同的
Nonce(key:3rd:request:{appKey}:{nonce}) - 如果存在则拒绝请求
- 验证通过后将
Nonce存入 Redis,有效期 30 分钟
- 检查 Redis 中是否已存在相同的
4.4.4 完整调用示例
使用 Java 发起请求:
import cn.hutool.crypto.digest.MD5;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
import java.util.UUID;
public class ThirdPartyClient {
private final String appKey = "your-app-key";
private final String appSecret = "your-app-secret";
private final String baseUrl = "http://localhost:8080";
public void callApi(String endpoint, Object requestBody) {
// 1. 生成请求参数
long timestamp = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString().replace("-", ""); // 32位
// 2. 计算签名
String signString = nonce + timestamp + appSecret;
String sign = MD5.create().digestHex(signString);
// 3. 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.set("App-Key", appKey);
headers.set("Timestamp", String.valueOf(timestamp));
headers.set("Nonce", nonce);
headers.set("Sign", sign);
headers.set("Content-Type", "application/json");
// 4. 发起请求
RestTemplate restTemplate = new RestTemplate();
HttpEntity<Object> entity = new HttpEntity<>(requestBody, headers);
String response = restTemplate.exchange(
baseUrl + endpoint,
HttpMethod.POST,
entity,
String.class
).getBody();
System.out.println("Response: " + response);
}
}使用 cURL 发起请求:
#!/bin/bash
APP_KEY="your-app-key"
APP_SECRET="your-app-secret"
TIMESTAMP=$(date +%s%3N) # 毫秒时间戳
NONCE=$(openssl rand -hex 16) # 生成32位随机字符串
# 计算签名
SIGN_STRING="${NONCE}${TIMESTAMP}${APP_SECRET}"
SIGN=$(echo -n "$SIGN_STRING" | md5sum | awk '{print $1}')
# 发起请求
curl -X POST "http://localhost:8080/api/3rd/your-endpoint" \
-H "App-Key: $APP_KEY" \
-H "Timestamp: $TIMESTAMP" \
-H "Nonce: $NONCE" \
-H "Sign: $SIGN" \
-H "Content-Type: application/json" \
-d '{"key": "value"}'4.4.5 错误处理
常见错误及解决方案:
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
| "nonce,sign,appKey,nonce不能为空,且nonce长度必须为32" | 请求头参数缺失或 Nonce 长度不正确 | 检查请求头是否包含所有必填参数,确保 Nonce 为32位 |
| "请求超时,请重新设定请求参数" | 请求时间戳与服务器时间差值超过配置值 | 确保客户端时间同步,或增大超时配置 |
| "签名无效" | 签名计算错误 | 检查签名算法,确保参数顺序为 nonce+timestamp+appSecret |
| "nonce不能数重复" | Nonce 已被使用 | 每次请求必须生成新的随机 Nonce |
4.4.6 安全建议
App-Secret 保密:
- 不要在客户端代码中硬编码 App-Secret
- 不要通过网络传输 App-Secret
- 定期更换 App-Secret
时间同步:
- 确保客户端和服务端时间同步
- 建议使用 NTP 服务同步时间
Nonce 生成:
- 使用加密安全的随机数生成器
- 确保每次请求的 Nonce 都是唯一的
HTTPS 传输:
- 生产环境必须使用 HTTPS
- 防止请求被中间人拦截
日志记录:
- 记录所有第三方请求的关键信息
- 便于问题排查和安全审计
4.5 默认ID排序拦截器
功能:
- 拦截 MyBatis 查询操作
- 为查询自动添加 ORDER BY id DESC 排序
- 保证查询结果的默认排序
5. 配置项说明
5.1 核心配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| beyond-soft.web.uploadPath | String | "/upload" | 文件上传路径 |
| beyond-soft.web.authResourcePrefix | String | "/file/**" | 认证资源路径前缀 |
| beyond-soft.web.unAuthResourcePrefix | String | "/files/**" | 非认证资源路径前缀 |
| beyond-soft.web.logUri | String | "/log" | 日志查询接口路径 |
| beyond-soft.web.password | String | "Admin@123" | 默认密码 |
5.2 认证配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| beyond-soft.web.auth.enabled | boolean | true | 是否启用认证 |
| beyond-soft.web.auth.superAdminHasAllPerms | boolean | true | 超级管理员是否拥有所有权限 |
| beyond-soft.web.auth.excludes | List<String> | [] | 认证白名单路径 |
5.3 第三方请求配置
5.3.1 入站配置(Inbound)
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| beyond-soft.web.third-inbound.uri | String | "/api/3rd/**" | 第三方请求路径模式,支持通配符 |
| beyond-soft.web.third-inbound.timeout | Long | 5000 | 请求超时时间(毫秒),超过此时间的请求将被拒绝 |
| beyond-soft.web.third-inbound.enabledSign | boolean | true | 是否启用签名验证,生产环境强烈建议开启 |
配置示例:
beyond-soft:
web:
third-inbound:
uri: "/api/3rd/**" # 第三方API路径前缀
timeout: 5000 # 5秒超时
enabledSign: true # 启用签名验证5.3.2 出站配置(Outbound)
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| beyond-soft.web.third-outbound.showLog | boolean | true | 是否显示日志 |
| beyond-soft.web.third-outbound.address | String | "" | 远程服务地址 |
| beyond-soft.web.third-outbound.timeout | Long | 900000 | 远程调用超时时间(毫秒) |
| beyond-soft.web.third-outbound.maxInMemorySize | Integer | 10485760 | 最大内存大小(字节) |
| beyond-soft.web.third-outbound.defaultHeaders | Map<String,String> | {} | 默认请求头 |
5.4 权限配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| beyond-soft.web.permission.enabled | Boolean | false | 是否启用权限校验 |
| beyond-soft.web.permission.configPath | String | "classpath:permission-mappings.yml" | 权限配置文件路径 |
5.5 数据权限配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| beyond-soft.web.data-permission.enabled | Boolean | false | 是否启用数据权限 |
| beyond-soft.web.data-permission.configPath | String | "classpath:data-permission-rules.yml" | 数据权限配置文件路径 |
5.6 配置示例
beyond-soft:
web:
uploadPath: "/upload"
authResourcePrefix: "/file/**"
unAuthResourcePrefix: "/files/**"
logUri: "/log"
password: "Admin@123"
auth:
enabled: true
superAdminHasAllPerms: true
excludes:
- "/login"
- "/register"
- "/public/**"
third-inbound:
uri: "/api/3rd/**"
timeout: 5000
enabledSign: true
third-outbound:
showLog: true
address: "https://api.example.com"
timeout: 900000
maxInMemorySize: 10485760
defaultHeaders:
X-API-Key: "your-api-key"
permission:
enabled: true
configPath: "classpath:permission-mappings.yml"
data-permission:
enabled: true
configPath: "classpath:data-permission-rules.yml"6. 高级用法
6.1 自定义权限处理器
@Component
public class CustomDataPermissionHandler implements DataPermissionHandler<CustomUser> {
@Override
public String buildDataPermissionCondition(DataPermissionRule rule, CustomUser currentUser) {
// 根据用户角色和权限规则构建数据权限条件
if (currentUser.isAdmin()) {
return ""; // 管理员拥有全部权限
}
// 构建具体的数据权限条件
return "creator_id = " + currentUser.getId();
}
@Override
public boolean isSuperAdmin(CustomUser currentUser) {
return currentUser.getRole().equals("ADMIN");
}
@Override
public Long getUserId(CustomUser currentUser) {
return currentUser.getId();
}
}6.2 自定义查询条件
// 自定义查询VO
public class CustomQueryVO {
@QueryCondition(type = QueryType.LIKE_RIGHT, column = "user_name")
private String name;
@QueryCondition(type = QueryType.BETWEEN)
private Integer[] ageRange;
@TimeRange(column = "create_time", isStart = true)
private LocalDateTime startTime;
@TimeRange(column = "create_time", isStart = false)
private LocalDateTime endTime;
}
// 在Service中使用
public PageResult<User> queryUsers(CustomQueryVO queryVO) {
QueryWrapper<User> wrapper = autoQueryBuilder.buildQueryWrapper(queryVO);
return userMapper.selectPage(new Page<>(queryVO.getPage(), queryVO.getSize()), wrapper);
}6.3 权限配置文件
permission-mappings.yml:
mappings:
"GET /user/list": "user:list"
"POST /user/create": "user:create"
"PUT /user/update": "user:update"
"DELETE /user/delete": "user:delete"
"/user/profile": "user:profile"6.4 数据权限配置文件
data-permission-rules.yml:
rules:
user:
tableName: "sys_user"
condition: "creator_id = #{userId}"
description: "用户只能查看自己创建的数据"
order:
tableName: "sys_order"
condition: "sales_id = #{userId}"
description: "销售员只能查看自己的订单"7. 最佳实践
7.1 权限设计
- 权限标识设计: 采用
模块:操作:资源的格式,如user:create:profile - 角色权限映射: 通过配置文件管理角色和权限的映射关系
- 动态权限: 支持运行时动态分配权限
7.2 数据权限
- 配置化管理: 通过 YAML 配置文件管理数据权限规则
- 缓存优化: 使用 Caffeine 缓存提升查询性能
- SQL 注入防护: 验证和过滤权限条件,防止 SQL 注入
7.3 查询优化
- 注解驱动: 使用注解简化查询条件构建
- 类型安全: 支持多种查询类型,确保类型安全
- 性能优化: 缓存查询条件,减少重复构建
7.4 序列化处理
- 精度保护: 长整型序列化为字符串,避免前端精度丢失
- 时间格式: 统一时间格式,支持多种输入格式
- 空值处理: 空字符串自动转换为 null
8. 常见问题
8.1 权限校验问题
问题: 接口返回 401 未登录错误 解决方案:
- 检查请求是否携带有效的 Token
- 检查 Sa-Token 配置是否正确
- 确认请求路径不在白名单中
问题: 接口返回 403 无权限错误 解决方案:
- 检查权限配置文件是否包含该接口的权限映射
- 确认当前用户拥有对应的权限标识
- 检查权限拦截器是否正确启用
8.2 数据权限问题
问题: 数据权限条件未生效 解决方案:
- 确认
beyond-soft.web.data-permission.enabled设置为 true - 检查数据权限配置文件路径是否正确
- 确认查询语句是 SELECT 语句
8.3 序列化问题
问题: 长整型精度丢失 解决方案: 检查 Jackson 配置是否正确应用了 ToStringSerializer
问题: 时间格式转换异常 解决方案: 确认时间格式配置和输入格式匹配
8.4 文件上传问题
问题: 文件上传失败 解决方案:
- 检查上传路径是否可写
- 确认文件大小是否超过限制
- 检查文件类型是否符合要求
9. 扩展功能
9.1 自定义拦截器
可以通过实现对应的接口扩展功能:
// 自定义数据权限处理器
public class CustomDataPermissionHandler implements DataPermissionHandler<YourUserClass> {
// 实现自定义逻辑
}
// 自定义查询条件处理器
public class CustomQueryHandler {
// 实现自定义查询逻辑
}9.2 配置扩展
可以通过添加配置属性扩展功能:
@ConfigurationProperties(prefix = "beyond-soft.web.custom")
@Data
public class CustomProperties {
private boolean enabled = false;
private String configPath;
// 其他配置项
}10. 性能优化
10.1 缓存策略
- 权限缓存: 使用 Caffeine 缓存权限映射,减少文件读取
- 数据权限缓存: 缓存数据权限规则,提升查询性能
- 用户名缓存: 缓存用户ID到用户名的映射,避免重复查询
10.2 SQL 优化
- 拦截器优化: 避免对非查询语句执行权限检查
- 条件构建优化: 预编译正则表达式,提升匹配性能
- 结果处理优化: 批量处理查询结果,减少反射调用
10.3 内存优化
- 缓存大小限制: 设置合理的缓存大小和过期时间
- 对象复用: 复用查询条件对象,减少内存分配
- 连接池配置: 合理配置数据库连接池参数