Skip to content

beyondsoft-web-spring3-starter 开发文档

1. 项目概述

beyondsoft-web-spring3-starter 是一个基于 Spring Boot 3.x 的 Web 开发 Starter,提供了全面的 Web 开发支持,包括统一响应处理、数据权限控制、拦截器、过滤器、控制器基类等功能。该模块依赖于 common-base 模块,旨在简化 Spring Boot Web 应用的开发,提高开发效率和代码质量。

2. 核心功能

2.1 统一响应处理

提供统一的响应格式和响应处理机制,确保所有 API 返回格式一致。

2.2 数据权限控制

实现基于 YAML 配置的数据权限控制,支持多种数据范围类型,如全部数据、部门数据、个人数据等。

2.3 拦截器机制

提供多种拦截器,包括:

  • 数据权限拦截器
  • 默认排序拦截器
  • 权限拦截器
  • 用户名填充拦截器
  • 第三方请求拦截器

2.4 过滤器

  • 日志记录过滤器

2.5 控制器基类

提供基础控制器类,封装常用的 CRUD 操作。

2.6 查询条件注解

提供查询条件相关注解,简化查询参数处理。

2.7 工具类

提供多种 Web 开发工具类,如自动查询构建、文件上传辅助、查询包装器构建等。

2.8 自定义反序列化器

提供自定义的 JSON 反序列化器,处理特殊数据类型。

3. 核心组件

3.1 统一响应

  • UnifiedResponseHandler:统一响应处理器

3.2 数据权限

  • DataPermissionHandler:数据权限处理器
  • DefaultDataPermissionHandler:默认数据权限处理器
  • DataPermissionRule:数据权限规则模型
  • DataScopeType:数据范围类型枚举
  • SqlParser:SQL 解析器
  • YamlRuleLoader:YAML 规则加载器

3.3 拦截器

  • DataPermissionInterceptor:数据权限拦截器
  • DefaultOrderByIdInterceptor:默认排序拦截器
  • PermissionInterceptor:权限拦截器
  • UsernameFillInterceptor:用户名填充拦截器
  • ThirdPartRequestInterceptor:第三方请求拦截器

3.4 过滤器

  • LoggingFilter:日志记录过滤器

3.5 控制器

  • BaseController:基础控制器
  • FlexBaseController:Flex 基础控制器

3.6 工具类

  • AutoQueryBuilder:自动查询构建器
  • FileUploadHelper:文件上传辅助类
  • QueryWrapperBuilder:查询包装器构建器
  • LocalDateTimeUtils:LocalDateTime 工具类

3.7 注解

  • QueryCondition:查询条件注解
  • TimeRange:时间范围注解
  • TimeRangeArray:时间范围数组注解
  • OriginalResponse:原始响应注解

3.8 反序列化器

  • CustomBooleanDeserializer:自定义布尔反序列化器
  • CustomLocalDateTimeDeserializer:自定义 LocalDateTime 反序列化器
  • QuotedStringToNullDeserializer:引号字符串转 null 反序列化器

3.9 配置属性

  • BeyondSoftWebProperties:Web 配置属性

3.10 模型类

  • BeyondSoftThirdPartLog:第三方请求日志模型

4. 使用说明

4.1 添加依赖

在项目的 pom.xml 文件中添加以下依赖:

xml
<dependency>
    <groupId>com.beyondsoft</groupId>
    <artifactId>beyondsoft-web-spring3-starter</artifactId>
    <version>${beyondsoft.version}</version>
</dependency>

4.2 统一响应处理

4.2.1 自动统一响应

启用统一响应处理后,所有 API 响应都会被自动格式化为统一的响应格式,控制器直接返回实体或集合即可,无需手动包装:

