Skip to content

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 核心特性

  1. 统一响应处理: 提供统一的响应格式,支持成功和失败的统一返回
  2. 权限控制: 基于 Sa-Token 的登录和权限拦截
  3. 数据权限: 基于 SQL 拦截器的数据权限控制
  4. 时间处理: 支持多种时间格式的自动转换
  5. 序列化配置: 长整型精度丢失问题处理
  6. 自动查询构建: 基于注解的自动查询条件构建
  7. 文件上传: 统一的文件上传处理
  8. 第三方集成: 支持第三方请求的拦截和处理
  9. 异步任务支持: 支持异步任务中的用户上下文传递

2. 核心功能

2.1 统一响应处理

2.1.1 统一响应处理器

核心功能:

  • 实现 ResponseBodyAdvice 接口
  • 自动包装返回值为统一格式
  • 支持 @OriginalResponse 注解跳过包装
  • 提供统一的成功/失败响应格式

响应格式:

json
{
  "code": 200,
  "message": "success",
  "data": {},
  "timestamp": "2023-10-01T10:00:00"
}

2.1.2 原始响应注解

功能说明:

  • 用于标注接口方法或类,跳过统一响应包装
  • 适用于需要返回原始格式的接口

使用示例:

java
@OriginalResponse
@GetMapping("/raw-data")
public List<User> getRawData() {
    return userService.list();
}

2.2 权限控制

2.2.1 权限拦截器

核心流程:

  1. 从配置文件加载权限映射规则
  2. 根据当前请求的 URL 和 HTTP 方法匹配权限标识符
  3. 验证当前登录用户是否拥有对应权限
  4. 超级管理员用户(权限标识为 ":😗")自动放行
  5. 白名单路径直接放行

权限配置格式:

yaml
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 数据权限拦截器

核心流程:

  1. 拦截 MyBatis 查询操作
  2. 解析 SQL 语句,提取表名
  3. 根据表名查找对应的数据权限规则
  4. 构建数据权限过滤条件
  5. 将过滤条件注入到原 SQL 中
  6. 超级管理员用户不受数据权限限制

功能特点:

  • 对业务代码无侵入
  • 基于 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 等)

查询类型:

java
public enum QueryType {
    EQUAL,      // 等于
    NOT_EQUAL,  // 不等于
    LIKE,       // 模糊查询
    LIKE_LEFT,  // 左模糊
    LIKE_RIGHT, // 右模糊
    BETWEEN,    // 范围查询
    IN,         // IN 查询
    GT,         // 大于
    GE,         // 大于等于
    LT,         // 小于
    LE,         // 小于等于
    IS_NOT_NULL,// 不为空
    IS_NULL     // 为空
}

使用示例:

java
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 时间范围查询注解

功能:

  • 标注时间范围查询字段
  • 支持开始时间和结束时间

使用示例:

java
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 用户认证和第三方调用认证两种场景

工作原理:

  1. 在主线程执行 decorate 方法时,同时尝试获取两种认证信息:
    • 通过 Sa-Token 获取当前登录用户ID(普通用户场景)
    • 通过 HttpServletRequest 获取 App-KeyNonce(第三方调用场景)
  2. 返回一个装饰后的 Runnable,在异步线程执行前设置用户上下文到 ThreadLocal
  3. 异步任务执行完成后,在 finally 块中清理 ThreadLocal,防止内存泄漏

使用场景:

  • Spring 异步任务 (@Async)
  • 线程池任务执行
  • 需要在异步任务中获取当前用户信息的场景
  • 第三方API调用触发的异步任务(如异步日志记录、异步通知等)

配置示例:

java
@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;
    }
}

使用示例:

java
@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);
    }
}

