framework-starter-web - Web 功能自动配置模块
1. 模块概述
framework-starter-web 是 epoch-framework 中的 Web 功能自动配置模块,集成了 Spring MVC、Swagger (Knife4j)、全局异常处理、统一响应格式等核心功能。该模块通过自动配置的方式,简化了 Web 应用的开发,提供了标准化的 API 开发规范和最佳实践。
2. 模块结构
framework-starter-web/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/epoch/framework/starter/web/
│ │ │ ├── config/ # 自动配置类
│ │ │ ├── exception/ # 全局异常处理
│ │ │ ├── filter/ # 过滤器实现
│ │ │ ├── interceptor/ # 拦截器实现
│ │ │ ├── response/ # 统一响应格式
│ │ │ ├── validation/ # 参数验证配置
│ │ │ ├── swagger/ # Swagger 配置
│ │ │ └── support/ # 支持工具类
│ │ └── resources/
│ │ └── META-INF/
│ │ └── spring.factories # Spring Boot 自动配置入口
│ └── test/ # 测试代码
└── pom.xml # Maven 依赖配置
3. 核心功能
3.1 Spring MVC 自动配置
- 自动配置 Spring MVC 核心组件
- 支持视图解析器和消息转换器配置
- 提供静态资源访问支持
- 集成 ContentNegotiation 和 MediaType 配置
3.2 Swagger (Knife4j) API 文档
- 自动配置 Swagger 3.0 和 Knife4j 增强
- 提供 API 文档自动生成功能
- 支持接口调试和在线测试
- 提供接口版本管理和分组功能
3.3 跨域支持自动配置
- 自动配置 CORS 跨域支持
- 支持自定义跨域配置
- 提供细粒度的跨域控制
- 支持预请求 (Preflight) 缓存
3.4 拦截器自动配置
- 提供全局拦截器配置
- 支持请求日志记录
- 提供请求耗时统计
- 支持权限验证和访问控制
3.5 过滤器自动配置
- 自动配置常用过滤器
- 支持 XSS 防护过滤器
- 提供请求参数过滤
- 支持字符编码过滤器
3.6 全局异常处理
- 统一处理应用异常
- 提供标准化的错误响应
- 支持自定义异常类型
- 提供异常日志记录
3.7 统一响应格式
- 提供全局响应包装
- 支持标准化的响应结构
- 提供分页响应封装
- 支持响应状态码映射
3.8 参数验证配置
- 集成 JSR-303/JSR-380 参数验证
- 提供自定义验证注解
- 支持分组验证和顺序验证
- 提供友好的验证错误提示
4. 配置说明
4.1 Spring MVC 配置
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
view:
prefix: /WEB-INF/views/
suffix: .jsp
static-path-pattern: /static/**
contentnegotiation:
favor-parameter: false
favor-path-extension: false
throw-exception-if-no-handler-found: true
web:
resources:
static-locations: classpath:/static/,classpath:/public/,classpath:/resources/
4.2 Swagger (Knife4j) 配置
knife4j:
enable: true
openapi:
title: Epoch API 文档
description: Epoch 平台 API 接口文档
version: 1.0.0
contact:
name: Epoch Team
email: admin@epoch.com
license:
name: Apache version-v2
url: http://www.apache.org/licenses/LICENSE-2.0
servers:
- url: http://localhost:8080
description: 开发环境
- url: http://test.epoch.com
description: 测试环境
- url: http://prod.epoch.com
description: 生产环境
setting:
language: zh_cn
enable-swagger-models: true
swagger-model-name: 数据模型
enable-document-manage: true
enable-after-script: true
enable-version: true
enable-filter-multipart-api-method-type: GET
cors:
enabled: true
4.3 跨域配置
epoch:
framework:
web:
cors:
enabled: true
allowed-origins: '*'
allowed-methods: GET,POST,PUT,DELETE,OPTIONS
allowed-headers: '*'
exposed-headers: Content-Disposition
allow-credentials: true
max-age: 3600
4.4 全局异常处理配置
epoch:
framework:
web:
exception:
enabled: true
show-details: true
log-exceptions: true
default-error-code: 500
default-error-message: 服务器内部错误
4.5 统一响应配置
epoch:
framework:
web:
response:
enabled: true
wrap-success-response: true
default-success-message: 操作成功
default-error-message: 操作失败
page:
page-param-name: page
size-param-name: size
total-field-name: total
records-field-name: records
4.6 参数验证配置
epoch:
framework:
web:
validation:
enabled: true
message-source: messages
default-message: 参数验证失败
locale: zh_CN
5. 使用指南
5.1 引入依赖
<dependency>
<groupId>com.epoch</groupId>
<artifactId>framework-starter-web</artifactId>
<version>${epoch.version}</version>
</dependency>
5.2 控制器开发
@RestController
@RequestMapping("/api/v1/users")
@Api(tags = "用户管理")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
@ApiOperation("根据ID获取用户信息")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataTypeClass = Long.class)
public ResponseResult<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseResult.success(user);
}
@GetMapping
@ApiOperation("获取用户列表")
@ApiImplicitParams({
@ApiImplicitParam(name = "page", value = "页码", defaultValue = "1", dataTypeClass = Integer.class),
@ApiImplicitParam(name = "size", value = "每页大小", defaultValue = "10", dataTypeClass = Integer.class),
@ApiImplicitParam(name = "username", value = "用户名", required = false, dataTypeClass = String.class)
})
public ResponseResult<PageResult<User>> getUserList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String username) {
PageResult<User> userList = userService.getUserList(page, size, username);
return ResponseResult.success(userList);
}
@PostMapping
@ApiOperation("创建用户")
@ApiImplicitParam(name = "user", value = "用户信息", required = true, dataTypeClass = UserRequest.class)
public ResponseResult<Long> createUser(@Valid @RequestBody UserRequest user) {
Long userId = userService.createUser(user);
return ResponseResult.success(userId);
}
@PutMapping("/{id}")
@ApiOperation("更新用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataTypeClass = Long.class),
@ApiImplicitParam(name = "user", value = "用户信息", required = true, dataTypeClass = UserRequest.class)
})
public ResponseResult<Void> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserRequest user) {
userService.updateUser(id, user);
return ResponseResult.success();
}
@DeleteMapping("/{id}")
@ApiOperation("删除用户")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataTypeClass = Long.class)
public ResponseResult<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseResult.success();
}
}
5.3 请求参数验证
@Data
@ApiModel("用户请求参数")
public class UserRequest {
@ApiModelProperty(value = "用户名", required = true, minLength = 3, maxLength = 20)
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在 3-20 个字符之间")
private String username;
@ApiModelProperty(value = "密码", required = true, minLength = 6)
@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码长度不能少于 6 个字符")
private String password;
@ApiModelProperty(value = "邮箱", required = true)
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@ApiModelProperty(value = "年龄", required = true, min = 1, max = 120)
@NotNull(message = "年龄不能为空")
@Min(value = 1, message = "年龄不能小于 1 岁")
@Max(value = 120, message = "年龄不能大于 120 岁")
private Integer age;
@ApiModelProperty(value = "状态", required = true)
@NotNull(message = "状态不能为空")
@EnumValid(target = UserStatus.class, message = "状态值无效")
private Integer status;
}
5.4 自定义异常
@Data
@EqualsAndHashCode(callSuper = true)
public class BusinessException extends RuntimeException {
private Integer code;
private String message;
private Object data;
public BusinessException(String message) {
super(message);
this.code = 500;
this.message = message;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BusinessException(Integer code, String message, Object data) {
super(message);
this.code = code;
this.message = message;
this.data = data;
}
}
5.5 Swagger API 文档访问
- 访问地址:
http://localhost:8080/doc.html - API 文档 JSON:
http://localhost:8080/v3/api-docs - API 分组文档:
http://localhost:8080/v3/api-docs/group/default
5.6 统一响应格式使用
// 成功响应
return ResponseResult.success();
return ResponseResult.success(data);
return ResponseResult.success("操作成功", data);
// 失败响应
return ResponseResult.error();
return ResponseResult.error("操作失败");
return ResponseResult.error(500, "操作失败");
return ResponseResult.error(500, "操作失败", data);
// 分页响应
PageResult<User> pageResult = new PageResult<>(total, records);
return ResponseResult.success(pageResult);
6. 自定义配置
6.1 自定义拦截器
@Component
public class LoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
startTime.set(System.currentTimeMillis());
logger.info("Request: {} {}", request.getMethod(), request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 可以在这里添加响应处理逻辑
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long elapsedTime = System.currentTimeMillis() - startTime.get();
logger.info("Response: {} {} - {}ms", request.getMethod(), request.getRequestURI(), elapsedTime);
startTime.remove();
}
}
@Configuration
public class WebInterceptorConfig implements WebMvcConfigurer {
@Autowired
private LoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/static/**", "/doc.html", "/v3/api-docs/**");
}
}
6.2 自定义过滤器
@Component
@WebFilter(urlPatterns = "/*", filterName = "XssFilter")
public class XssFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 过滤器初始化
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 使用 XSS 防护包装请求
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}
@Override
public void destroy() {
// 过滤器销毁
}
}
6.3 自定义异常处理
@ControllerAdvice
public class CustomExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
@ExceptionHandler(CustomBusinessException.class)
@ResponseBody
public ResponseResult handleCustomBusinessException(CustomBusinessException e) {
logger.error("CustomBusinessException: {}", e.getMessage(), e);
return ResponseResult.error(400, e.getMessage());
}
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseBody
public ResponseResult handleResourceNotFoundException(ResourceNotFoundException e) {
logger.error("ResourceNotFoundException: {}", e.getMessage(), e);
return ResponseResult.error(404, e.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseResult handleException(Exception e) {
logger.error("Exception: {}", e.getMessage(), e);
return ResponseResult.error(500, "服务器内部错误");
}
}
6.4 自定义参数验证器
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = EnumValidator.class)
public @interface EnumValid {
String message() default "枚举值无效";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> target();
}
public class EnumValidator implements ConstraintValidator<EnumValid, Integer> {
private Class<? extends Enum<?>> target;
@Override
public void initialize(EnumValid constraintAnnotation) {
this.target = constraintAnnotation.target();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
Enum<?>[] enumConstants = target.getEnumConstants();
for (Enum<?> enumConstant : enumConstants) {
if (enumConstant.ordinal() == value) {
return true;
}
}
return false;
}
}
6.5 自定义消息转换器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 自定义 JSON 消息转换器
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// 配置日期格式化
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
objectMapper.registerModule(new JavaTimeModule());
// 配置 null 值处理
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
converter.setObjectMapper(objectMapper);
// 将自定义转换器添加到转换器列表的最前面
converters.add(0, converter);
}
}
7. 最佳实践
7.1 API 设计规范
- 版本控制:使用 URL 路径版本控制(如
/api/v1/users) - 资源命名:使用名词复数形式(如
/users、/orders) - HTTP 方法:遵循 RESTful 原则,使用 GET/POST/PUT/DELETE 等方法
- 状态码:使用标准的 HTTP 状态码(如 200、400、404、500)
- 参数传递:路径参数用于资源标识,查询参数用于过滤和分页
7.2 响应格式规范
- 统一结构:使用标准的响应结构(如
{"code": 200, "message": "success", "data": {}}) - 错误处理:提供详细的错误码和错误信息
- 分页响应:统一分页参数(page/size)和响应结构(total/records)
- 空值处理:避免返回 null 值,使用空数组或空对象代替
7.3 参数验证规范
- 前端验证:提供即时的前端验证反馈
- 后端验证:所有参数必须进行后端验证
- 验证注解:使用 JSR-303/JSR-380 验证注解
- 自定义验证:针对业务需求创建自定义验证注解
- 错误提示:提供清晰、友好的验证错误提示
7.4 安全配置规范
- 跨域配置:在生产环境中限制允许的来源
- 请求过滤:防止 XSS、SQL 注入等攻击
- 敏感信息:不在响应中暴露敏感信息
- 接口保护:对敏感接口进行权限控制
7.5 性能优化建议
- 合理使用缓存:对频繁访问的数据使用缓存
- 异步处理:对耗时操作使用异步处理
- 批量操作:减少数据库交互次数
- 懒加载:避免加载不必要的数据
- 响应压缩:启用 GZIP 等压缩算法
8. 常见问题
8.1 Swagger 文档无法访问
原因:Swagger 配置不正确或依赖缺失。
解决方案:
- 检查 Swagger (Knife4j) 依赖是否正确引入
- 确保 Swagger 配置项
knife4j.enable为 true - 检查是否有拦截器或过滤器阻止了 Swagger 访问
- 查看应用日志,查找 Swagger 相关错误
8.2 跨域请求失败
原因:CORS 配置不正确或请求方法不被允许。
解决方案:
- 检查跨域配置项
epoch.framework.web.cors.enabled是否为 true - 确保请求方法在允许的方法列表中
- 检查是否设置了正确的允许来源、头部等配置
- 查看浏览器控制台的 CORS 错误信息
8.3 全局异常处理不生效
原因:异常处理配置不正确或异常类型不匹配。
解决方案:
- 检查异常处理配置项
epoch.framework.web.exception.enabled是否为 true - 确保异常处理类使用了
@ControllerAdvice注解 - 检查异常处理方法的
@ExceptionHandler注解是否正确 - 查看应用日志,确认异常是否被正确捕获
8.4 参数验证不生效
原因:参数验证配置不正确或验证注解使用错误。
解决方案:
- 确保在控制器方法参数上使用了
@Valid或@Validated注解 - 检查验证注解是否正确应用在实体类字段上
- 查看应用日志,确认验证错误是否被正确捕获
- 检查是否引入了 JSR-303/JSR-380 验证依赖
8.5 统一响应格式不生效
原因:响应包装配置不正确或控制器方法返回类型不符合要求。
解决方案:
- 检查响应包装配置项
epoch.framework.web.response.enabled是否为 true - 确保控制器方法返回类型为
ResponseResult或其子类 - 检查响应包装的实现类是否正确配置
- 查看应用日志,确认响应是否被正确包装
9. 版本变更
9.1 主要变更
-
3.4.0:
- 升级 Swagger 版本至 3.0,集成 Knife4j 4.6.0
- 优化全局异常处理,支持更多异常类型
- 完善统一响应格式,增加分页响应支持
- 增强参数验证功能,支持分组验证
-
3.3.0:
- 重构 Web 功能自动配置,提高可扩展性
- 新增跨域配置自动配置
- 优化拦截器和过滤器配置
- 增加请求日志记录功能
-
3.2.0:
- 初始版本,提供 Spring MVC、Swagger、全局异常处理、统一响应格式等功能