java
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        // 直接返回实体,无需CommonResult包装
        return userService.getById(id);
    }
    
    @GetMapping("/list")
    public List<User> getUserList() {
        // 直接返回集合,无需CommonResult包装
        return userService.list();
    }
    
    @PostMapping
    public User addUser(@RequestBody User user) {
        // 直接返回保存后的实体
        userService.save(user);
        return user;
    }
    
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        // 直接返回更新后的实体
        user.setId(id);
        userService.updateById(user);
        return user;
    }
    
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        // 返回void表示操作成功
        userService.removeById(id);
    }
}

4.2.2 统一响应格式

系统自动将控制器返回的实体或集合包装为统一格式:

json
{
  "status": 200,
  "code": 200,
  "msg": "操作成功",
  "data": {...},  // 控制器返回的实体或集合
  "timestamp": 1634567890000,
  "traceId": "1234567890abcdef"
}

4.2.3 跳过统一响应处理

如果需要跳过统一响应处理,直接返回原始响应,可以使用 @OriginalResponse 注解:

java
@GetMapping("/raw")
@OriginalResponse
public String getRawResponse() {
    // 直接返回字符串,不进行统一包装
    return "原始响应内容";
}

4.3 数据权限配置与使用

4.3.1 数据权限配置

application.yml 或独立的 YAML 文件中配置数据权限规则:

yaml
data-permission:
  rules:
    # 用户数据权限:只能查看本部门数据
    - resource: user
      scopeType: DEPARTMENT
      condition: department_id = #{currentUser.departmentId}
      priority: 1
      enabled: true
    
    # 订单数据权限:只能查看本人创建的订单
    - resource: order
      scopeType: PERSONAL
      condition: create_user = #{currentUser.username}
      priority: 2
      enabled: true
    
    # 产品数据权限:管理员可以查看所有产品,普通用户只能查看上架产品
    - resource: product
      scopeType: CUSTOM
      condition: (is_admin = #{currentUser.isAdmin} AND status = 1) OR status = 1
      priority: 3
      enabled: true
    
    # 部门数据权限:只能查看本部门及子部门数据
    - resource: department
      scopeType: DEPARTMENT_CHILDREN
      condition: id IN #{currentUser.departmentIds}
      priority: 4
      enabled: true

4.3.2 数据权限规则说明

参数名类型默认值说明
resourceString-必填,资源名称,对应业务模块
scopeTypeDataScopeType-必填,数据范围类型:ALL、DEPARTMENT、DEPARTMENT_CHILDREN、PERSONAL、CUSTOM
conditionString-必填,权限条件表达式,支持SpEL表达式
priorityInteger0规则优先级,数值越小优先级越高
enabledBooleantrue是否启用该规则

4.3.3 在业务方法中使用数据权限

java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    @Override
    public List<User> listWithDataPermission() {
        // 数据权限拦截器会自动修改此查询的条件
        return list();
    }
    
    @Override
    public Page<User> pageWithDataPermission(Page<User> page, UserQueryDTO queryDTO) {
        // 构建查询条件
        QueryWrapper<User> queryWrapper = AutoQueryBuilder.buildQueryWrapper(queryDTO);
        // 数据权限拦截器会自动修改分页查询的条件
        return page(page, queryWrapper);
    }
}

4.3.4 数据范围类型详解

范围类型说明适用场景
ALL所有数据管理员或超级用户
DEPARTMENT本部门数据部门负责人查看本部门数据
DEPARTMENT_CHILDREN本部门及子部门数据公司领导查看部门及下属部门数据
PERSONAL个人数据普通用户查看自己创建的数据
CUSTOM自定义数据复杂的数据权限规则

4.3.5 自定义数据权限处理器

实现 DataPermissionHandler 接口可以自定义数据权限处理逻辑:

java
@Component
public class CustomDataPermissionHandler implements DataPermissionHandler {
    
    @Override
    public void handleDataPermission(String resource, DataPermissionRule rule, Object handler) {
        // 自定义数据权限处理逻辑
        if ("custom_resource".equals(resource)) {
            // 特殊处理自定义资源
        }
    }
}

4.4 控制器基类使用

4.4.1 BaseController 基本使用

