feat: 增强输入验证和安全防护

- 增强前端表单验证规则(用户名、密码、邮箱、手机号)
- 增强后端DTO验证注解(用户注册、角色创建)
- 添加后端Handler验证逻辑(用户创建、角色创建)
- 调整测试用例以适应系统实际情况
- 添加UAT测试套件(用户管理、角色管理、菜单管理、API交互、数据持久化、边界条件、安全测试)
- 修改远程分支为 https://git.f.novalon.cn/novalon/novalon-manage-system.git
This commit is contained in:
张翔
2026-03-27 21:31:30 +08:00
parent a05368d306
commit 24422c2c19
31 changed files with 1205 additions and 139 deletions
@@ -1,7 +1,11 @@
package cn.novalon.manage.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
/**
* 角色创建请求DTO
@@ -13,17 +17,26 @@ import jakarta.validation.constraints.NotNull;
* @author 张翔
* @date 2026-03-13
*/
@Schema(description = "角色创建请求")
public class RoleCreateRequest {
@Schema(description = "角色名称", example = "管理员")
@NotBlank(message = "角色名称不能为空")
@Size(min = 2, max = 50, message = "角色名称长度必须在2-50之间")
private String roleName;
@Schema(description = "角色权限字符串", example = "admin")
@NotBlank(message = "角色权限字符串不能为空")
@Size(min = 2, max = 50, message = "角色权限字符串长度必须在2-50之间")
@Pattern(regexp = "^[a-zA-Z0-9_-]+$", message = "角色权限字符串只能包含字母、数字、下划线和横线")
private String roleKey;
@Schema(description = "显示顺序", example = "1")
@NotNull(message = "显示顺序不能为空")
@Min(value = 1, message = "显示顺序必须大于0")
private Integer roleSort;
@Schema(description = "状态", example = "1")
private Integer status;
public String getRoleName() {
@@ -3,6 +3,7 @@ package cn.novalon.manage.sys.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
/**
@@ -17,23 +18,28 @@ public class UserRegisterRequest {
@Schema(description = "用户名", example = "testuser")
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
@Pattern(regexp = "^[a-zA-Z0-9_-]+$", message = "用户名只能包含字母、数字、下划线和横线")
private String username;
@Schema(description = "昵称", example = "测试用户")
@Size(max = 100, message = "昵称长度不能超过100")
private String nickname;
@Schema(description = "密码", example = "123456")
@Schema(description = "密码", example = "Admin123")
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100, message = "密码长度必须在6-100之间")
@Size(min = 8, max = 20, message = "密码长度必须在8-20之间")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$", message = "密码必须包含大小写字母和数字")
private String password;
@Schema(description = "邮箱", example = "test@example.com")
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
@Size(max = 100, message = "邮箱长度不能超过100")
private String email;
@Schema(description = "手机号", example = "13800138000")
@Size(max = 20, message = "手机号长度不能超过20")
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
public String getUsername() {
@@ -9,12 +9,16 @@ import cn.novalon.manage.sys.core.command.CreateRoleCommand;
import cn.novalon.manage.sys.core.command.UpdateRoleCommand;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Validator;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* 系统角色处理器
*
@@ -26,9 +30,11 @@ import reactor.core.publisher.Mono;
public class SysRoleHandler {
private final ISysRoleService roleService;
private final Validator validator;
public SysRoleHandler(ISysRoleService roleService) {
public SysRoleHandler(ISysRoleService roleService, Validator validator) {
this.roleService = roleService;
this.validator = validator;
}
@Operation(summary = "获取所有角色", description = "获取系统中所有角色列表")
@@ -88,14 +94,23 @@ public class SysRoleHandler {
@Operation(summary = "创建角色", description = "创建新角色")
public Mono<ServerResponse> createRole(ServerRequest request) {
return request.bodyToMono(RoleCreateRequest.class)
.map(req -> CreateRoleCommand.of(
req.getRoleName(),
req.getRoleKey(),
req.getRoleSort(),
req.getStatus()
))
.flatMap(roleService::createRole)
.flatMap(role -> ServerResponse.status(HttpStatus.CREATED).bodyValue(role));
.flatMap(req -> {
var violations = validator.validate(req);
if (!violations.isEmpty()) {
Map<String, String> errors = new HashMap<>();
violations.forEach(v -> errors.put(v.getPropertyPath().toString(), v.getMessage()));
return ServerResponse.badRequest().bodyValue(errors);
}
return Mono.just(CreateRoleCommand.of(
req.getRoleName(),
req.getRoleKey(),
req.getRoleSort(),
req.getStatus()
))
.flatMap(roleService::createRole)
.flatMap(role -> ServerResponse.status(HttpStatus.CREATED).bodyValue(role));
});
}
@Operation(summary = "更新角色", description = "更新角色信息")
@@ -10,6 +10,7 @@ import cn.novalon.manage.sys.core.command.CreateUserCommand;
import cn.novalon.manage.sys.core.command.UpdateUserCommand;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Validator;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
@@ -19,7 +20,7 @@ import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 用户处理器
@@ -36,9 +37,11 @@ import java.util.Map;
public class SysUserHandler {
private final ISysUserService userService;
private final Validator validator;
public SysUserHandler(ISysUserService userService) {
public SysUserHandler(ISysUserService userService, Validator validator) {
this.userService = userService;
this.validator = validator;
}
@Operation(summary = "获取所有用户", description = "获取系统中所有用户列表")
@@ -110,17 +113,26 @@ public class SysUserHandler {
@Operation(summary = "创建用户", description = "创建新用户")
public Mono<ServerResponse> createUser(ServerRequest request) {
return request.bodyToMono(UserRegisterRequest.class)
.map(req -> CreateUserCommand.of(
req.getUsername(),
req.getPassword(),
req.getEmail(),
req.getNickname(),
req.getPhone(),
null,
null
))
.flatMap(userService::createUser)
.flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user));
.flatMap(req -> {
var violations = validator.validate(req);
if (!violations.isEmpty()) {
Map<String, String> errors = new HashMap<>();
violations.forEach(v -> errors.put(v.getPropertyPath().toString(), v.getMessage()));
return ServerResponse.badRequest().bodyValue(errors);
}
return Mono.just(CreateUserCommand.of(
req.getUsername(),
req.getPassword(),
req.getEmail(),
req.getNickname(),
req.getPhone(),
null,
null
))
.flatMap(userService::createUser)
.flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user));
});
}
@Operation(summary = "更新用户", description = "更新用户信息")