后端API全面优化:添加参数验证、全局异常处理和金币增减方法

问题修复:
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状态码和业务错误码
This commit is contained in:
H1d3rOne committed 2026-03-09 16:00:58 +08:00
1 parent 966d7269ba
commit 04e858f6cf
6 files changed
+206

No files matched your search

@@ -0,0 +1,53 @@
package xuan.common;
public class ApiResponse<T> {
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 <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, data, null, 200, System.currentTimeMillis());
}
public static <T> ApiResponse<T> success(String message) {
return new ApiResponse<>(true, null, message, 200, System.currentTimeMillis());
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(false, null, message, 500, System.currentTimeMillis());
}
public static <T> ApiResponse<T> 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; }
}
@@ -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; }
}
@@ -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; }
}
@@ -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; }
}
@@ -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<ApiResponse<?>> handleException(Exception e) {
logger.error("系统异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("系统繁忙,请稍后重试"));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<?>> 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<ApiResponse<?>> 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<ApiResponse<?>> handleIllegalArgument(IllegalArgumentException e) {
logger.warn("参数错误: {}", e.getMessage());
return ResponseEntity.badRequest()
.body(ApiResponse.error(e.getMessage(), 400));
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ApiResponse<?>> handleRuntimeException(RuntimeException e) {
logger.error("运行时异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("操作失败,请稍后重试"));
}
}
@@ -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) {