java
@RestController
@RequestMapping("/api/users")
public class UserController extends BaseController<User, Long> {
    
    // 注入Service
    private final UserService userService;
    
    public UserController(UserService userService) {
        super(userService);
    }
    
    // 自动继承以下CRUD方法:
    // 1. POST /api/users - 新增用户
    // 2. PUT /api/users/{id} - 更新用户
    // 3. DELETE /api/users/{id} - 删除用户
    // 4. GET /api/users/{id} - 获取用户详情
    // 5. GET /api/users - 获取用户列表
    // 6. GET /api/users/page - 分页获取用户列表
    
    // 可以重写基础方法以扩展功能
    @Override
    @AuditLog(handleName = "新增用户")
    public User add(User user) {
        // 扩展新增逻辑
        return super.add(user);
    }
    
    // 添加自定义方法
    @GetMapping("/status/{status}")
    public List<User> getUsersByStatus(@PathVariable Integer status) {
        return userService.listByStatus(status);
    }
}

4.4.2 FlexBaseController 使用

java
@RestController
@RequestMapping("/api/orders")
public class OrderController extends FlexBaseController<Order, Long> {
    
    private final OrderService orderService;
    
    public OrderController(OrderService orderService) {
        super(orderService);
    }
    
    // 自动继承基于MyBatis Flex的CRUD方法
    
    // 自定义方法示例
    @GetMapping("/user/{userId}")
    public List<Order> getOrdersByUserId(@PathVariable Long userId) {
        return orderService.listByUserId(userId);
    }
}

4.5 查询条件注解详解

4.5.1 单参数查询注解

java
@GetMapping("/list")
public Page<User> list(
    @QueryCondition(Eq("username")) String username,  // 等于条件
    @QueryCondition(Like("nickname")) String nickname,  // 模糊查询
    @QueryCondition(Gt("age")) Integer minAge,  // 大于条件
    @QueryCondition(Lt("age")) Integer maxAge,  // 小于条件
    @QueryCondition(In("status")) List<Integer> statusList,  // 包含条件
    @QueryCondition(Ne("id")) Long excludeId,  // 不等于条件
    Page<User> page) {
    
    // 构建查询条件
    QueryWrapper<User> queryWrapper = QueryWrapperBuilder.create()
        .eq(StringUtils.isNotBlank(username), "username", username)
        .like(StringUtils.isNotBlank(nickname), "nickname", nickname)
        .gt(minAge != null, "age", minAge)
        .lt(maxAge != null, "age", maxAge)
        .in(CollectionUtils.isNotEmpty(statusList), "status", statusList)
        .ne(excludeId != null, "id", excludeId)
        .build();
    
    return userService.page(page, queryWrapper);
}

4.5.2 时间范围查询

java
@GetMapping("/list")
public Page<User> list(
    @TimeRange(start = "createTimeStart", end = "createTimeEnd") DateRange createTimeRange,  // 单个时间范围
    @TimeRangeArray(start = "updateTimeStart", end = "updateTimeEnd") List<DateRange> updateTimeRanges,  // 多个时间范围
    Page<User> page) {
    
    QueryWrapper<User> queryWrapper = QueryWrapperBuilder.create();
    
    // 处理单个时间范围
    if (createTimeRange != null) {
        queryWrapper.between("create_time", createTimeRange.getStart(), createTimeRange.getEnd());
    }
    
    // 处理多个时间范围
    if (CollectionUtils.isNotEmpty(updateTimeRanges)) {
        updateTimeRanges.forEach(range -> {
            queryWrapper.or(wrapper -> wrapper.between("update_time", range.getStart(), range.getEnd()));
        });
    }
    
    return userService.page(page, queryWrapper);
}

4.5.3 基于DTO的查询条件构建

java
// 查询DTO定义
@Data
public class UserQueryDTO {
    
    @Eq("username")
    private String username;
    
    @Like("nickname")
    private String nickname;
    
