# 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 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> 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 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 com.njcn rdms-spring-boot-starter-web ``` 本模块已经聚合: - `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 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 基础设施层”,而不是“业务功能层”。