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 文件中添加以下依赖:
<dependency>
<groupId>com.beyondsoft</groupId>
<artifactId>beyondsoft-web-spring3-starter</artifactId>
<version>${beyondsoft.version}</version>
</dependency>4.2 统一响应处理
4.2.1 自动统一响应
启用统一响应处理后,所有 API 响应都会被自动格式化为统一的响应格式,控制器直接返回实体或集合即可,无需手动包装:
@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 统一响应格式
系统自动将控制器返回的实体或集合包装为统一格式:
{
"status": 200,
"code": 200,
"msg": "操作成功",
"data": {...}, // 控制器返回的实体或集合
"timestamp": 1634567890000,
"traceId": "1234567890abcdef"
}4.2.3 跳过统一响应处理
如果需要跳过统一响应处理,直接返回原始响应,可以使用 @OriginalResponse 注解:
@GetMapping("/raw")
@OriginalResponse
public String getRawResponse() {
// 直接返回字符串,不进行统一包装
return "原始响应内容";
}4.3 数据权限配置与使用
4.3.1 数据权限配置
在 application.yml 或独立的 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: true4.3.2 数据权限规则说明
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
resource | String | - | 必填,资源名称,对应业务模块 |
scopeType | DataScopeType | - | 必填,数据范围类型:ALL、DEPARTMENT、DEPARTMENT_CHILDREN、PERSONAL、CUSTOM |
condition | String | - | 必填,权限条件表达式,支持SpEL表达式 |
priority | Integer | 0 | 规则优先级,数值越小优先级越高 |
enabled | Boolean | true | 是否启用该规则 |
4.3.3 在业务方法中使用数据权限
@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 接口可以自定义数据权限处理逻辑:
@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 基本使用
@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 使用
@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 单参数查询注解
@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 时间范围查询
@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的查询条件构建
// 查询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 单文件上传
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
// 上传文件到指定目录
String filePath = FileUploadHelper.uploadFile(file, "user-avatars");
return filePath; // 直接返回文件路径,无需包装
}4.6.2 多文件上传
@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 带参数的文件上传
@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对象的注解自动构建查询条件
// 查询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 查询包装器构建器
功能:链式构建查询条件,支持动态条件
// 基础用法
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 文件上传辅助
功能:简化文件上传操作,支持多种存储策略
// 基本用法
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 的格式化和解析操作
// 格式化日期时间
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" - 中文:
"是"/"否"
使用示例:
@Data
public class User {
private Long id;
private String username;
// 自动支持多种布尔值格式
@JsonDeserialize(using = CustomBooleanDeserializer.class)
private Boolean active;
}请求示例:
{
"username": "test",
"active": "1" // 会被转换为 true
}8.2 CustomLocalDateTimeDeserializer LocalDateTime 反序列化器
功能:支持多种日期时间格式的自动解析
支持的格式:
yyyy-MM-dd HH:mm:ssyyyy-MM-dd HH:mm:ss.SSSyyyy-MM-ddyyyy/MM/dd HH:mm:ssyyyy-MM-dd'T'HH:mm:ss'Z'(ISO格式)yyyy-MM-dd'T'HH:mm:ss.SSS'Z'(ISO格式带毫秒)
使用示例:
@Data
public class Order {
private Long id;
private String orderNo;
// 自动支持多种日期时间格式
@JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
private LocalDateTime createTime;
}请求示例:
{
"orderNo": "ORDER001",
"createTime": "2023-01-01 12:00:00" // 会被正确解析
}8.3 QuotedStringToNullDeserializer 空字符串转 null 反序列化器
功能:将空引号字符串 "" 转换为 null,避免数据库存储空字符串
使用示例:
@Data
public class Product {
private Long id;
private String name;
// 空字符串会被转换为 null
@JsonDeserialize(using = QuotedStringToNullDeserializer.class)
private String description;
}请求示例:
{
"name": "测试产品",
"description": "" // 会被转换为 null
}8.4 全局配置反序列化器
可以在 Spring Boot 配置类中全局配置反序列化器:
@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 统一响应处理器
@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 数据权限拦截器
@Component
public class DataPermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 数据权限处理逻辑
}
}9.3 控制器基类
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. 注意事项
- 数据权限配置:确保数据权限规则配置正确,避免数据泄露或权限控制失效。
- 拦截器顺序:注意拦截器的执行顺序,避免出现逻辑错误。
- 统一响应:使用
@OriginalResponse注解可以跳过统一响应处理,返回原始响应。 - 文件上传:确保文件上传路径存在且有写入权限。
- 日期时间格式:统一使用 LocalDateTime 处理日期时间,避免时区问题。
12. 版本历史
- 初始版本:提供基础的 Web 开发支持
13. 联系方式
如有问题或建议,请联系开发团队。
文档更新时间:2025-12-30 文档版本:1.0.0