    @Gt("age")
    private Integer minAge;
    
    @Lt("age")
    private Integer maxAge;
    
    @In("status")
    private List<Integer> statusList;
    
    @TimeRange(start = "createTime")
    private LocalDateTime createTimeStart;
    
    @TimeRange(end = "createTime")
    private LocalDateTime createTimeEnd;
}

// 控制器方法
@GetMapping("/list")
public Page<User> list(UserQueryDTO queryDTO, Page<User> page) {
    // 自动构建查询条件
    QueryWrapper<User> queryWrapper = AutoQueryBuilder.buildQueryWrapper(queryDTO);
    return userService.page(page, queryWrapper);
}

4.6 文件上传辅助

4.6.1 单文件上传

java
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
    // 上传文件到指定目录
    String filePath = FileUploadHelper.uploadFile(file, "user-avatars");
    return filePath;  // 直接返回文件路径,无需包装
}

4.6.2 多文件上传

java
@PostMapping("/upload/multiple")
public List<String> uploadMultiple(@RequestParam("files") MultipartFile[] files) {
    // 批量上传文件
    List<String> filePaths = new ArrayList<>();
    for (MultipartFile file : files) {
        if (!file.isEmpty()) {
            String filePath = FileUploadHelper.uploadFile(file, "attachments");
            filePaths.add(filePath);
        }
    }
    return filePaths;  // 直接返回文件路径列表
}

4.6.3 带参数的文件上传

java
@PostMapping("/upload/with-param")
public UploadResult uploadWithParam(
        @RequestParam("file") MultipartFile file,
        @RequestParam("bizType") String bizType,
        @RequestParam("bizId") Long bizId) {
    
    // 构建存储目录
    String storageDir = String.format("%s/%d", bizType, bizId);
    
    // 上传文件
    String filePath = FileUploadHelper.uploadFile(file, storageDir);
    
    // 构建返回结果
    UploadResult result = new UploadResult();
    result.setFilePath(filePath);
    result.setFileName(file.getOriginalFilename());
    result.setFileSize(file.getSize());
    result.setFileType(file.getContentType());
    
    return result;  // 直接返回结果对象
}

// UploadResult 定义
@Data
public class UploadResult {
    private String filePath;
    private String fileName;
    private long fileSize;
    private String fileType;
}

5. 数据权限控制

5.1 数据范围类型

  • ALL:全部数据
  • DEPARTMENT:部门数据
  • PERSONAL:个人数据
  • CUSTOM:自定义数据

5.2 权限规则配置

数据权限规则通过 YAML 文件配置,支持以下属性:

  • resource:资源名称
  • scopeType:数据范围类型
  • condition:权限条件表达式
  • priority:优先级

5.3 权限拦截器

数据权限拦截器会在请求处理前拦截,根据配置的权限规则动态修改 SQL 查询条件,实现数据权限控制。

6. 拦截器配置

6.1 注册拦截器

通过 BeyondSoftWebAutoConfig 自动配置类注册所有拦截器,无需手动配置。

6.2 自定义拦截器

可以通过实现 HandlerInterceptor 接口自定义拦截器,并通过配置类注册。

7. 工具类详解

7.1 AutoQueryBuilder 自动查询构建器

功能:根据DTO对象的注解自动构建查询条件

java
// 查询DTO定义
@Data
public class OrderQueryDTO {
    
    @Eq("orderNo")
    private String orderNo;
    
    @Like("customerName")
    private String customerName;
    
    @In("status")
    private List<Integer> statusList;
    
    @Gte("createTime")
    private LocalDateTime startTime;
    
    @Lte("createTime")
    private LocalDateTime endTime;
    
    @AndOperator
    @Eq("isDeleted")
    private Boolean deleted = false;
}

