From 04e858f6cff5486f29c0c0a2c60491015484dc28 Mon Sep 17 00:00:00 2001 From: H1d3rOne Date: Mon, 9 Mar 2026 16:00:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AFAPI=E5=85=A8=E9=9D=A2?= =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E6=B7=BB=E5=8A=A0=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E3=80=81=E5=85=A8=E5=B1=80=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=92=8C=E9=87=91=E5=B8=81=E5=A2=9E=E5=87=8F?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题修复: 1. 参数验证问题 - 创建DTO类:UpdateVipRequest、UpdateCoinsRequest、UpdatePhoneRequest - 使用@NotBlank、@NotNull、@Min、@Max、@Pattern注解验证参数 - 防止NullPointerException和非法参数 2. 全局异常处理 - 创建GlobalExceptionHandler全局异常处理器 - 统一处理Exception、MethodArgumentNotValidException等异常 - 返回友好的错误信息,避免暴露敏感信息 - 添加日志记录 3. 统一响应格式 - 创建ApiResponse统一响应类 - 包含success、data、error、code、timestamp字段 - 提供success()和error()静态方法 4. 金币增减方法 - UserInfoService新增addCoins方法(增加金币) - UserInfoService新增deductCoins方法(扣除金币) - 添加参数验证和余额检查 - 金币不足时抛出IllegalArgumentException 技术实现: - 使用Spring @RestControllerAdvice实现全局异常处理 - 使用javax.validation注解进行参数验证 - 使用SLF4J记录日志 - 统一HTTP状态码和业务错误码 --- src/main/java/xuan/common/ApiResponse.java | 53 ++++++++++++++++ .../java/xuan/dto/UpdateCoinsRequest.java | 21 +++++++ .../java/xuan/dto/UpdatePhoneRequest.java | 20 ++++++ src/main/java/xuan/dto/UpdateVipRequest.java | 23 +++++++ .../exception/GlobalExceptionHandler.java | 62 +++++++++++++++++++ .../java/xuan/service/UserInfoService.java | 27 ++++++++ 6 files changed, 206 insertions(+) create mode 100644 src/main/java/xuan/common/ApiResponse.java create mode 100644 src/main/java/xuan/dto/UpdateCoinsRequest.java create mode 100644 src/main/java/xuan/dto/UpdatePhoneRequest.java create mode 100644 src/main/java/xuan/dto/UpdateVipRequest.java create mode 100644 src/main/java/xuan/exception/GlobalExceptionHandler.java diff --git a/src/main/java/xuan/common/ApiResponse.java b/src/main/java/xuan/common/ApiResponse.java new file mode 100644 index 0000000..97ff91f --- /dev/null +++ b/src/main/java/xuan/common/ApiResponse.java @@ -0,0 +1,53 @@ +package xuan.common; + +public class ApiResponse { + + private boolean success; + private T data; + private String error; + private Integer code; + private Long timestamp; + + public ApiResponse() { + this.timestamp = System.currentTimeMillis(); + } + + public ApiResponse(boolean success, T data, String error, Integer code, Long timestamp) { + this.success = success; + this.data = data; + this.error = error; + this.code = code; + this.timestamp = timestamp; + } + + public static ApiResponse success(T data) { + return new ApiResponse<>(true, data, null, 200, System.currentTimeMillis()); + } + + public static ApiResponse success(String message) { + return new ApiResponse<>(true, null, message, 200, System.currentTimeMillis()); + } + + public static ApiResponse error(String message) { + return new ApiResponse<>(false, null, message, 500, System.currentTimeMillis()); + } + + public static ApiResponse error(String message, Integer code) { + return new ApiResponse<>(false, null, message, code, System.currentTimeMillis()); + } + + public boolean isSuccess() { return success; } + public void setSuccess(boolean success) { this.success = success; } + + public T getData() { return data; } + public void setData(T data) { this.data = data; } + + public String getError() { return error; } + public void setError(String error) { this.error = error; } + + public Integer getCode() { return code; } + public void setCode(Integer code) { this.code = code; } + + public Long getTimestamp() { return timestamp; } + public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } +} diff --git a/src/main/java/xuan/dto/UpdateCoinsRequest.java b/src/main/java/xuan/dto/UpdateCoinsRequest.java new file mode 100644 index 0000000..6fca556 --- /dev/null +++ b/src/main/java/xuan/dto/UpdateCoinsRequest.java @@ -0,0 +1,21 @@ +package xuan.dto; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +public class UpdateCoinsRequest { + + @NotBlank(message = "userId不能为空") + private String userId; + + @NotNull(message = "coins不能为空") + @Min(value = 0, message = "金币数不能小于0") + private Integer coins; + + public String getUserId() { return userId; } + public void setUserId(String userId) { this.userId = userId; } + + public Integer getCoins() { return coins; } + public void setCoins(Integer coins) { this.coins = coins; } +} diff --git a/src/main/java/xuan/dto/UpdatePhoneRequest.java b/src/main/java/xuan/dto/UpdatePhoneRequest.java new file mode 100644 index 0000000..79bdc69 --- /dev/null +++ b/src/main/java/xuan/dto/UpdatePhoneRequest.java @@ -0,0 +1,20 @@ +package xuan.dto; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +public class UpdatePhoneRequest { + + @NotBlank(message = "userId不能为空") + private String userId; + + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + public String getUserId() { return userId; } + public void setUserId(String userId) { this.userId = userId; } + + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } +} diff --git a/src/main/java/xuan/dto/UpdateVipRequest.java b/src/main/java/xuan/dto/UpdateVipRequest.java new file mode 100644 index 0000000..6ae2ced --- /dev/null +++ b/src/main/java/xuan/dto/UpdateVipRequest.java @@ -0,0 +1,23 @@ +package xuan.dto; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +public class UpdateVipRequest { + + @NotBlank(message = "userId不能为空") + private String userId; + + @NotNull(message = "vipLevel不能为空") + @Min(value = 0, message = "VIP等级不能小于0") + @Max(value = 10, message = "VIP等级不能大于10") + private Integer vipLevel; + + public String getUserId() { return userId; } + public void setUserId(String userId) { this.userId = userId; } + + public Integer getVipLevel() { return vipLevel; } + public void setVipLevel(Integer vipLevel) { this.vipLevel = vipLevel; } +} diff --git a/src/main/java/xuan/exception/GlobalExceptionHandler.java b/src/main/java/xuan/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..ff2f9a3 --- /dev/null +++ b/src/main/java/xuan/exception/GlobalExceptionHandler.java @@ -0,0 +1,62 @@ +package xuan.exception; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import xuan.common.ApiResponse; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.util.stream.Collectors; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception e) { + logger.error("系统异常", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("系统繁忙,请稍后重试")); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidation(MethodArgumentNotValidException e) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(", ")); + logger.warn("参数验证失败: {}", message); + return ResponseEntity.badRequest() + .body(ApiResponse.error(message, 400)); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity> handleConstraintViolation(ConstraintViolationException e) { + String message = e.getConstraintViolations().stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.joining(", ")); + logger.warn("参数验证失败: {}", message); + return ResponseEntity.badRequest() + .body(ApiResponse.error(message, 400)); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleIllegalArgument(IllegalArgumentException e) { + logger.warn("参数错误: {}", e.getMessage()); + return ResponseEntity.badRequest() + .body(ApiResponse.error(e.getMessage(), 400)); + } + + @ExceptionHandler(RuntimeException.class) + public ResponseEntity> handleRuntimeException(RuntimeException e) { + logger.error("运行时异常", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("操作失败,请稍后重试")); + } +} diff --git a/src/main/java/xuan/service/UserInfoService.java b/src/main/java/xuan/service/UserInfoService.java index 2f53ce5..f667861 100644 --- a/src/main/java/xuan/service/UserInfoService.java +++ b/src/main/java/xuan/service/UserInfoService.java @@ -54,6 +54,33 @@ public class UserInfoService { return null; } + public UserInfo addCoins(String userId, Integer amount) { + if (amount == null || amount <= 0) { + throw new IllegalArgumentException("金币数量必须大于0"); + } + UserInfo user = userInfoRepository.findByUserId(userId); + if (user != null) { + user.setCoins(user.getCoins() + amount); + return userInfoRepository.save(user); + } + return null; + } + + public UserInfo deductCoins(String userId, Integer amount) { + if (amount == null || amount <= 0) { + throw new IllegalArgumentException("金币数量必须大于0"); + } + UserInfo user = userInfoRepository.findByUserId(userId); + if (user != null) { + if (user.getCoins() < amount) { + throw new IllegalArgumentException("金币不足"); + } + user.setCoins(user.getCoins() - amount); + return userInfoRepository.save(user); + } + return null; + } + public UserInfo updatePhone(String userId, String phone) { UserInfo user = userInfoRepository.findByUserId(userId); if (user != null) {