Ruoyi-vue-plus-5.x第六篇Web开发与前后端交互: 6.1 Spring MVC架构设计
·
👋 大家好,我是 阿问学长!专注于分享优质开源项目解析、毕业设计项目指导支持、幼小初高的教辅资料推荐等,欢迎关注交流!🚀
Spring MVC架构设计
前言
Spring MVC是Spring框架的Web层实现,采用了经典的MVC(Model-View-Controller)设计模式,为Web应用开发提供了灵活、强大的解决方案。RuoYi-Vue-Plus框架基于Spring MVC构建了完整的Web层架构,通过合理的分层设计和组件配置,实现了高效的前后端交互。本文将详细介绍Spring MVC的架构设计、控制器开发、视图解析器配置以及拦截器与过滤器的使用等内容。
MVC模式与组件架构
Spring MVC核心组件
/**
* Spring MVC配置类
*/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.ruoyi.web.controller")
public class WebMvcConfig implements WebMvcConfigurer {
private static final Logger log = LoggerFactory.getLogger(WebMvcConfig.class);
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
@Autowired
private ResourcesConfig resourcesConfig;
/**
* 静态资源处理
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 本地文件上传路径
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
.addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
// swagger配置
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.resourceChain(false);
}
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
/**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
}
/**
* 内容协商配置
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.favorParameter(true)
.parameterName("mediaType")
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
/**
* 消息转换器配置
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Jackson JSON转换器
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = jackson2HttpMessageConverter.getObjectMapper();
// 生成JSON时,将所有Long转换成String
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
mapper.registerModule(simpleModule);
// 时间格式化
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setDateFormat(new SimpleDateFormat(DateUtils.YYYY_MM_DD_HH_MM_SS));
jackson2HttpMessageConverter.setObjectMapper(mapper);
converters.add(jackson2HttpMessageConverter);
// 添加FastJson转换器
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
fastJsonConfig.setDateFormat(DateUtils.YYYY_MM_DD_HH_MM_SS);
// 处理中文乱码问题
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON);
fastConverter.setSupportedMediaTypes(fastMediaTypes);
fastConverter.setFastJsonConfig(fastJsonConfig);
converters.add(fastConverter);
}
/**
* 异常解析器配置
*/
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new GlobalExceptionHandler());
}
/**
* 路径匹配配置
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setUseSuffixPatternMatch(false)
.setUseTrailingSlashMatch(false)
.setUrlPathHelper(new UrlPathHelper())
.setPathMatcher(new AntPathMatcher());
}
/**
* 视图控制器配置
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/unauth").setViewName("error/unauth");
}
/**
* 默认servlet处理器配置
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
/**
* Web应用配置
*/
@Configuration
public class WebApplicationConfig {
/**
* 应用启动配置
*/
@Bean
public ServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
// 设置最大连接数
protocol.setMaxConnections(2000);
// 设置最大线程数
protocol.setMaxThreads(200);
// 设置最小空闲线程数
protocol.setMinSpareThreads(10);
// 设置连接超时时间
protocol.setConnectionTimeout(5000);
// 设置请求处理超时时间
protocol.setAsyncTimeout(30000);
}
});
return factory;
}
/**
* 字符编码过滤器
*/
@Bean
public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
FilterRegistrationBean<CharacterEncodingFilter> registration = new FilterRegistrationBean<>();
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
registration.setFilter(characterEncodingFilter);
registration.addUrlPatterns("/*");
registration.setName("characterEncodingFilter");
registration.setOrder(1);
return registration;
}
/**
* 隐藏HTTP方法过滤器
*/
@Bean
public FilterRegistrationBean<HiddenHttpMethodFilter> hiddenHttpMethodFilter() {
FilterRegistrationBean<HiddenHttpMethodFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new HiddenHttpMethodFilter());
registration.addUrlPatterns("/*");
registration.setName("hiddenHttpMethodFilter");
registration.setOrder(2);
return registration;
}
/**
* 请求方法过滤器
*/
@Bean
public FilterRegistrationBean<HttpPutFormContentFilter> httpPutFormContentFilter() {
FilterRegistrationBean<HttpPutFormContentFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new HttpPutFormContentFilter());
registration.addUrlPatterns("/*");
registration.setName("httpPutFormContentFilter");
registration.setOrder(3);
return registration;
}
}
请求处理流程
/**
* 请求处理流程演示控制器
*/
@RestController
@RequestMapping("/api/demo")
@Slf4j
public class RequestFlowController {
/**
* 演示完整的请求处理流程
*/
@GetMapping("/flow/{id}")
public R<Map<String, Object>> demonstrateRequestFlow(
@PathVariable("id") Long id,
@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "type", defaultValue = "default") String type,
HttpServletRequest request,
HttpServletResponse response) {
log.info("开始处理请求: id={}, name={}, type={}", id, name, type);
// 1. 请求参数处理
Map<String, Object> requestInfo = new HashMap<>();
requestInfo.put("pathVariable", id);
requestInfo.put("requestParam", name);
requestInfo.put("defaultParam", type);
requestInfo.put("requestURI", request.getRequestURI());
requestInfo.put("method", request.getMethod());
requestInfo.put("contentType", request.getContentType());
// 2. 请求头处理
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
}
requestInfo.put("headers", headers);
// 3. 业务逻辑处理
Map<String, Object> businessData = processBusinessLogic(id, name, type);
// 4. 响应处理
response.setHeader("X-Custom-Header", "Custom-Value");
response.setHeader("X-Process-Time", String.valueOf(System.currentTimeMillis()));
// 5. 构建响应数据
Map<String, Object> responseData = new HashMap<>();
responseData.put("requestInfo", requestInfo);
responseData.put("businessData", businessData);
responseData.put("timestamp", new Date());
log.info("请求处理完成: id={}", id);
return R.ok(responseData);
}
/**
* 演示请求体处理
*/
@PostMapping("/body")
public R<Object> handleRequestBody(@RequestBody Map<String, Object> requestBody,
@RequestHeader("Content-Type") String contentType) {
log.info("处理请求体: contentType={}, body={}", contentType, requestBody);
// 处理请求体数据
Map<String, Object> processedData = new HashMap<>();
processedData.put("originalData", requestBody);
processedData.put("processedAt", new Date());
processedData.put("contentType", contentType);
// 数据验证
if (requestBody.containsKey("name") && requestBody.containsKey("value")) {
processedData.put("status", "valid");
processedData.put("message", "数据验证通过");
} else {
processedData.put("status", "invalid");
processedData.put("message", "缺少必要字段");
}
return R.ok(processedData);
}
/**
* 演示文件上传处理
*/
@PostMapping("/upload")
public R<Object> handleFileUpload(@RequestParam("file") MultipartFile file,
@RequestParam(value = "description", required = false) String description) {
log.info("处理文件上传: fileName={}, size={}, description={}",
file.getOriginalFilename(), file.getSize(), description);
try {
// 文件信息
Map<String, Object> fileInfo = new HashMap<>();
fileInfo.put("originalName", file.getOriginalFilename());
fileInfo.put("size", file.getSize());
fileInfo.put("contentType", file.getContentType());
fileInfo.put("description", description);
// 文件处理逻辑
String savedPath = saveFile(file);
fileInfo.put("savedPath", savedPath);
fileInfo.put("uploadTime", new Date());
return R.ok(fileInfo);
} catch (Exception e) {
log.error("文件上传失败", e);
return R.fail("文件上传失败: " + e.getMessage());
}
}
/**
* 演示响应类型处理
*/
@GetMapping(value = "/response", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<Object> handleDifferentResponseTypes(
@RequestParam(value = "format", defaultValue = "json") String format) {
Map<String, Object> data = new HashMap<>();
data.put("message", "Hello World");
data.put("timestamp", new Date());
data.put("format", format);
HttpHeaders headers = new HttpHeaders();
headers.add("X-Response-Format", format);
if ("xml".equalsIgnoreCase(format)) {
headers.setContentType(MediaType.APPLICATION_XML);
} else {
headers.setContentType(MediaType.APPLICATION_JSON);
}
return new ResponseEntity<>(data, headers, HttpStatus.OK);
}
/**
* 业务逻辑处理
*/
private Map<String, Object> processBusinessLogic(Long id, String name, String type) {
Map<String, Object> result = new HashMap<>();
result.put("processedId", id * 2);
result.put("processedName", name != null ? name.toUpperCase() : "DEFAULT");
result.put("processedType", type + "_PROCESSED");
result.put("processTime", System.currentTimeMillis());
return result;
}
/**
* 保存文件
*/
private String saveFile(MultipartFile file) throws IOException {
String fileName = file.getOriginalFilename();
String savedPath = "/uploads/" + System.currentTimeMillis() + "_" + fileName;
// 这里应该实现实际的文件保存逻辑
log.info("文件保存到: {}", savedPath);
return savedPath;
}
}
控制器开发
RESTful API设计
/**
* 用户管理控制器 - RESTful API设计示例
*/
@RestController
@RequestMapping("/api/users")
@Api(tags = "用户管理")
@Validated
@Slf4j
public class UserController extends BaseController {
@Autowired
private ISysUserService userService;
/**
* 获取用户列表
*/
@GetMapping
@ApiOperation("获取用户列表")
@PreAuthorize("@ss.hasPermi('system:user:list')")
public TableDataInfo<SysUser> list(SysUser user) {
startPage();
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
/**
* 根据用户ID获取详细信息
*/
@GetMapping("/{userId}")
@ApiOperation("获取用户详情")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "Long", paramType = "path")
@PreAuthorize("@ss.hasPermi('system:user:query')")
public R<SysUser> getInfo(@PathVariable("userId") Long userId) {
userService.checkUserDataScope(userId);
SysUser user = userService.selectUserById(userId);
return R.ok(user);
}
/**
* 新增用户
*/
@PostMapping
@ApiOperation("新增用户")
@PreAuthorize("@ss.hasPermi('system:user:add')")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
public R<Void> add(@Validated @RequestBody SysUser user) {
if (!userService.checkUserNameUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setCreateBy(getUsername());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
return toAjax(userService.insertUser(user));
}
/**
* 修改用户
*/
@PutMapping("/{userId}")
@ApiOperation("修改用户")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "Long", paramType = "path")
@PreAuthorize("@ss.hasPermi('system:user:edit')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
public R<Void> edit(@PathVariable("userId") Long userId, @Validated @RequestBody SysUser user) {
userService.checkUserAllowed(user);
userService.checkUserDataScope(userId);
if (!userService.checkUserNameUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setUserId(userId);
user.setUpdateBy(getUsername());
return toAjax(userService.updateUser(user));
}
/**
* 删除用户
*/
@DeleteMapping("/{userIds}")
@ApiOperation("删除用户")
@ApiImplicitParam(name = "userIds", value = "用户ID数组", required = true, dataType = "Long[]", paramType = "path")
@PreAuthorize("@ss.hasPermi('system:user:remove')")
@Log(title = "用户管理", businessType = BusinessType.DELETE)
public R<Void> remove(@PathVariable Long[] userIds) {
if (ArrayUtils.contains(userIds, getUserId())) {
return R.fail("当前用户不能删除");
}
return toAjax(userService.deleteUserByIds(userIds));
}
/**
* 重置密码
*/
@PutMapping("/{userId}/password")
@ApiOperation("重置用户密码")
@PreAuthorize("@ss.hasPermi('system:user:resetPwd')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
public R<Void> resetPassword(@PathVariable("userId") Long userId, @RequestBody Map<String, String> params) {
userService.checkUserAllowed(SysUser.builder().userId(userId).build());
userService.checkUserDataScope(userId);
String newPassword = params.get("password");
if (StringUtils.isEmpty(newPassword)) {
return R.fail("新密码不能为空");
}
SysUser user = new SysUser();
user.setUserId(userId);
user.setPassword(SecurityUtils.encryptPassword(newPassword));
user.setUpdateBy(getUsername());
return toAjax(userService.resetUserPwd(user));
}
/**
* 状态修改
*/
@PutMapping("/{userId}/status")
@ApiOperation("修改用户状态")
@PreAuthorize("@ss.hasPermi('system:user:edit')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
public R<Void> changeStatus(@PathVariable("userId") Long userId, @RequestBody Map<String, String> params) {
userService.checkUserAllowed(SysUser.builder().userId(userId).build());
userService.checkUserDataScope(userId);
String status = params.get("status");
SysUser user = new SysUser();
user.setUserId(userId);
user.setStatus(status);
user.setUpdateBy(getUsername());
return toAjax(userService.updateUserStatus(user));
}
/**
* 根据用户编号获取授权角色
*/
@GetMapping("/{userId}/authRole")
@ApiOperation("获取用户授权角色")
@PreAuthorize("@ss.hasPermi('system:user:query')")
public R<Map<String, Object>> authRole(@PathVariable("userId") Long userId) {
SysUser user = userService.selectUserById(userId);
List<SysRole> roles = roleService.selectRolesByUserId(userId);
Map<String, Object> data = new HashMap<>();
data.put("user", user);
data.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
return R.ok(data);
}
/**
* 用户授权角色
*/
@PutMapping("/{userId}/authRole")
@ApiOperation("用户授权角色")
@PreAuthorize("@ss.hasPermi('system:user:edit')")
@Log(title = "用户管理", businessType = BusinessType.GRANT)
public R<Void> insertAuthRole(@PathVariable("userId") Long userId, @RequestParam Long[] roleIds) {
userService.checkUserDataScope(userId);
userService.insertUserAuth(userId, roleIds);
return R.ok();
}
/**
* 获取部门树列表
*/
@GetMapping("/deptTree")
@ApiOperation("获取部门树")
@PreAuthorize("@ss.hasPermi('system:user:list')")
public R<List<Tree<Long>>> deptTree(SysDept dept) {
return R.ok(deptService.selectDeptTreeList(dept));
}
}
/**
* 基础控制器
*/
public class BaseController {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 设置请求分页数据
*/
protected void startPage() {
PageUtils.startPage();
}
/**
* 设置请求排序数据
*/
protected void startOrderBy() {
PageDomain pageDomain = TableSupport.buildPageRequest();
if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) {
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
PageHelper.orderBy(orderBy);
}
}
/**
* 清理分页的线程变量
*/
protected void clearPage() {
PageHelper.clearPage();
}
/**
* 响应请求分页数据
*/
@SuppressWarnings({"rawtypes", "unchecked"})
protected TableDataInfo getDataTable(List<?> list) {
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.SUCCESS);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(PageUtils.getTotal(list));
return rspData;
}
/**
* 返回成功
*/
public R<Void> success() {
return R.ok();
}
/**
* 返回失败消息
*/
public R<Void> error() {
return R.fail();
}
/**
* 返回成功消息
*/
public R<Void> success(String message) {
return R.ok(message);
}
/**
* 返回失败消息
*/
public R<Void> error(String message) {
return R.fail(message);
}
/**
* 返回警告消息
*/
public R<Void> warn(String message) {
return R.warn(message);
}
/**
* 响应返回结果
*
* @param rows 影响行数
* @return 操作结果
*/
protected R<Void> toAjax(int rows) {
return rows > 0 ? R.ok() : R.fail();
}
/**
* 响应返回结果
*
* @param result 结果
* @return 操作结果
*/
protected R<Void> toAjax(boolean result) {
return result ? success() : error();
}
/**
* 页面跳转
*/
public String redirect(String url) {
return StringUtils.format("redirect:{}", url);
}
/**
* 获取用户缓存信息
*/
public LoginUser getLoginUser() {
return LoginHelper.getLoginUser();
}
/**
* 获取登录用户id
*/
public Long getUserId() {
return LoginHelper.getUserId();
}
/**
* 获取登录部门id
*/
public Long getDeptId() {
return LoginHelper.getDeptId();
}
/**
* 获取登录用户名
*/
public String getUsername() {
return LoginHelper.getUsername();
}
}
参数验证与绑定
/**
* 参数验证示例控制器
*/
@RestController
@RequestMapping("/api/validation")
@Validated
@Slf4j
public class ValidationController {
/**
* 基础参数验证
*/
@PostMapping("/basic")
public R<Object> basicValidation(@Validated @RequestBody UserCreateRequest request) {
log.info("基础参数验证: {}", request);
return R.ok("验证通过", request);
}
/**
* 分组验证
*/
@PostMapping("/group")
public R<Object> groupValidation(@Validated(UserCreateRequest.Create.class) @RequestBody UserCreateRequest request) {
log.info("分组验证: {}", request);
return R.ok("创建分组验证通过", request);
}
/**
* 更新分组验证
*/
@PutMapping("/group")
public R<Object> updateGroupValidation(@Validated(UserCreateRequest.Update.class) @RequestBody UserCreateRequest request) {
log.info("更新分组验证: {}", request);
return R.ok("更新分组验证通过", request);
}
/**
* 路径参数验证
*/
@GetMapping("/path/{id}")
public R<Object> pathValidation(@PathVariable @Min(value = 1, message = "ID必须大于0") Long id) {
log.info("路径参数验证: id={}", id);
return R.ok("路径参数验证通过", id);
}
/**
* 查询参数验证
*/
@GetMapping("/query")
public R<Object> queryValidation(
@RequestParam @NotBlank(message = "名称不能为空") String name,
@RequestParam @Min(value = 1, message = "页码必须大于0") Integer page,
@RequestParam @Range(min = 10, max = 100, message = "页面大小必须在10-100之间") Integer size) {
log.info("查询参数验证: name={}, page={}, size={}", name, page, size);
Map<String, Object> result = new HashMap<>();
result.put("name", name);
result.put("page", page);
result.put("size", size);
return R.ok("查询参数验证通过", result);
}
/**
* 自定义验证
*/
@PostMapping("/custom")
public R<Object> customValidation(@Validated @RequestBody CustomValidationRequest request) {
log.info("自定义验证: {}", request);
return R.ok("自定义验证通过", request);
}
/**
* 嵌套对象验证
*/
@PostMapping("/nested")
public R<Object> nestedValidation(@Validated @RequestBody NestedValidationRequest request) {
log.info("嵌套对象验证: {}", request);
return R.ok("嵌套对象验证通过", request);
}
}
/**
* 用户创建请求对象
*/
@Data
public class UserCreateRequest {
// 验证分组接口
public interface Create {}
public interface Update {}
@NotNull(groups = Update.class, message = "更新时ID不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
@Length(min = 2, max = 20, message = "用户名长度必须在2-20之间")
private String username;
@NotBlank(groups = Create.class, message = "创建时密码不能为空")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$",
message = "密码必须包含大小写字母和数字,长度至少8位")
private String password;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Min(value = 18, message = "年龄不能小于18岁")
@Max(value = 100, message = "年龄不能大于100岁")
private Integer age;
@NotEmpty(message = "角色列表不能为空")
private List<@NotNull(message = "角色ID不能为空") Long> roleIds;
}
/**
* 自定义验证请求对象
*/
@Data
public class CustomValidationRequest {
@NotBlank(message = "用户名不能为空")
@UniqueUsername(message = "用户名已存在")
private String username;
@NotBlank(message = "密码不能为空")
@StrongPassword(message = "密码强度不够")
private String password;
@ValidEnum(enumClass = UserStatus.class, message = "用户状态值无效")
private String status;
}
/**
* 嵌套验证请求对象
*/
@Data
public class NestedValidationRequest {
@NotBlank(message = "订单号不能为空")
private String orderNo;
@NotNull(message = "用户信息不能为空")
@Valid
private UserInfo userInfo;
@NotEmpty(message = "订单项不能为空")
@Valid
private List<OrderItem> items;
@Data
public static class UserInfo {
@NotBlank(message = "用户名不能为空")
private String name;
@Email(message = "邮箱格式不正确")
private String email;
}
@Data
public static class OrderItem {
@NotBlank(message = "商品名称不能为空")
private String productName;
@Min(value = 1, message = "数量必须大于0")
private Integer quantity;
@DecimalMin(value = "0.01", message = "价格必须大于0")
private BigDecimal price;
}
}
/**
* 自定义用户名唯一性验证注解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
@Documented
public @interface UniqueUsername {
String message() default "用户名已存在";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
/**
* 用户名唯一性验证器
*/
@Component
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
@Autowired
private ISysUserService userService;
@Override
public boolean isValid(String username, ConstraintValidatorContext context) {
if (StringUtils.isEmpty(username)) {
return true; // 空值由@NotBlank处理
}
return userService.checkUserNameUnique(username);
}
}
/**
* 强密码验证注解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class)
@Documented
public @interface StrongPassword {
String message() default "密码强度不够";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
/**
* 强密码验证器
*/
public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
if (StringUtils.isEmpty(password)) {
return true; // 空值由@NotBlank处理
}
// 密码强度检查:至少8位,包含大小写字母、数字和特殊字符
String pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
return password.matches(pattern);
}
}
/**
* 枚举值验证注解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidEnumValidator.class)
@Documented
public @interface ValidEnum {
String message() default "枚举值无效";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClass();
}
/**
* 枚举值验证器
*/
public class ValidEnumValidator implements ConstraintValidator<ValidEnum, String> {
private Class<? extends Enum<?>> enumClass;
@Override
public void initialize(ValidEnum constraintAnnotation) {
this.enumClass = constraintAnnotation.enumClass();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
Enum<?>[] enumConstants = enumClass.getEnumConstants();
for (Enum<?> enumConstant : enumConstants) {
if (enumConstant.name().equals(value)) {
return true;
}
}
return false;
}
}
视图解析器配置
多视图解析器配置
/**
* 视图解析器配置
*/
@Configuration
public class ViewResolverConfig {
/**
* JSP视图解析器
*/
@Bean
@Order(1)
public InternalResourceViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
resolver.setOrder(1);
return resolver;
}
/**
* Thymeleaf视图解析器
*/
@Bean
@Order(2)
public SpringResourceTemplateResolver thymeleafTemplateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCacheable(false);
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}
@Bean
@Order(2)
public SpringTemplateEngine thymeleafTemplateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(thymeleafTemplateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
@Bean
@Order(2)
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(thymeleafTemplateEngine());
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setOrder(2);
return viewResolver;
}
/**
* FreeMarker视图解析器
*/
@Bean
@Order(3)
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/templates/freemarker/");
configurer.setDefaultEncoding("UTF-8");
Properties settings = new Properties();
settings.setProperty("template_update_delay", "0");
settings.setProperty("default_encoding", "UTF-8");
settings.setProperty("number_format", "0.##########");
settings.setProperty("datetime_format", "yyyy-MM-dd HH:mm:ss");
settings.setProperty("classic_compatible", "true");
settings.setProperty("template_exception_handler", "ignore");
configurer.setFreemarkerSettings(settings);
return configurer;
}
@Bean
@Order(3)
public FreeMarkerViewResolver freeMarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(true);
resolver.setPrefix("");
resolver.setSuffix(".ftl");
resolver.setContentType("text/html;charset=UTF-8");
resolver.setRequestContextAttribute("request");
resolver.setExposeRequestAttributes(true);
resolver.setExposeSessionAttributes(true);
resolver.setExposeSpringMacroHelpers(true);
resolver.setOrder(3);
return resolver;
}
/**
* JSON视图解析器
*/
@Bean
@Order(4)
public MappingJackson2JsonView jsonView() {
MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
jsonView.setContentType("application/json;charset=UTF-8");
jsonView.setPrettyPrint(true);
return jsonView;
}
@Bean
@Order(4)
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(4);
return resolver;
}
@Autowired
private ApplicationContext applicationContext;
}
/**
* 视图控制器
*/
@Controller
@RequestMapping("/view")
public class ViewController {
/**
* JSP视图
*/
@GetMapping("/jsp")
public String jspView(Model model) {
model.addAttribute("message", "Hello JSP!");
model.addAttribute("timestamp", new Date());
return "hello"; // 解析为 /WEB-INF/views/hello.jsp
}
/**
* Thymeleaf视图
*/
@GetMapping("/thymeleaf")
public String thymeleafView(Model model) {
model.addAttribute("message", "Hello Thymeleaf!");
model.addAttribute("user", createSampleUser());
model.addAttribute("items", createSampleItems());
return "hello"; // 解析为 classpath:/templates/hello.html
}
/**
* FreeMarker视图
*/
@GetMapping("/freemarker")
public String freeMarkerView(Model model) {
model.addAttribute("message", "Hello FreeMarker!");
model.addAttribute("user", createSampleUser());
model.addAttribute("items", createSampleItems());
return "hello"; // 解析为 classpath:/templates/freemarker/hello.ftl
}
/**
* JSON视图
*/
@GetMapping("/json")
public String jsonView(Model model) {
model.addAttribute("message", "Hello JSON!");
model.addAttribute("user", createSampleUser());
model.addAttribute("timestamp", new Date());
return "jsonView"; // 使用Bean名称解析器,返回JSON格式
}
/**
* 重定向
*/
@GetMapping("/redirect")
public String redirect() {
return "redirect:/view/thymeleaf";
}
/**
* 转发
*/
@GetMapping("/forward")
public String forward() {
return "forward:/view/thymeleaf";
}
/**
* 创建示例用户
*/
private Map<String, Object> createSampleUser() {
Map<String, Object> user = new HashMap<>();
user.put("id", 1L);
user.put("name", "张三");
user.put("email", "zhangsan@example.com");
user.put("age", 25);
return user;
}
/**
* 创建示例列表
*/
private List<Map<String, Object>> createSampleItems() {
List<Map<String, Object>> items = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
Map<String, Object> item = new HashMap<>();
item.put("id", i);
item.put("name", "Item " + i);
item.put("value", i * 10);
items.add(item);
}
return items;
}
}
总结
本文详细介绍了Spring MVC架构设计,包括:
- MVC模式与组件架构:Spring MVC核心组件配置、请求处理流程演示
- 控制器开发:RESTful API设计、参数验证与绑定、基础控制器封装
- 视图解析器配置:多视图解析器配置、不同视图技术集成
Spring MVC为Web应用开发提供了完整的解决方案,通过合理的架构设计和组件配置,可以构建高效、灵活的Web应用。
在下一篇文章中,我们将探讨RESTful API设计与实现。
参考资料
更多推荐

所有评论(0)