Compare commits

...

2 Commits

Author SHA1 Message Date
yulinling
bda0b64406 feat: 优化数据库连接配置 2025-08-05 23:31:16 +08:00
yulinling
75c35369fc feat: 自定义校验
fix: 修复valid
2025-08-05 23:02:47 +08:00
10 changed files with 217 additions and 82 deletions

19
pom.xml
View File

@ -201,6 +201,25 @@
<version>3.0.2</version> <version>3.0.2</version>
</dependency> </dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<!-- 公共工具 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies> </dependencies>
<!-- 插件配置 --> <!-- 插件配置 -->

View File

@ -0,0 +1,39 @@
package asia.yulinling.workflow.annotation;
import asia.yulinling.workflow.annotation.validator.FieldMatchValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>
* 字段校验
* </p>
*
* @author YLL
* @since 2025/8/5
*/
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
public @interface FieldMatch {
String message() default "字段值不匹配";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String first();
String second();
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface List {
FieldMatch[] value();
}
}

View File

@ -0,0 +1,41 @@
package asia.yulinling.workflow.annotation.validator;
import asia.yulinling.workflow.annotation.FieldMatch;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
/**
* <p>
* 校验逻辑
* </p>
*
* @author YLL
* @since 2025/8/5
*/
@Slf4j
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
@Override
public void initialize(FieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
try {
final Object firstObj = BeanUtils.getProperty(o, firstFieldName);
final Object secondObj = BeanUtils.getProperty(o, secondFieldName);
return firstObj == null && secondObj == null ||
firstObj != null && firstObj.equals(secondObj);
} catch (final Exception ignore) {
// ignore
}
return true;
}
}

View File

@ -8,18 +8,16 @@ import asia.yulinling.workflow.model.vo.LoginVO;
import asia.yulinling.workflow.model.vo.RegisterVO; import asia.yulinling.workflow.model.vo.RegisterVO;
import asia.yulinling.workflow.service.AuthService; import asia.yulinling.workflow.service.AuthService;
import asia.yulinling.workflow.utils.JwtUtil; import asia.yulinling.workflow.utils.JwtUtil;
import com.alibaba.druid.support.spring.stat.annotation.Stat;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -33,6 +31,7 @@ import org.springframework.web.bind.annotation.RestController;
* @author YLL * @author YLL
* @since 2025/6/13 * @since 2025/6/13
*/ */
@Slf4j
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/auth") @RequestMapping("/auth")
@ -44,18 +43,12 @@ public class AuthController {
@PostMapping("/login") @PostMapping("/login")
public ResponseEntity<ApiResponse<LoginVO>> login(@Valid @RequestBody LoginRequest loginRequest) { public ResponseEntity<ApiResponse<LoginVO>> login(@Valid @RequestBody LoginRequest loginRequest) {
try { Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( loginRequest.getUsername(), loginRequest.getPassword()
loginRequest.getUsername(), loginRequest.getPassword() ));
));
LoginVO loginVO = authService.login(authentication, loginRequest.getRememberMe()); LoginVO loginVO = authService.login(authentication, loginRequest.getRememberMe());
return ResponseEntity.ok().body(ApiResponse.ofStatus(Status.LOGIN_SUCCESS, loginVO)); return ResponseEntity.ok().body(ApiResponse.ofStatus(Status.LOGIN_SUCCESS, loginVO));
} catch (BadCredentialsException e) {
return ResponseEntity.ok().body(ApiResponse.ofStatus(Status.USERNAME_PASSWORD_ERROR));
} catch (LockedException e) {
return ResponseEntity.ok().body(ApiResponse.ofStatus(Status.ACCOUNT_LOCKED));
}
} }
@PostMapping("/logout") @PostMapping("/logout")
@ -69,8 +62,9 @@ public class AuthController {
} }
@PostMapping("/register") @PostMapping("/register")
public ApiResponse<RegisterVO> register(@RequestBody RegisterRequest registerRequest) throws Exception { public ApiResponse<RegisterVO> register(@Valid @RequestBody RegisterRequest registerRequest) throws Exception {
return authService.register(registerRequest); authService.register(registerRequest);
return ApiResponse.ofStatus(Status.SUCCESS);
} }
} }

