refactor: replace BizException with ResponseStatusException

This commit is contained in:
siujamo committed 2026-08-06 17:20:36 +08:00
1 parent 4aafbaeadc
commit b131738dda
16 files changed
+97 -93

No files matched your search

@@ -3,6 +3,8 @@ package com.onixbyte.deltaforceguide.config;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.onixbyte.deltaforceguide.exeption.ForbiddenException;
import com.onixbyte.deltaforceguide.exeption.UnauthorisedException;
import com.onixbyte.deltaforceguide.filter.TokenAuthenticationFilter;
import com.onixbyte.deltaforceguide.properties.CookieProperties;
import com.onixbyte.deltaforceguide.properties.TokenProperties;
@@ -12,7 +14,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@@ -22,7 +23,6 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.onixbyte.deltaforceguide.exeption.BizException;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.web.cors.CorsConfigurationSource;
@@ -43,7 +43,7 @@ public class SecurityConfig {
/**
* Configures the HTTP security filter chain including endpoint authorisation and JWT filter.
*
* @param http the HTTP security builder
* @param httpSecurity the HTTP security builder
* @return the configured security filter chain
*/
@Bean
@@ -64,11 +64,11 @@ public class SecurityConfig {
.exceptionHandling(customiser -> customiser
.authenticationEntryPoint((request, response, authException) -> {
log.error("Unauthenticated request: {}", request, authException);
throw new BizException(HttpStatus.UNAUTHORIZED, "请先登录");
throw new UnauthorisedException("请先登录");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
log.error("Denied request: {}", request, accessDeniedException);
throw new BizException(HttpStatus.FORBIDDEN, "权限不足");
throw new ForbiddenException("权限不足");
})
)
.addFilterAfter(tokenAuthenticationFilter, ExceptionTranslationFilter.class)
@@ -77,6 +77,7 @@ public class SecurityConfig {
/**
* Provides the BCrypt password encoder for credential hashing.
*
* @return the BCrypt password encoder
*/
@Bean
@@ -1,7 +1,6 @@
package com.onixbyte.deltaforceguide.controller;
import com.onixbyte.deltaforceguide.domain.dto.ErrorResponse;
import com.onixbyte.deltaforceguide.exeption.BizException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
@@ -9,6 +8,7 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.server.ResponseStatusException;
/**
* Global exception handler that translates exceptions into standard error responses.
@@ -17,12 +17,11 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public ResponseEntity<ErrorResponse> handleBizException(BizException exception) {
var status = exception.getStatus();
@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<ErrorResponse> handleResponseStatusException(ResponseStatusException exception) {
var status = exception.getStatusCode();
return ResponseEntity.status(status)
.body(new ErrorResponse(exception.getMessage()));
.body(new ErrorResponse(exception.getReason()));
}
@ExceptionHandler(AuthenticationException.class)
@@ -0,0 +1,11 @@
package com.onixbyte.deltaforceguide.exeption;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class BadRequestException extends ResponseStatusException {
public BadRequestException(String reason) {
super(HttpStatus.BAD_REQUEST, reason);
}
}
@@ -1,40 +0,0 @@
package com.onixbyte.deltaforceguide.exeption;
import org.springframework.http.HttpStatus;
/**
* Custom runtime exception that carries an HTTP status code for API error responses.
*
* @author zihluwang
*/
public class BizException extends RuntimeException {
/**
* The HTTP status code associated with this business exception.
* <p>
* This status code indicates the appropriate HTTP response status that should be returned to
* clients when this exception occurs. It enables consistent error handling across
* REST API endpoints.
*/
private final HttpStatus status;
public BizException(String message) {
super(message);
this.status = HttpStatus.INTERNAL_SERVER_ERROR;
}
public BizException(HttpStatus status, String message) {
super(message);
this.status = status;
}
/**
* Returns the HTTP status code associated with this business exception.
*
* @return the HTTP status code that should be used in the error response
*/
public HttpStatus getStatus() {
return status;
}
}
@@ -0,0 +1,11 @@
package com.onixbyte.deltaforceguide.exeption;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class ForbiddenException extends ResponseStatusException {
public ForbiddenException(String reason) {
super(HttpStatus.FORBIDDEN, reason);
}
}
@@ -0,0 +1,11 @@
package com.onixbyte.deltaforceguide.exeption;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class InternalServerErrorException extends ResponseStatusException {
public InternalServerErrorException(String reason) {
super(HttpStatus.INTERNAL_SERVER_ERROR, reason);
}
}
@@ -0,0 +1,11 @@
package com.onixbyte.deltaforceguide.exeption;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class NotFoundException extends ResponseStatusException {
public NotFoundException(String reason) {
super(HttpStatus.NOT_FOUND, reason);
}
}
@@ -0,0 +1,11 @@
package com.onixbyte.deltaforceguide.exeption;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class UnauthorisedException extends ResponseStatusException {
public UnauthorisedException(String reason) {
super(HttpStatus.UNAUTHORIZED, reason);
}
}
@@ -2,7 +2,7 @@ package com.onixbyte.deltaforceguide.filter;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.onixbyte.deltaforceguide.client.TokenClient;
import com.onixbyte.deltaforceguide.exeption.BizException;
import com.onixbyte.deltaforceguide.exeption.UnauthorisedException;
import com.onixbyte.deltaforceguide.manager.UserManager;
import com.onixbyte.deltaforceguide.manager.UserRoleManager;
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
@@ -16,11 +16,11 @@ import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.util.WebUtils;
@@ -89,7 +89,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
var userWrapper = userManager.findByUsername(username);
if (userWrapper.isEmpty()) {
throw new BizException(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录");
throw new UnauthorisedException("登录已过期,请重新登录");
}
var user = userWrapper.get();
@@ -110,8 +110,8 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
} catch (JWTVerificationException e) {
log.error("JWT verification failed.", e);
handlerExceptionResolver.resolveException(request, response, null,
new BizException(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录"));
} catch (BizException e) {
new UnauthorisedException("登录已过期,请重新登录"));
} catch (ResponseStatusException e) {
handlerExceptionResolver.resolveException(request, response, null, e);
}
}
@@ -1,7 +1,8 @@
package com.onixbyte.deltaforceguide.interceptor;
import com.onixbyte.crypto.util.CryptoUtil;
import com.onixbyte.deltaforceguide.exeption.BizException;
import com.onixbyte.deltaforceguide.exeption.InternalServerErrorException;
import com.onixbyte.deltaforceguide.exeption.UnauthorisedException;
import com.onixbyte.deltaforceguide.manager.WebhookManager;
import com.onixbyte.deltaforceguide.shared.GitHubWebhookHeader;
import com.onixbyte.deltaforceguide.wrapper.RepeatedlyReadRequestWrapper;
@@ -10,7 +11,6 @@ import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@@ -44,7 +44,7 @@ public class GitHubWebhookInterceptor implements HandlerInterceptor {
@NonNull Object handler
) {
if (!(request instanceof RepeatedlyReadRequestWrapper req)) {
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
throw new InternalServerErrorException(
"Request body is not readable");
}
@@ -58,7 +58,7 @@ public class GitHubWebhookInterceptor implements HandlerInterceptor {
if (signatureHeader == null || signatureHeader.isBlank()) {
log.warn("Missing {} header from ip={}",
GitHubWebhookHeader.SIGNATURE_256, request.getRemoteAddr());
throw new BizException(HttpStatus.UNAUTHORIZED,
throw new UnauthorisedException(
"Missing webhook signature header");
}
@@ -70,13 +70,13 @@ public class GitHubWebhookInterceptor implements HandlerInterceptor {
computed.getBytes(StandardCharsets.UTF_8),
signatureHeader.getBytes(StandardCharsets.UTF_8))) {
log.warn("Invalid webhook signature from ip={}", request.getRemoteAddr());
throw new BizException(HttpStatus.UNAUTHORIZED, "Invalid webhook signature");
throw new UnauthorisedException("Invalid webhook signature");
}
return true;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
log.error("Failed to compute HMAC-SHA256", e);
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
throw new InternalServerErrorException(
"Failed to verify webhook signature");
}
}
@@ -4,11 +4,10 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.onixbyte.deltaforceguide.domain.dto.DailyPasswordResponse;
import com.onixbyte.deltaforceguide.exeption.BizException;
import com.onixbyte.deltaforceguide.exeption.InternalServerErrorException;
import com.onixbyte.deltaforceguide.shared.JacksonModules;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@@ -76,7 +75,7 @@ public class DailyPasswordManager {
.body(DailyPasswordResponse.class);
if (Objects.isNull(response)) {
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "暂无每日密码数据。");
throw new InternalServerErrorException("暂无每日密码数据。");
}
redisTemplate.opsForValue().set(key, response, Duration.ofDays(1L));
@@ -1,7 +1,8 @@
package com.onixbyte.deltaforceguide.security.provider;
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
import com.onixbyte.deltaforceguide.exeption.BizException;
import com.onixbyte.deltaforceguide.exeption.InternalServerErrorException;
import com.onixbyte.deltaforceguide.exeption.UnauthorisedException;
import com.onixbyte.deltaforceguide.manager.UserManager;
import com.onixbyte.deltaforceguide.repository.UserCredentialRepository;
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
@@ -10,7 +11,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -50,14 +50,14 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof UsernamePasswordAuthentication usernamePasswordAuthentication)) {
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "用户认证失败,请稍后再试。");
throw new InternalServerErrorException("用户认证失败,请稍后再试。");
}
// get userContainer from database
var userContainer = userManager.findByUsernameOrEmail(usernamePasswordAuthentication.getPrincipal());
if (userContainer.isEmpty()) {
log.error("User {} is trying to authenticate but no userContainer found.", usernamePasswordAuthentication.getPrincipal());
throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。");
throw new UnauthorisedException("用户名或密码错误。");
}
var user = userContainer.get();
@@ -68,12 +68,12 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
// get userContainer credentials from database
var userCredentials = userCredentialRepository.findOne(Example.of(userCredentialExample))
.orElseThrow(() -> new BizException(HttpStatus.UNAUTHORIZED, "您还没有配置密码,请联系管理员处理。"));
.orElseThrow(() -> new UnauthorisedException("您还没有配置密码,请联系管理员处理。"));
// validate password
if (!passwordEncoder.matches(usernamePasswordAuthentication.getCredentials(), userCredentials.getCredential())) {
log.error("User {} is trying to authenticate but password is incorrect.", usernamePasswordAuthentication.getPrincipal());
throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。");
throw new UnauthorisedException("用户名或密码错误。");
}
// erase credentials
@@ -2,11 +2,10 @@ package com.onixbyte.deltaforceguide.service;
import com.onixbyte.deltaforceguide.domain.dto.LoginRequest;
import com.onixbyte.deltaforceguide.domain.entity.User;
import com.onixbyte.deltaforceguide.exeption.BizException;
import com.onixbyte.deltaforceguide.exeption.InternalServerErrorException;
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.stereotype.Service;
@@ -33,7 +32,6 @@ public class AuthService {
*
* @param request the login credentials containing principle and password
* @return the authenticated {@link User}
* @throws BizException if authentication fails or the result type is unexpected
*/
public User login(LoginRequest request) {
var _authentication = authenticationManager.authenticate(UsernamePasswordAuthentication
@@ -43,7 +41,7 @@ public class AuthService {
"Type mismatched, required type is UsernamePasswordAuthentication but got {}.",
_authentication.getClass()
);
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "登录服务异常,请稍后再试。");
throw new InternalServerErrorException("登录服务异常,请稍后再试。");
}
return authentication.getDetails();
@@ -5,7 +5,7 @@ import com.onixbyte.deltaforceguide.domain.dto.FirearmResponse;
import com.onixbyte.deltaforceguide.domain.dto.PageResponse;
import com.onixbyte.deltaforceguide.domain.entity.Firearm;
import com.onixbyte.deltaforceguide.enumeration.FirearmType;
import com.onixbyte.deltaforceguide.exeption.BizException;
import com.onixbyte.deltaforceguide.exeption.NotFoundException;
import com.onixbyte.deltaforceguide.repository.FirearmRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -84,7 +84,7 @@ public class FirearmService {
*/
public FirearmResponse updateFirearm(Long id, FirearmRequest request) {
var firearm = firearmRepository.findById(id)
.orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "Firearm not found: " + id));
.orElseThrow(() -> new NotFoundException("Firearm not found: " + id));
firearm.setName(request.name());
firearm.setType(request.type());
@@ -105,7 +105,7 @@ public class FirearmService {
*/
public void deleteFirearm(Long id) {
Firearm firearm = firearmRepository.findById(id)
.orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "Firearm not found: " + id));
.orElseThrow(() -> new NotFoundException("Firearm not found: " + id));
firearmRepository.delete(firearm);
}
}
@@ -1,14 +1,10 @@
package com.onixbyte.deltaforceguide.service;
import com.onixbyte.deltaforceguide.domain.dto.BuildSummaryResponse;
import com.onixbyte.deltaforceguide.domain.dto.ChangePasswordRequest;
import com.onixbyte.deltaforceguide.domain.dto.PageResponse;
import com.onixbyte.deltaforceguide.domain.dto.UpdateProfileRequest;
import com.onixbyte.deltaforceguide.domain.dto.UserProfileResponse;
import com.onixbyte.deltaforceguide.domain.dto.*;
import com.onixbyte.deltaforceguide.domain.entity.User;
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
import com.onixbyte.deltaforceguide.domain.entity.UserRole;
import com.onixbyte.deltaforceguide.exeption.BizException;
import com.onixbyte.deltaforceguide.exeption.BadRequestException;
import com.onixbyte.deltaforceguide.manager.UserCredentialManager;
import com.onixbyte.deltaforceguide.manager.UserManager;
import com.onixbyte.deltaforceguide.manager.UserRoleManager;
@@ -213,12 +209,11 @@ public class UserService {
*
* @param user the authenticated user
* @param request the change password request containing old and new passwords
* @throws BizException if the old password is incorrect
*/
public void changePassword(User user, ChangePasswordRequest request) {
var credential = userCredentialManager.findByUserIdAndProvider(user.getId(), CredentialProvider.LOCAL);
if (credential.isEmpty() || !passwordEncoder.matches(request.oldPassword(), credential.get().getCredential())) {
throw new BizException(HttpStatus.BAD_REQUEST, "旧密码不正确。");
throw new BadRequestException("旧密码不正确。");
}
var encoded = passwordEncoder.encode(request.newPassword());
upsertCredential(user.getId(), CredentialProvider.LOCAL, encoded);
@@ -243,11 +238,10 @@ public class UserService {
*
* @param userId the target user ID
* @param role the role to assign
* @throws BizException if attempting to assign a protected role
*/
public void assignRole(Long userId, String role) {
if (!Role.ROLE_ADMIN.equals(role)) {
throw new BizException(HttpStatus.BAD_REQUEST, "只能通过API分配ROLE_ADMIN角色。");
throw new BadRequestException("只能通过API分配ROLE_ADMIN角色。");
}
var user = ensureUserExists(userId);
if (userRoleManager.existsByUserIdAndRole(userId, role)) {
@@ -265,11 +259,10 @@ public class UserService {
*
* @param userId the target user ID
* @param role the role to remove
* @throws BizException if attempting to remove a protected role
*/
public void removeRole(Long userId, String role) {
if (!Role.ROLE_ADMIN.equals(role)) {
throw new BizException(HttpStatus.BAD_REQUEST, "只能通过API移除ROLE_ADMIN角色。");
throw new BadRequestException("只能通过API移除ROLE_ADMIN角色。");
}
ensureUserExists(userId);
userRoleManager.deleteByUserIdAndRole(userId, role);
@@ -1,14 +1,13 @@
package com.onixbyte.deltaforceguide.service;
import com.onixbyte.deltaforceguide.domain.dto.*;
import com.onixbyte.deltaforceguide.exeption.BadRequestException;
import com.onixbyte.deltaforceguide.manager.ModificationManager;
import com.onixbyte.deltaforceguide.manager.WebhookManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import com.onixbyte.deltaforceguide.exeption.BizException;
import org.yaml.snakeyaml.Yaml;
import java.time.Duration;
@@ -111,7 +110,7 @@ public class WebhookService {
toLong(data.get("firearmId")),
(String) data.get("firearmName"));
if (firearmId == null) {
throw new BizException(HttpStatus.BAD_REQUEST,
throw new BadRequestException(
"YAML must contain firearmId or firearmName");
}
String name = (String) data.get("name");