// 使用示例
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
    
    @Override
    public Page<Order> pageQuery(OrderQueryDTO queryDTO, Page<Order> page) {
        // 自动构建查询条件
        QueryWrapper<Order> queryWrapper = AutoQueryBuilder.buildQueryWrapper(queryDTO);
        return this.page(page, queryWrapper);
    }
    
    @Override
    public List<Order> listQuery(OrderQueryDTO queryDTO) {
        // 自动构建查询条件
        QueryWrapper<Order> queryWrapper = AutoQueryBuilder.buildQueryWrapper(queryDTO);
        return this.list(queryWrapper);
    }
}

7.2 QueryWrapperBuilder 查询包装器构建器

功能:链式构建查询条件,支持动态条件

java
// 基础用法
QueryWrapper<User> queryWrapper = QueryWrapperBuilder.create()
    .eq("status", 1)  // 等于条件
    .like(StringUtils.isNotBlank(username), "username", username)  // 动态模糊查询
    .in(CollectionUtils.isNotEmpty(statusList), "status", statusList)  // 动态包含查询
    .between(startTime != null && endTime != null, "create_time", startTime, endTime)  // 动态范围查询
    .orderByDesc("create_time")  // 降序排序
    .orderByAsc("id")  // 升序排序
    .build();

// 复杂条件示例
QueryWrapper<User> complexWrapper = QueryWrapperBuilder.create()
    .eq("is_deleted", false)
    .and(wrapper -> wrapper
        .like("username", "admin")
        .or()
        .like("email", "admin")
    )
    .or(wrapper -> wrapper
        .eq("phone", "13800138000")
        .and()
        .eq("status", 1)
    )
    .build();

7.3 FileUploadHelper 文件上传辅助

功能:简化文件上传操作,支持多种存储策略

java
// 基本用法
String filePath = FileUploadHelper.uploadFile(file, "upload-dir");

// 带文件名生成策略
String customFilePath = FileUploadHelper.uploadFile(file, "upload-dir", fileName -> {
    // 自定义文件名生成逻辑
    String fileExt = StringUtils.getFilenameExtension(fileName);
    return String.format("%s.%s", UUID.randomUUID(), fileExt);
});

// 检查文件大小和类型
try {
    FileUploadHelper.checkFile(file, 10 * 1024 * 1024, Arrays.asList("image/jpeg", "image/png", "image/gif"));
    String filePath = FileUploadHelper.uploadFile(file, "images");
} catch (IllegalArgumentException e) {
    // 处理文件大小或类型错误
    throw new BusinessException("文件大小或类型不符合要求");
}

7.4 LocalDateTimeUtils 日期时间工具

功能:简化 LocalDateTime 的格式化和解析操作

java
// 格式化日期时间
String formattedDate = LocalDateTimeUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");

// 解析字符串为 LocalDateTime
LocalDateTime localDateTime = LocalDateTimeUtils.parse("2023-01-01 12:00:00", "yyyy-MM-dd HH:mm:ss");

// 获取当前时间戳(毫秒)
long timestamp = LocalDateTimeUtils.toTimestamp(LocalDateTime.now());

// 从时间戳创建 LocalDateTime
LocalDateTime dateFromTimestamp = LocalDateTimeUtils.fromTimestamp(System.currentTimeMillis());

// 获取两个时间的间隔(秒)
long secondsBetween = LocalDateTimeUtils.betweenSeconds(startTime, endTime);

// 获取今天的开始时间
LocalDateTime todayStart = LocalDateTimeUtils.getTodayStart();

// 获取今天的结束时间
LocalDateTime todayEnd = LocalDateTimeUtils.getTodayEnd();

8. 自定义反序列化器详解

8.1 CustomBooleanDeserializer 布尔类型反序列化器

功能:支持多种布尔值格式的自动转换

支持的格式

  • 布尔值:true/false
  • 字符串:"true"/"false"
  • 数字:1/0
  • 字符串数字:"1"/"0"
  • 中文:"是"/"否"

使用示例

java
@Data
public class User {
    private Long id;
    private String username;
    
    // 自动支持多种布尔值格式
    @JsonDeserialize(using = CustomBooleanDeserializer.class)
    private Boolean active;
}