View File

@ -1,6 +1,7 @@
package asia.yulinling.workflow.dto.request; package asia.yulinling.workflow.dto.request;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
/** /**
* <p> * <p>
@ -27,5 +28,6 @@ public class LoginRequest {
/** /**
* 记住我 * 记住我
*/ */
@NotNull(message = "记住我不能为空")
private Boolean rememberMe = false; private Boolean rememberMe = false;
} }

View File

@ -1,5 +1,6 @@
package asia.yulinling.workflow.dto.request; package asia.yulinling.workflow.dto.request;
import asia.yulinling.workflow.annotation.FieldMatch;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
@ -15,6 +16,7 @@ import lombok.Data;
* @since 2025/6/19 * @since 2025/6/19
*/ */
@Data @Data
@FieldMatch(first = "password", second = "confirmPassword", message = "密码不一致")
public class RegisterRequest { public class RegisterRequest {
/** /**

View File

@ -4,11 +4,23 @@ import asia.yulinling.workflow.exception.PageException;
import asia.yulinling.workflow.exception.ServiceException; import asia.yulinling.workflow.exception.ServiceException;
import asia.yulinling.workflow.model.ApiResponse; import asia.yulinling.workflow.model.ApiResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
/** /**
* <p> * <p>
* 全局异常处理 * 全局异常处理
@ -22,6 +34,63 @@ import org.springframework.web.servlet.ModelAndView;
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
private static final String DEFAULT_ERROR_VIEW = "error"; private static final String DEFAULT_ERROR_VIEW = "error";
/**
* 统一 ServiceException 异常处理
*
* @param e ServiceException
* @return 统一返回 json 格式
*/
@ExceptionHandler(ServiceException.class)
@ResponseBody
public ApiResponse<?> catchErrorHandler(ServiceException e) {
log.error("service error:{}", e.getMessage());
return ApiResponse.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage(), null);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<?>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
// Map<String, String> errors = new LinkedHashMap<>();
//
// ex.getBindingResult().getAllErrors().forEach(error -> {
// String fieldName;
//
// if (error instanceof FieldError) {
// // 处理字段级错误 @NotBlank, @Size
// fieldName = ((FieldError) error).getField();
// } else {
// // 处理对象级错误 @FieldMatch
// fieldName = error.getObjectName(); // 返回对象名 "registerRequest"
// }
//
// String errorMessage = error.getDefaultMessage(); // 获取默认错误消息
// errors.put(fieldName, errorMessage);
// });
ObjectError objectError = ex.getBindingResult().getAllErrors().get(0);
String defaultMessage = objectError.getDefaultMessage();
ApiResponse<?> response = ApiResponse.of(HttpStatus.BAD_REQUEST.value(), defaultMessage, null);
return ResponseEntity.badRequest().body(response);
}
/**
* 统一 页面 异常处理
*
* @param e PageException
* @return 统一跳转到异常页面
*/
@ExceptionHandler(PageException.class)
public ModelAndView pageErrorHandler(PageException e) {
log.error("test" + e.getMessage());
ModelAndView view = new ModelAndView();
view.addObject("exception", e);
view.setViewName(DEFAULT_ERROR_VIEW);
return view;
}
/** /**
* 统一 json 异常处理 * 统一 json 异常处理
* *
@ -34,32 +103,4 @@ public class GlobalExceptionHandler {
log.error("json error:{}", e.getMessage()); log.error("json error:{}", e.getMessage());
return ApiResponse.of(500, e.getMessage(), null); return ApiResponse.of(500, e.getMessage(), null);
} }
/**
* 统一 ServiceException 异常处理
*
* @param e ServiceException
* @return 统一返回 json 格式
*/
@ExceptionHandler(ServiceException.class)
@ResponseBody
public ApiResponse<?> catchErrorHandler(ServiceException e) {
log.error("service error:{}", e.getMessage());
return ApiResponse.of(500, e.getMessage(), null);
}
/**
* 统一 页面 异常处理
*
* @param e PageException
* @return 统一跳转到异常页面
*/
@ExceptionHandler(PageException.class)
public ModelAndView pageErrorHandler(PageException e) {
log.error(e.getMessage());
ModelAndView view = new ModelAndView();
view.addObject("exception", e);
view.setViewName(DEFAULT_ERROR_VIEW);
return view;
}
} }