注意事项:

  1. 必须在配置线程池时设置 TaskDecorator
  2. Sa-Token 场景:只有通过 Sa-Token 认证的请求才能获取到用户ID
  3. 第三方调用场景:只有包含 App-KeyNonce 请求头的第三方请求才能获取到相应信息
  4. 非 Web 上下文(如定时任务)中,用户ID 和第三方信息均为 null
  5. 装饰器会自动清理 ThreadLocal,无需手动清理
  6. 两种认证场景互不干扰,装饰器会自动识别当前请求类型

3. 数据模型

3.1 查询条件模型

java
public class QueryCondition {
    private QueryType queryType;    // 查询类型
    private String column;          // 数据库字段名
    private Object value;           // 查询值
    private Object[] rangeValues;   // 范围查询值
    private boolean ignoreEmpty;    // 是否忽略空值
}

3.2 时间范围模型

java
public class TimeRangeCondition {
    private String column;          // 时间字段名
    private LocalDateTime start;    // 开始时间
    private LocalDateTime end;      // 结束时间
    private boolean isStart;        // 是否开始时间
}

4. 拦截器

4.1 数据权限拦截器

核心流程:

  1. 拦截 MyBatis 查询操作
  2. 解析 SQL 提取表名和别名
  3. 根据表名查找数据权限规则
  4. 检查是否超级管理员
  5. 构建数据权限过滤条件
  6. 注入过滤条件到原 SQL
  7. 创建新的 MappedStatement 执行修改后的 SQL

缓存策略:

  • 规则缓存:使用 Caffeine 缓存数据权限规则,过期时间 30 分钟
  • 表规则缓存:缓存表名到规则的映射,过期时间 30 分钟

4.2 权限拦截器

核心流程:

  1. 检查权限校验是否启用
  2. 检查请求路径是否在白名单中
  3. 校验用户登录状态
  4. 检查是否超级管理员
  5. 根据 URL 和 HTTP 方法匹配权限标识符
  6. 校验用户是否拥有对应权限

匹配优先级:

  1. 精确匹配: METHOD::URL
  2. 传统匹配: ALL::URL
  3. 模式匹配: METHOD::URL_PATTERN
  4. 模式匹配: ALL::URL_PATTERN

4.3 用户名填充拦截器

核心流程:

  1. 拦截 MyBatis 结果集处理
  2. 遍历查询结果
  3. 检查对象是否为 FlexBaseEntity 实例
  4. 获取 createBy 和 updateBy 字段值
  5. 从缓存中获取对应用户名
  6. 设置 createByUsername 和 updateByUsername 字段

4.4 第三方请求拦截器

核心流程:

  1. 拦截第三方请求
  2. 验证请求参数完整性
  3. 验证请求时效性(防止超时请求)
  4. 验证签名有效性
  5. 防止重放攻击(Nonce去重)
  6. 记录请求日志

4.4.1 请求参数

第三方调用必须在 HTTP 请求头中包含以下参数:

请求头名称类型必填说明
App-KeyString第三方应用的唯一标识,由系统分配
TimestampLong请求时间戳(毫秒),用于验证请求时效性
NonceString随机字符串,长度必须为32位,用于防止重放攻击
SignString请求签名,使用 MD5 算法生成

4.4.2 签名算法

签名生成步骤

  1. 获取必要参数

    • nonce: 32位随机字符串
    • timestamp: 当前时间戳(毫秒)
    • appSecret: 应用密钥(由系统分配,需保密)
  2. 拼接签名字符串

    signString = nonce + timestamp + appSecret
  3. 计算 MD5 签名

    sign = MD5(signString).toLowerCase()

签名示例

java
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 验证流程