请求示例

json
{
  "username": "test",
  "active": "1"  // 会被转换为 true
}

8.2 CustomLocalDateTimeDeserializer LocalDateTime 反序列化器

功能:支持多种日期时间格式的自动解析

支持的格式

  • yyyy-MM-dd HH:mm:ss
  • yyyy-MM-dd HH:mm:ss.SSS
  • yyyy-MM-dd
  • yyyy/MM/dd HH:mm:ss
  • yyyy-MM-dd'T'HH:mm:ss'Z' (ISO格式)
  • yyyy-MM-dd'T'HH:mm:ss.SSS'Z' (ISO格式带毫秒)

使用示例

java
@Data
public class Order {
    private Long id;
    private String orderNo;
    
    // 自动支持多种日期时间格式
    @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
    private LocalDateTime createTime;
}

请求示例

json
{
  "orderNo": "ORDER001",
  "createTime": "2023-01-01 12:00:00"  // 会被正确解析
}

8.3 QuotedStringToNullDeserializer 空字符串转 null 反序列化器

功能:将空引号字符串 "" 转换为 null,避免数据库存储空字符串

使用示例

java
@Data
public class Product {
    private Long id;
    private String name;
    
    // 空字符串会被转换为 null
    @JsonDeserialize(using = QuotedStringToNullDeserializer.class)
    private String description;
}

请求示例

json
{
  "name": "测试产品",
  "description": ""  // 会被转换为 null
}

8.4 全局配置反序列化器

可以在 Spring Boot 配置类中全局配置反序列化器:

java
@Configuration
public class JacksonConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        
        // 全局注册自定义反序列化器
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Boolean.class, new CustomBooleanDeserializer());
        module.addDeserializer(LocalDateTime.class, new CustomLocalDateTimeDeserializer());
        module.addDeserializer(String.class, new QuotedStringToNullDeserializer());
        
        objectMapper.registerModule(module);
        return objectMapper;
    }
}

9. 核心代码实现

9.1 统一响应处理器

java
@RestControllerAdvice
public class UnifiedResponseHandler implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 支持的返回类型判断
    }
    
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 统一响应处理逻辑
    }
}

9.2 数据权限拦截器

java
@Component
public class DataPermissionInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 数据权限处理逻辑
    }
}

9.3 控制器基类

java
public abstract class BaseController<T, ID> {
    // 基础 CRUD 操作实现
}

10. 依赖说明

依赖项版本用途
spring-boot-starter-web-Web 支持
sa-token-redis-jackson-Redis 支持的 Sa-Token
sa-token-jwt-JWT 支持的 Sa-Token
sa-token-spring-boot3-starter-Sa-Token Spring Boot 3 Starter
common-base-公共基础模块
lombok-简化 Java 代码
spring-aspects-Spring AOP 支持
spring-boot-starter-webflux-WebFlux 支持
spring-boot-starter-data-mongodb-MongoDB 支持
caffeine-缓存实现
flyway-core-数据库迁移
flyway-mysql-MySQL 数据库迁移
jsqlparser-SQL 解析
spring-boot-configuration-processor-配置文件处理器
mybatis-flex-spring-boot3-starter-MyBatis Flex 支持

11. 注意事项

  1. 数据权限配置:确保数据权限规则配置正确,避免数据泄露或权限控制失效。
  2. 拦截器顺序:注意拦截器的执行顺序,避免出现逻辑错误。
  3. 统一响应:使用 @OriginalResponse 注解可以跳过统一响应处理,返回原始响应。
  4. 文件上传:确保文件上传路径存在且有写入权限。
  5. 日期时间格式:统一使用 LocalDateTime 处理日期时间,避免时区问题。

12. 版本历史

  • 初始版本:提供基础的 Web 开发支持

13. 联系方式

如有问题或建议,请联系开发团队。


文档更新时间:2025-12-30 文档版本:1.0.0

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