View File

@ -37,5 +37,5 @@ public interface AuthService {
* @param request 注册请求 * @param request 注册请求
* @return 请求结果 * @return 请求结果
*/ */
ApiResponse<RegisterVO> register(RegisterRequest request) throws Exception; RegisterVO register(RegisterRequest request) throws Exception;
} }

View File

@ -1,7 +1,6 @@
package asia.yulinling.workflow.service.impl; package asia.yulinling.workflow.service.impl;
import asia.yulinling.workflow.constant.Status; import asia.yulinling.workflow.constant.Status;
import asia.yulinling.workflow.dto.request.LoginRequest;
import asia.yulinling.workflow.dto.request.RegisterRequest; import asia.yulinling.workflow.dto.request.RegisterRequest;
import asia.yulinling.workflow.mapper.UserMapper; import asia.yulinling.workflow.mapper.UserMapper;
import asia.yulinling.workflow.model.ApiResponse; import asia.yulinling.workflow.model.ApiResponse;
@ -13,14 +12,10 @@ import asia.yulinling.workflow.service.AuthService;
import asia.yulinling.workflow.utils.JwtUtil; import asia.yulinling.workflow.utils.JwtUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -52,18 +47,18 @@ public class AuthServiceImpl implements AuthService {
*/ */
@Override @Override
public LoginVO login(Authentication authentication, boolean rememberMe) { public LoginVO login(Authentication authentication, boolean rememberMe) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
String accessToken = jwtUtil.generateToken(authentication, rememberMe); String accessToken = jwtUtil.generateToken(authentication, rememberMe);
Long expiresIn = jwtUtil.getExpiresIn(rememberMe); Long expiresIn = jwtUtil.getExpiresIn(rememberMe);
log.info("用户登录成功: {}", userPrincipal.getUsername()); log.info("用户登录成功: {}", userPrincipal.getUsername());
return LoginVO.builder() return LoginVO.builder()
.userId(userPrincipal.getId()) .userId(userPrincipal.getId())
.username(userPrincipal.getUsername()) .username(userPrincipal.getUsername())
.accessToken(accessToken) .accessToken(accessToken)
.expiresIn(expiresIn) .expiresIn(expiresIn)
.build(); .build();
} }
/** /**
@ -77,6 +72,10 @@ public class AuthServiceImpl implements AuthService {
jwtUtil.invalidateToken(token); jwtUtil.invalidateToken(token);
} catch (SecurityException e) { } catch (SecurityException e) {
log.info("invalidateToken: {}", e.getMessage()); log.info("invalidateToken: {}", e.getMessage());
throw e;
} catch (Exception e) {
log.error("Unexpected error during token invalidation: {}", e.getMessage(), e);
throw new SecurityException("Failed to invalidate token", e);
} }
} }
@ -88,20 +87,14 @@ public class AuthServiceImpl implements AuthService {
*/ */
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public ApiResponse<RegisterVO> register(RegisterRequest request) throws Exception { public RegisterVO register(RegisterRequest request) throws Exception {
// if (userMapper.exists(new LambdaQueryWrapper<User>().eq(User::getUsername, request.getUsername()))) {
if (!StrUtil.equals(request.getConfirmPassword(), request.getPassword())) { // return ApiResponse.of(400, "用户名已存在", null);
log.info("password {}, confirmPassword {}", request.getConfirmPassword(), request.getPassword()); // }
return ApiResponse.of(400, "密码不一致", null); //
} // if (userMapper.exists(new LambdaQueryWrapper<User>().eq(User::getEmail, request.getEmail()))) {
// return ApiResponse.of(400, "邮箱已注册", null);
if (userMapper.exists(new LambdaQueryWrapper<User>().eq(User::getUsername, request.getUsername()))) { // }
return ApiResponse.of(400, "用户名已存在", null);
}
if (userMapper.exists(new LambdaQueryWrapper<User>().eq(User::getEmail, request.getEmail()))) {
return ApiResponse.of(400, "邮箱已注册", null);
}
User user = new User(); User user = new User();
user.setUsername(request.getUsername()); user.setUsername(request.getUsername());
@ -119,7 +112,7 @@ public class AuthServiceImpl implements AuthService {
log.info("insert user after: {}", user); log.info("insert user after: {}", user);
RegisterVO registerVO = new RegisterVO(); RegisterVO registerVO = new RegisterVO();
registerVO.setUserId(user.getId()); registerVO.setUserId(user.getId());
return ApiResponse.ofStatus(Status.REGISTER_SUCCESS, registerVO); return registerVO;
} catch (Exception e) { } catch (Exception e) {
log.error("数据插入用户数据失败: {}", e.getMessage()); log.error("数据插入用户数据失败: {}", e.getMessage());
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@ -2,26 +2,30 @@
server.port=8080 server.port=8080
#server.servlet.context-path=/demo #server.servlet.context-path=/demo
# mysql配置 # mysql配置
spring.datasource.url=jdbc:mysql://122.152.201.90:9912/workflow?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 # MySQL 配置
spring.datasource.url=jdbc:mysql://122.152.201.90:9912/workflow?\
useUnicode=true&characterEncoding=UTF-8&useSSL=false&\
rewriteBatchedStatements=true&cachePrepStmts=true&\
prepStmtCacheSize=250&prepStmtCacheSqlLimit=2048&\
useServerPrepStmts=true&connectionTimeout=3000&\
socketTimeout=60000&serverTimezone=GMT%2B8
spring.datasource.username=root spring.datasource.username=root
spring.datasource.password=ENC(lLJgIEE5YJuSKnOpFBC4NFL+iqZZK97573fvgC7hZ8u3S6o/TlK15WfjnKTPOrQO) spring.datasource.password=ENC(lLJgIEE5YJuSKnOpFBC4NFL+iqZZK97573fvgC7hZ8u3S6o/TlK15WfjnKTPOrQO)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.sql.init.mode=always
spring.sql.init.continue-on-error=true
spring.sql.init.schema-locations=classpath:db/schema.sql
spring.sql.init.data-locations=classpath:db/data.sql
# 连接池配置 # 连接池配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.initial-size=4 spring.datasource.druid.initial-size=10
spring.datasource.druid.min-idle=6 spring.datasource.druid.min-idle=10
spring.datasource.druid.max-active=25 spring.datasource.druid.max-active=50
spring.datasource.druid.max-wait=300000 spring.datasource.druid.max-wait=5000
spring.datasource.druid.test-while-idle=true spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false spring.datasource.druid.test-on-return=false
spring.datasource.druid.validation-query=SELECT 1 spring.datasource.druid.validation-query=SELECT 1
spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000 spring.datasource.druid.min-evictable-idle-time-millis=300000
# 关闭自动初始化(按需开启)
spring.sql.init.mode=never
# Redis配置 # Redis配置
spring.data.redis.host=122.152.201.90 spring.data.redis.host=122.152.201.90
spring.data.redis.port=6379 spring.data.redis.port=6379