服务端验证步骤

  1. 参数完整性验证

    • 检查 App-KeyTimestampNonceSign 是否存在
    • 验证 Nonce 长度是否为 32 位
  2. 时效性验证

    • 计算当前时间与请求时间的差值
    • 超过配置的超时时间(默认5000ms)则拒绝请求
    • 防止旧请求被重放
  3. 签名验证

    • 根据 App-Key 从 Redis 获取对应的 App-Secret
    • 使用相同算法计算签名:MD5(nonce + timestamp + appSecret)
    • 比对计算结果与请求中的签名是否一致
  4. 重放攻击防护

    • 检查 Redis 中是否已存在相同的 Nonce(key: 3rd:request:{appKey}:{nonce}
    • 如果存在则拒绝请求
    • 验证通过后将 Nonce 存入 Redis,有效期 30 分钟

4.4.4 完整调用示例

使用 Java 发起请求

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 发起请求

bash
#!/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 安全建议

  1. App-Secret 保密

    • 不要在客户端代码中硬编码 App-Secret
    • 不要通过网络传输 App-Secret
    • 定期更换 App-Secret
  2. 时间同步

    • 确保客户端和服务端时间同步
    • 建议使用 NTP 服务同步时间
  3. Nonce 生成

    • 使用加密安全的随机数生成器
    • 确保每次请求的 Nonce 都是唯一的
  4. HTTPS 传输

    • 生产环境必须使用 HTTPS
    • 防止请求被中间人拦截
  5. 日志记录

    • 记录所有第三方请求的关键信息
    • 便于问题排查和安全审计

4.5 默认ID排序拦截器

功能:

  1. 拦截 MyBatis 查询操作
  2. 为查询自动添加 ORDER BY id DESC 排序
  3. 保证查询结果的默认排序

5. 配置项说明

5.1 核心配置

配置项类型默认值说明
beyond-soft.web.uploadPathString"/upload"文件上传路径
beyond-soft.web.authResourcePrefixString"/file/**"认证资源路径前缀
beyond-soft.web.unAuthResourcePrefixString"/files/**"非认证资源路径前缀
beyond-soft.web.logUriString"/log"日志查询接口路径
beyond-soft.web.passwordString"Admin@123"默认密码

5.2 认证配置

配置项类型默认值说明
beyond-soft.web.auth.enabledbooleantrue是否启用认证
beyond-soft.web.auth.superAdminHasAllPermsbooleantrue超级管理员是否拥有所有权限
beyond-soft.web.auth.excludesList<String>[]认证白名单路径

5.3 第三方请求配置

5.3.1 入站配置(Inbound)

配置项类型默认值说明
beyond-soft.web.third-inbound.uriString"/api/3rd/**"第三方请求路径模式,支持通配符
beyond-soft.web.third-inbound.timeoutLong5000请求超时时间(毫秒),超过此时间的请求将被拒绝
beyond-soft.web.third-inbound.enabledSignbooleantrue是否启用签名验证,生产环境强烈建议开启

配置示例

yaml
beyond-soft:
  web:
    third-inbound:
      uri: "/api/3rd/**"        # 第三方API路径前缀
      timeout: 5000              # 5秒超时
      enabledSign: true          # 启用签名验证

5.3.2 出站配置(Outbound)

配置项类型默认值说明
beyond-soft.web.third-outbound.showLogbooleantrue是否显示日志
beyond-soft.web.third-outbound.addressString""远程服务地址
beyond-soft.web.third-outbound.timeoutLong900000远程调用超时时间(毫秒)
beyond-soft.web.third-outbound.maxInMemorySizeInteger10485760最大内存大小(字节)
beyond-soft.web.third-outbound.defaultHeadersMap<String,String>{}默认请求头

5.4 权限配置

配置项类型默认值说明
beyond-soft.web.permission.enabledBooleanfalse是否启用权限校验
beyond-soft.web.permission.configPathString"classpath:permission-mappings.yml"权限配置文件路径

5.5 数据权限配置

配置项类型默认值说明
beyond-soft.web.data-permission.enabledBooleanfalse是否启用数据权限
beyond-soft.web.data-permission.configPathString"classpath:data-permission-rules.yml"数据权限配置文件路径

5.6 配置示例

yml

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 自定义权限处理器

java
@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 自定义查询条件

java
// 自定义查询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:

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

yaml
rules:
  user:
    tableName: "sys_user"
    condition: "creator_id = #{userId}"
    description: "用户只能查看自己创建的数据"
  order:
    tableName: "sys_order"
    condition: "sales_id = #{userId}"
    description: "销售员只能查看自己的订单"

7. 最佳实践

7.1 权限设计

  1. 权限标识设计: 采用 模块:操作:资源 的格式,如 user:create:profile
  2. 角色权限映射: 通过配置文件管理角色和权限的映射关系
  3. 动态权限: 支持运行时动态分配权限

7.2 数据权限

  1. 配置化管理: 通过 YAML 配置文件管理数据权限规则
  2. 缓存优化: 使用 Caffeine 缓存提升查询性能
  3. SQL 注入防护: 验证和过滤权限条件,防止 SQL 注入

7.3 查询优化

  1. 注解驱动: 使用注解简化查询条件构建
  2. 类型安全: 支持多种查询类型,确保类型安全
  3. 性能优化: 缓存查询条件,减少重复构建

7.4 序列化处理

  1. 精度保护: 长整型序列化为字符串,避免前端精度丢失
  2. 时间格式: 统一时间格式,支持多种输入格式
  3. 空值处理: 空字符串自动转换为 null

8. 常见问题

8.1 权限校验问题

问题: 接口返回 401 未登录错误 解决方案:

  1. 检查请求是否携带有效的 Token
  2. 检查 Sa-Token 配置是否正确
  3. 确认请求路径不在白名单中

问题: 接口返回 403 无权限错误 解决方案:

  1. 检查权限配置文件是否包含该接口的权限映射
  2. 确认当前用户拥有对应的权限标识
  3. 检查权限拦截器是否正确启用

8.2 数据权限问题

问题: 数据权限条件未生效 解决方案:

  1. 确认 beyond-soft.web.data-permission.enabled 设置为 true
  2. 检查数据权限配置文件路径是否正确
  3. 确认查询语句是 SELECT 语句

8.3 序列化问题

问题: 长整型精度丢失 解决方案: 检查 Jackson 配置是否正确应用了 ToStringSerializer

问题: 时间格式转换异常 解决方案: 确认时间格式配置和输入格式匹配

8.4 文件上传问题

问题: 文件上传失败 解决方案:

  1. 检查上传路径是否可写
  2. 确认文件大小是否超过限制
  3. 检查文件类型是否符合要求

9. 扩展功能

9.1 自定义拦截器

可以通过实现对应的接口扩展功能:

java
// 自定义数据权限处理器
public class CustomDataPermissionHandler implements DataPermissionHandler<YourUserClass> {
    // 实现自定义逻辑
}

// 自定义查询条件处理器
public class CustomQueryHandler {
    // 实现自定义查询逻辑
}

9.2 配置扩展

可以通过添加配置属性扩展功能:

java
@ConfigurationProperties(prefix = "beyond-soft.web.custom")
@Data
public class CustomProperties {
    private boolean enabled = false;
    private String configPath;
    // 其他配置项
}

10. 性能优化

10.1 缓存策略

  1. 权限缓存: 使用 Caffeine 缓存权限映射,减少文件读取
  2. 数据权限缓存: 缓存数据权限规则,提升查询性能
  3. 用户名缓存: 缓存用户ID到用户名的映射,避免重复查询

10.2 SQL 优化

  1. 拦截器优化: 避免对非查询语句执行权限检查
  2. 条件构建优化: 预编译正则表达式,提升匹配性能
  3. 结果处理优化: 批量处理查询结果,减少反射调用

10.3 内存优化

  1. 缓存大小限制: 设置合理的缓存大小和过期时间
  2. 对象复用: 复用查询条件对象,减少内存分配
  3. 连接池配置: 合理配置数据库连接池参数

Copyright © 2025-present | 网站备案号:豫ICP备19038229号-1