Files
cn-rdms/rdms-framework/rdms-spring-boot-starter-web/README.md

607 lines
16 KiB
Markdown
Raw Normal View History

2026-03-11 19:32:37 +08:00
# rdms-spring-boot-starter-web
## 1. 模块定位
`rdms-spring-boot-starter-web` 是 Web 层基础设施模块,用来统一处理 HTTP 接口开发中的通用问题,而不是承载具体业务逻辑。
模块当前聚合了以下能力:
- Web 基础自动配置
- 全局异常处理
- 接口返回结果暂存
- 管理端 / 应用端接口前缀约定
- Swagger / Knife4j 文档
- API 访问日志
- XSS 防护
- API 加解密
- 返回字段脱敏
- Jackson JSON 定制
- Banner 输出
## 2. 设计思路
这个模块的设计重点,是把 Web 层横切能力收敛到 starter 中,业务模块只关注 Controller、参数对象和业务逻辑本身。
核心思路如下:
- 通过自动装配统一注册 `Filter``ControllerAdvice``RestTemplate`、Swagger 等基础组件。
- 通过包路径约定,自动给 Controller 增加统一前缀,减少每个模块重复写公共路径。
- 通过全局异常处理,把常见异常统一翻译成 `CommonResult`
- 通过过滤器和拦截器处理访问日志、XSS、防重复读取请求体、演示环境保护等横切逻辑。
- 通过注解方式扩展局部能力,例如 API 访问日志增强、接口加解密、字段脱敏。
自动装配入口如下:
- `com.njcn.rdms.framework.apilog.config.RdmsApiLogAutoConfiguration`
- `com.njcn.rdms.framework.jackson.config.RdmsJacksonAutoConfiguration`
- `com.njcn.rdms.framework.swagger.config.RdmsSwaggerAutoConfiguration`
- `com.njcn.rdms.framework.web.config.RdmsWebAutoConfiguration`
- `com.njcn.rdms.framework.apilog.config.RdmsApiLogRpcAutoConfiguration`
- `com.njcn.rdms.framework.xss.config.RdmsXssAutoConfiguration`
- `com.njcn.rdms.framework.banner.config.RdmsBannerAutoConfiguration`
- `com.njcn.rdms.framework.encrypt.config.RdmsApiEncryptAutoConfiguration`
## 3. 功能模块
### 3.1 Web 基础自动配置
`RdmsWebAutoConfiguration` 是本模块的核心入口,主要提供以下能力:
- 注册全局异常处理器 `GlobalExceptionHandler`
- 注册返回结果处理器 `GlobalResponseBodyHandler`
- 注册 `WebFrameworkUtils`
- 注册跨域过滤器 `CorsFilter`
- 注册请求体缓存过滤器 `CacheRequestBodyFilter`
- 提供普通 `RestTemplate`
- 提供带 `@LoadBalanced``RestTemplate`
其中有两个基础能力需要重点关注:
#### 3.1.1 按包路径自动增加接口前缀
模块会根据 Controller 所在包路径,自动追加统一前缀:
- `**.controller.admin.**` 默认追加 `/admin-api`
- `**.controller.app.**` 默认追加 `/app-api`
默认配置来自 `rdms.web`
```yaml
rdms:
web:
admin-api:
prefix: /admin-api
controller: "**.controller.admin.**"
app-api:
prefix: /app-api
controller: "**.controller.app.**"
admin-ui:
url: http://127.0.0.1:80
# 说明:
# - admin-api/app-api 已有默认值(如上所示),不改可省略
# - admin-ui.url 无默认值,需要显式配置
```
例如:
```java
package com.njcn.rdms.module.system.controller.admin.user;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/get")
public CommonResult<String> get() {
return CommonResult.success("ok");
}
}
```
实际访问路径是:
```text
/admin-api/user/get
```
如果 Controller 放在:
```text
com.xxx.xxx.controller.app.xxx
```
则会自动挂到 `/app-api` 下。
#### 3.1.2 `CommonResult` 不是自动包装
这个模块不会把任意 Controller 返回值自动包成 `CommonResult`
`GlobalResponseBodyHandler` 的作用,是在返回前记录已经构造好的 `CommonResult`,供访问日志等能力读取,而不是替开发者自动改写返回结构。
因此 Controller 仍然需要显式返回:
```java
return CommonResult.success(data);
```
而不是依赖框架自动包装。
### 3.2 全局异常处理
`GlobalExceptionHandler` 会把常见异常统一转换成 `CommonResult`,包括:
- 参数缺失
- 参数类型错误
- `@Validated` / `@Valid` 校验失败
- 请求方式不匹配
- Content-Type 不匹配
- 无权限访问
- 业务异常 `ServiceException`
- 系统异常
对于系统异常,除了统一返回错误结果外,还会通过 `ApiErrorLogCommonApi` 异步记录异常日志。
这意味着业务代码中通常只需要:
- 正常场景返回 `CommonResult.success(...)`
- 业务错误抛出 `ServiceException`
其余异常由框架统一兜底。
### 3.3 接口返回结果暂存
这里的“接口返回结果记录”,更准确地说是“接口返回结果暂存”。
对应实现是 `GlobalResponseBodyHandler`,它只会在 Controller 返回值为 `CommonResult` 时,把该结果放到当前请求上下文中,供后续组件读取。
它本身不负责:
- 打印日志
- 持久化日志
- 改写返回结构
它主要服务于访问日志能力。因为 `ApiAccessLogFilter` 执行时,需要拿到本次请求最终返回的 `CommonResult`,才能把返回码、返回消息、响应体等内容写入访问日志。
可以这样理解两者关系:
- 接口返回结果暂存:把本次返回结果放到 request 上,供后续读取
- API 访问日志:读取请求信息和返回结果,组装后异步上报日志
因此,这两个能力不是并列重复关系,而是前者为后者提供支撑。
### 3.4 请求体缓存
`CacheRequestBodyFilter` 会把请求体包装成可重复读取的形式。
这个能力主要解决以下问题:
- 过滤器里读过一次请求体后,后续代码还能继续读取
- 访问日志、XSS、防护类过滤器可以提前读取请求内容
- 异常日志记录时可以再次拿到请求参数
这类能力对 `application/json` 请求尤其重要。
### 3.5 跨域处理
模块默认注册了全局 `CorsFilter`,允许:
- 任意来源
- 任意请求头
- 任意请求方法
- 携带凭证
适合前后端分离场景的统一跨域处理。
如果项目已经有更严格的网关级跨域策略,需要注意是否与这里的配置重复。
### 3.6 RestTemplate 支持
模块提供两个 `RestTemplate` Bean
- 普通 `RestTemplate`
- 支持服务名负载均衡的 `loadBalancedRestTemplate`
使用示例:
```java
@Resource
private RestTemplate restTemplate;
@Resource(name = "loadBalancedRestTemplate")
private RestTemplate loadBalancedRestTemplate;
```
适用场景:
- `restTemplate`:直接调用固定地址
- `loadBalancedRestTemplate`:通过服务名访问注册中心中的其他服务
### 3.7 Swagger / Knife4j 文档
模块会自动装配 OpenAPI并按管理端、应用端拆分分组
- `/admin-api/**`
- `/app-api/**`
同时会在文档中预置常见请求头,例如认证头、租户头,便于在 Swagger 页面直接调试接口。
Swagger 配置示例:
```yaml
rdms:
swagger:
title: RDMS API # 文档标题
description: RDMS 接口文档 # 文档描述
author: RDMS # 作者/团队
version: 1.0.0 # 版本号
url: https://example.com # 项目或团队主页
email: dev@example.com # 联系邮箱
license: Apache 2.0 # 协议名称
license-url: https://www.apache.org/licenses/LICENSE-2.0.html # 协议地址
```
开发时通常只需要:
- 引入本模块
- 补齐 `rdms.swagger` 配置
- 在 Controller 上使用 `@Tag`
- 在接口上使用 `@Operation`
这样访问日志模块还能直接复用这些注解信息,自动推断操作模块、操作名称。
### 3.8 API 访问日志
模块通过 `ApiAccessLogFilter + ApiAccessLogInterceptor` 组合处理接口访问日志。
这里的访问日志,才是真正意义上的“日志记录能力”。
处理逻辑分成两层:
- 拦截器层:把 `HandlerMethod` 放到 request 中,供过滤器层读取
- 过滤器层:在请求结束后读取请求信息、异常信息、返回结果,并异步上报访问日志
访问日志内容包括:
- 用户信息
- 请求路径、方法、IP、User-Agent
- 请求参数
- 返回码、返回信息
- 执行耗时
- 操作模块、操作名称、操作类型
其中“返回码、返回信息、响应体”这部分数据,正是从前面的 `GlobalResponseBodyHandler` 暂存结果中读取出来的。
默认会记录访问日志;如需关闭,可配置:
```yaml
rdms:
access-log:
enable: false
```
如果需要对某个接口单独调整日志行为,可使用 `@ApiAccessLog`
常见用法示例:
```java
@Tag(name = "用户管理")
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/page")
@Operation(summary = "查询用户分页")
@ApiAccessLog(responseEnable = true, sanitizeKeys = {"mobile"})
public CommonResult<PageResult<UserRespVO>> page(UserPageReqVO reqVO) {
return CommonResult.success(userService.page(reqVO));
}
}
```
说明:
- `responseEnable = true`:额外记录响应体
- `sanitizeKeys`:从日志中移除敏感字段
- 如果没有显式指定操作名称,默认优先从 `@Operation` 中获取
访问日志的异步落库依赖 `ApiAccessLogCommonApi`,因此它本质上是“记录请求轨迹”,不是直接把日志写在本模块内部。
### 3.9 XSS 防护
模块内提供了 XSS 过滤能力,核心组成包括:
- `XssFilter`
- `XssRequestWrapper`
- `XssStringJsonDeserializer`
- `JsoupXssCleaner`
设计方式是:
- 对进入系统的请求内容做统一清洗
- 对 JSON 中的字符串字段做清洗
- 底层使用 `jsoup` 处理潜在的恶意脚本片段
适用场景:
- 富文本之外的大多数普通文本输入
- 表单提交
- JSON 请求体中的字符串字段
如果某些接口需要保留原始 HTML 内容,通常需要结合该模块的 XSS 配置做排除,而不是在业务层手工规避。
URL 白名单说明:
- 支持通过 `rdms.xss.exclude-urls` 配置 URL 白名单,命中的 URL 会跳过 XSS 过滤(包括 Filter 与 JSON 字符串反序列化)。
- 适合富文本、HTML 片段等需要保留原始内容的接口。
示例:
```yaml
rdms:
xss:
enable: true
exclude-urls:
- /admin-api/rich-text/**
- /app-api/article/save
```
### 3.10 API 加解密
模块提供了接口级加解密能力,核心组成包括:
- `@ApiEncrypt`
- `ApiEncryptFilter`
- `ApiDecryptRequestWrapper`
- `ApiEncryptResponseWrapper`
适合理解为:
- 请求进入时先解密
- Controller / Service 内部仍然处理明文
- 响应返回前再加密
开发使用上,一般是在需要加解密的接口上增加 `@ApiEncrypt`,其余请求不受影响。
示例:
```java
@RestController
@RequestMapping("/secure/demo")
public class SecureDemoController {
@PostMapping("/submit")
@ApiEncrypt
public CommonResult<String> submit(@RequestBody DemoReqVO reqVO) {
return CommonResult.success(reqVO.getContent());
}
}
```
这类能力适合:
- 对外开放接口
- 对传输内容有额外保护要求的场景
不适合把它当成通用权限控制手段。它解决的是“传输内容保护”,不是“访问授权”。
### 3.11 返回字段脱敏
模块内置了一组字段脱敏注解和序列化器,常见注解包括:
- `@MobileDesensitize`
- `@EmailDesensitize`
- `@IdCardDesensitize`
- `@BankCardDesensitize`
- `@ChineseNameDesensitize`
- `@PasswordDesensitize`
适用方式是:在返回对象字段上直接加注解,序列化为 JSON 时自动脱敏。
示例:
```java
@Data
public class UserRespVO {
private Long id;
@MobileDesensitize
private String mobile;
@EmailDesensitize
private String email;
@ChineseNameDesensitize
private String nickname;
}
```
这样接口内部仍可使用原始值,真正对外输出时才变成脱敏后的内容。
### 3.12 Jackson JSON 定制
模块包含 `RdmsJacksonAutoConfiguration`,用于统一注册 JSON 序列化 / 反序列化相关定制能力。
从模块结构上看,它主要承担两类职责:
- 承接本模块的 XSS、脱敏等 JSON 处理能力
- 把 JSON 相关行为统一收口到 starter 中,避免各业务模块重复配置 `ObjectMapper`
因此业务模块通常不需要自己再手动声明一套新的全局 Jackson 配置,除非有明确的覆盖需求。
### 3.13 Banner
模块还包含 `RdmsBannerAutoConfiguration``BannerApplicationRunner`,用于在应用启动时输出框架 Banner 信息。
这部分属于展示型能力,不影响具体业务逻辑。
## 4. 开发人员上手
### 4.1 引入依赖
```xml
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-web</artifactId>
</dependency>
```
本模块已经聚合:
- `spring-boot-starter-web`
- `spring-boot-starter-validation`
- `knife4j-openapi3-jakarta-spring-boot-starter`
- `springdoc-openapi-starter-webmvc-ui`
如果项目已经单独引入了这些依赖,需要留意是否存在重复配置。
### 4.2 准备基础配置
建议至少配置以下内容:
```yaml
spring:
application:
name: rdms-system-server
rdms:
web:
admin-api:
prefix: /admin-api
controller: "**.controller.admin.**"
app-api:
prefix: /app-api
controller: "**.controller.app.**"
admin-ui:
url: http://127.0.0.1:80
swagger:
title: RDMS API # 文档标题
description: RDMS 接口文档 # 文档描述
author: RDMS # 作者/团队
version: 1.0.0 # 版本号
url: https://example.com # 项目或团队主页
email: dev@example.com # 联系邮箱
license: Apache 2.0 # 协议名称
license-url: https://www.apache.org/licenses/LICENSE-2.0.html # 协议地址
access-log:
enable: true
```
### 4.3 按约定放置 Controller
如果希望自动带上前缀,需要把 Controller 放到约定包下:
- 管理端:`xx.controller.admin.xx`
- 应用端:`xx.controller.app.xx`
不符合这个包路径规则的 Controller不会自动追加 `/admin-api``/app-api`
### 4.4 统一返回 `CommonResult`
Controller 写法建议保持统一:
```java
@Tag(name = "部门管理")
@RestController
@RequestMapping("/dept")
public class DeptController {
@GetMapping("/get")
@Operation(summary = "获得部门详情")
public CommonResult<DeptRespVO> get(@RequestParam("id") Long id) {
return CommonResult.success(deptService.get(id));
}
}
```
建议遵循:
- 成功场景返回 `CommonResult.success(...)`
- 业务异常抛出 `ServiceException`
- 参数对象使用 `@Validated` / `@Valid`
### 4.5 使用访问日志
多数情况下,不需要额外写代码,访问日志会自动生效。
如果需要补充操作名、操作类型、响应体记录等信息,可以加:
```java
@ApiAccessLog(responseEnable = true)
```
建议同时补齐:
- `@Tag`
- `@Operation`
这样日志中的操作模块和操作名称会更完整;建议将 `@Tag` / `@Operation` 作为接口开发的必填规范。
### 4.6 使用加解密
只在需要的接口上加:
```java
@ApiEncrypt
```
然后让调用方按约定传输密文即可。接口内部仍按明文对象开发,不需要在 Controller 内手工做解密逻辑。
### 4.7 使用字段脱敏
在返回对象字段上直接加注解即可:
```java
@MobileDesensitize
private String mobile;
```
适合用户信息、联系方式、证件信息等对外展示场景。
### 4.8 使用 RestTemplate
固定地址调用:
```java
@Resource
private RestTemplate restTemplate;
```
服务名调用:
```java
@Resource(name = "loadBalancedRestTemplate")
private RestTemplate loadBalancedRestTemplate;
```
### 4.9 开发时需要注意的几个点
- 这个模块不会自动把任意返回值包装成 `CommonResult`,需要显式返回。
- 接口前缀是按包路径匹配出来的,不是看 Controller 名称。
- 访问日志默认开启,但是否真正异步落库,还取决于日志相关 RPC 接口是否可用。
- XSS、防脱敏、加解密都属于横切能力原则上应该通过统一配置或注解使用不建议在业务代码里重复造轮子。
- 如果项目已经在网关层处理跨域、日志、安全头等逻辑,需要评估是否与本模块职责重叠。
## 5. 适合承载什么,不适合承载什么
适合放在这个模块里的能力:
- Web 层公共配置
- HTTP 请求/响应横切处理
- 接口文档
- 访问日志
- 安全性输入输出处理
- Web 基础工具注册
不适合放在这个模块里的能力:
- 具体业务规则
- 菜单、权限、数据权限等业务权限判断
- 某个业务模块专属的 Controller 逻辑
- 与单一业务强耦合的转换规则
从职责上看这个模块更接近“Web 基础设施层”,而不是“业务功能层”。