初始化

This commit is contained in:
2026-02-02 21:24:59 +08:00
commit 018abc6675
28 changed files with 1052 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
package com.blog;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@MapperScan("com.blog.mapper")
@EnableAsync
@EnableScheduling
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

View File

@@ -0,0 +1,45 @@
package com.blog.aspect;
import com.blog.common.Captcha;
import com.blog.common.R;
import com.blog.dto.UserRegisterDto;
import com.blog.holder.GlobalContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Aspect
@Component
public class UserAspect {
@Around("execution(* com.blog.controller.UserController.register(..))")
public Object registerBefore(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if(arg instanceof UserRegisterDto){
UserRegisterDto userRegisterDto = (UserRegisterDto)arg;
Captcha captcha = GlobalContextHolder.getGlobalContext().getCaptchas().get(userRegisterDto.getKey());
if(captcha != null){
if(captcha.getTtl()< Instant.now().getEpochSecond()){
return R.err("验证码已过期");
}
if(!captcha.getText().equals(userRegisterDto.getVerificationCode())){
return R.err("无效的验证码");
}
try {
GlobalContextHolder.getGlobalContext().getCaptchas().remove(userRegisterDto.getKey());
Object result = joinPoint.proceed();
return result;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
return R.err("无效的验证码");
}
}
return R.err("无效的验证码");
}
}

View File

@@ -0,0 +1,15 @@
package com.blog.common;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Builder
@Data
public class Captcha {
private String text;
private long ttl;
}

View File

@@ -0,0 +1,11 @@
package com.blog.common;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class CaptchaLimit {
private long ttl;
}

View File

@@ -0,0 +1,33 @@
package com.blog.common;
import lombok.Data;
@Data
public class R<T> {
private int code;
private String msg;
private T data;
R(int code, String msg){
this.code = code;
this.msg = msg;
}
R(int code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> R<T> ok(String msg) {
return new R<>(0,msg);
}
public static <T> R<T> ok(String msg, T data) {
return new R<>(0,msg,data);
}
public static <T> R<T> err(String msg) {
return new R<>(-1,msg);
}
}

View File

@@ -0,0 +1,72 @@
package com.blog.config;
import com.blog.common.R;
import com.blog.filter.TokenBucketFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.context.SecurityContextHolderFilter;
@Configuration
@EnableWebSecurity
public class AuthorizationServerConfig {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TokenBucketFilter tokenBucketFilter;
@Bean
public PasswordEncoder passwordEncoder() {
// 默认使用 BCrypt它自带随机盐机制
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(tokenBucketFilter, SecurityContextHolderFilter.class)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/login",
"/user/captcha/**",
"/user/exist/**",
"/user/register").permitAll()
.anyRequest().authenticated()
)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.requestCache(RequestCacheConfigurer::disable)
.exceptionHandling(exceptions -> exceptions
// 1. 处理未认证 (401)
.authenticationEntryPoint((request, response, authException) -> {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
String json = objectMapper.writeValueAsString(R.err("无效的 token"));
response.getWriter().write(json);
})
// 2. 处理权限不足 (403)
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(403);
String json = objectMapper.writeValueAsString(R.err("权限不足"));
response.getWriter().write(json);
})
);
return http.build();
}
}

View File

@@ -0,0 +1,24 @@
package com.blog.config;
import com.blog.interceptor.IpLimitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private IpLimitInterceptor ipLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(ipLimitInterceptor)
// 1. 添加拦截路径(需要限流的接口)
.addPathPatterns("/user/captcha/**")
// 2. 排除不拦截的路径(如静态资源)
.excludePathPatterns("/static/**", "/assets/**");
}
}

View File

@@ -0,0 +1,27 @@
package com.blog.context;
import com.blog.common.Captcha;
import com.blog.common.CaptchaLimit;
import com.google.common.util.concurrent.RateLimiter;
import lombok.Data;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
@Data
public class GlobalContext {
// 全局限流:每秒允许 10 个请求
private final RateLimiter globalLimiter = RateLimiter.create(100.0);
private Map<String, Captcha> captchas;
private Map<String, CaptchaLimit> ipLimitPool;
public GlobalContext() {
captchas = new ConcurrentHashMap<>();
ipLimitPool = new ConcurrentHashMap<>();
}
}

View File

@@ -0,0 +1,35 @@
package com.blog.controller;
import com.blog.common.R;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<String> handleValidationException(MethodArgumentNotValidException e) {
e.printStackTrace();
// 1. 从异常对象中拿到所有的错误字段信息
BindingResult bindingResult = e.getBindingResult();
// 2. 提取第一条错误信息(或者拼接所有错误)
String defaultMessage = bindingResult.getFieldError().getDefaultMessage();
// 3. 返回你自定义的错误格式
return R.err(defaultMessage != null ? defaultMessage : "参数校验失败");
}
@ExceptionHandler(Exception.class)
public R<String> handleException(Exception e) {
e.printStackTrace();
return R.err("系统异常,请联系管理员");
}
@ExceptionHandler(NoResourceFoundException.class)
public R<String> handleException(NoResourceFoundException e) {
e.printStackTrace();
return R.err("资源不存在");
}
}

View File

@@ -0,0 +1,65 @@
package com.blog.controller;
import com.blog.common.Captcha;
import com.blog.common.R;
import com.blog.dto.UserLoginDto;
import com.blog.dto.UserRegisterDto;
import com.blog.holder.GlobalContextHolder;
import com.blog.service.UserService;
import com.wf.captcha.GifCaptcha;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public R register(@RequestBody @Valid UserRegisterDto userDto) {
userService.register(userDto);
return login(userDto);
}
@GetMapping("/exist/{username}")
public R exist(@PathVariable("username") String username) {
boolean exist = userService.exist(username);
if (exist) {
return R.ok("用户已存在");
}
return R.err("用户不存在");
}
@GetMapping("/test")
public R test() {
return R.ok("test");
}
@PostMapping("/login")
public R login(@Valid @RequestBody UserLoginDto user) {
Object res = userService.login(user);
if (res instanceof String) {
return R.err(res.toString());
}
return R.ok("登录成功", res);
}
@GetMapping("/captcha/{captchaId}")
public R captcha(@PathVariable("captchaId") String captchaId) throws Exception {
if (captchaId.isBlank() || captchaId.length() > 36) {
return R.err("无效的 captchaId");
}
// 生成 GIF 类型的验证码 (三个参数:宽、高、位数)
GifCaptcha specCaptcha = new GifCaptcha(130, 48, 4);
// 获取验证码文本
String text = specCaptcha.text().toLowerCase();
GlobalContextHolder.getGlobalContext().getCaptchas().put(captchaId, Captcha.builder().text(text).ttl(Instant.now().getEpochSecond() + 60).build());
String captchaBase64 = specCaptcha.toBase64();
return R.ok("获取成功", captchaBase64);
}
}

View File

@@ -0,0 +1,18 @@
package com.blog.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UserLoginDto {
@NotBlank(message = "用户名不能为空")
@Size(min = 4, max = 20, message = "用户名长度必须在4-20位之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 32, message = "密码长度必须在 6 到 32 位之间")
private String password;
}

View File

@@ -0,0 +1,22 @@
package com.blog.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
@Data
public class UserRegisterDto extends UserLoginDto {
@NotBlank(message = "邮箱不能为空")
@Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "邮箱格式不正确")
private String email;
@NotBlank(message = "邀请码不能为空")
private String inviteCode;
@NotBlank(message = "验证码不能为空")
private String verificationCode;
@NotBlank(message = "key 不能为空")
private String key;
}

View File

@@ -0,0 +1,42 @@
package com.blog.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.blog.dto.UserRegisterDto;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Builder
@Data
@TableName("sys_user")
public class UserEntity {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String username;
private String password;
private Integer enabled;
private Integer gender;
private String inviteCodeId;
private String nickname;
private String email;
private String phone;
private String avatar;
private LocalDateTime releaseDate;
private LocalDateTime createTime;
private LocalDateTime updateTime;
@TableLogic // 开启 MP 的逻辑删除支持
private Integer isDeleted;
public static UserEntity castFromRegisterDto(UserRegisterDto userDto, String password) {
return UserEntity.builder()
.username(userDto.getUsername())
.password(password)
.email(userDto.getEmail())
.inviteCodeId(userDto.getInviteCode())
.build();
}
}

View File

@@ -0,0 +1,45 @@
package com.blog.filter;
import com.blog.common.R;
import com.blog.holder.GlobalContextHolder;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class TokenBucketFilter extends OncePerRequestFilter {
@Autowired
private ObjectMapper objectMapper;
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
// 1. 尝试获取令牌 (tryAcquire 是非阻塞的)
// 使用 if 判断,符合你的高性能流控思维
if (GlobalContextHolder.getGlobalContext().getGlobalLimiter().tryAcquire()) {
// 2. 拿到令牌,放行
filterChain.doFilter(request, response);
} else {
// 3. 没拿到令牌,直接拒绝
handleRateLimitError(response);
}
}
private void handleRateLimitError(HttpServletResponse response) throws IOException {
response.setStatus(429);
response.setContentType("application/json;charset=UTF-8");
// 使用内置 JSON 或直接写字符串
response.getWriter().write(objectMapper.writeValueAsString(R.err("系统繁忙,请稍后再试")));
}
}

View File

@@ -0,0 +1,51 @@
package com.blog.helper;
import com.blog.entity.UserEntity;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserHelper {
public Map<String, String> obtainJWT(UserEntity userEntity) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userEntity.getId());
claims.put("username", userEntity.getUsername());
claims.put("nickname", userEntity.getNickname());
String accessToken = createToken(claims, 2*60);
// 3. 签发 Refresh Token (有效期长,如 7 天)
// 提示Refresh Token 通常可以只存一个 userId减少体积
String refreshToken = createToken(Map.of("userId", userEntity.getId()), 1 * 24 * 60);
Map<String, String> tokens = Map.of(
"accessToken", accessToken,
"refreshToken", refreshToken,
"tokenType", "Bearer"
);
return tokens;
}
// 密钥至少 32 个字符
private static final String SECRET = "your-super-secret-key-at-least-32-chars-long";
private static final Key KEY = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
public static String createToken(Map<String, Object> claims, int minutes) {
long now = System.currentTimeMillis();
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date(now))
.setExpiration(new Date(now + (long) minutes * 60 * 1000))
.signWith(KEY, SignatureAlgorithm.HS256)
.compact();
}
}

View File

@@ -0,0 +1,14 @@
package com.blog.holder;
import com.blog.context.GlobalContext;
public class GlobalContextHolder {
private static GlobalContext globalContext;
public static GlobalContext getGlobalContext() {
return globalContext;
}
public static void setGlobalContext(GlobalContext globalContext) {
GlobalContextHolder.globalContext = globalContext;
}
}

View File

@@ -0,0 +1,16 @@
package com.blog.initialization;
import com.blog.context.GlobalContext;
import com.blog.holder.GlobalContextHolder;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;
@Component
public class GlobalContextInitialization {
@PostConstruct
public void initGlobalContext(){
GlobalContext globalContext = new GlobalContext();
GlobalContextHolder.setGlobalContext(globalContext);
}
}

View File

@@ -0,0 +1,56 @@
package com.blog.interceptor;
import com.blog.common.CaptchaLimit;
import com.blog.common.R;
import com.blog.holder.GlobalContextHolder;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.IOException;
import java.time.Instant;
import java.util.Map;
@Component
public class IpLimitInterceptor implements HandlerInterceptor {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws IOException {
String ip = getClientIp(request);
long nowSed = Instant.now().getEpochSecond();
// 直接从全局上下文中操作 ipLimitPool
Map<String, CaptchaLimit> pool = GlobalContextHolder.getGlobalContext().getIpLimitPool();
CaptchaLimit limit = pool.get(ip);
// --- if 判断控制流 ---
if (limit != null && limit.getTtl()>nowSed) {
// 拒绝请求,不抛异常,直接返回 false
handleBlocked(response);
return false;
}
// --- 允许访问:更新上下文 ---
// 使用简单的对象封装或直接存 Long 均可
if (limit == null||limit.getTtl()<nowSed) {
pool.put(ip, CaptchaLimit.builder().ttl(nowSed +30).build());
}
return true;
}
private void handleBlocked(HttpServletResponse response) throws IOException {
response.setStatus(429);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(R.err("30秒内请勿重复操作")));
}
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Real-IP");
return (ip == null) ? request.getRemoteAddr() : ip;
}
}

View File

@@ -0,0 +1,9 @@
package com.blog.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.blog.entity.UserEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<UserEntity> {
}

View File

@@ -0,0 +1,14 @@
package com.blog.service;
import com.blog.dto.UserLoginDto;
import com.blog.dto.UserRegisterDto;
import jakarta.validation.Valid;
public interface UserService {
<T> T login(UserLoginDto user);
String register(UserRegisterDto userDto);
boolean exist(String username);
}

View File

@@ -0,0 +1,76 @@
package com.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.blog.dto.UserLoginDto;
import com.blog.dto.UserRegisterDto;
import com.blog.entity.UserEntity;
import com.blog.helper.UserHelper;
import com.blog.mapper.UserMapper;
import com.blog.service.UserService;
import com.blog.utils.ChartsGenerator;
import com.blog.vaildator.UserValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserHelper userHelper;
@Autowired
private UserValidator userValidator;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public <T> T login(UserLoginDto user) {
LambdaQueryWrapper<UserEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(UserEntity::getUsername, user.getUsername());
UserEntity userEntity = userMapper.selectOne(lambdaQueryWrapper);
String errMsg = userValidator.validateUser(user,userEntity);
if(errMsg!=null) return (T) errMsg;
return (T) userHelper.obtainJWT(userEntity);
}
@Override
public String register(UserRegisterDto userDto) {
LambdaQueryWrapper<UserEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(UserEntity::getUsername, userDto.getUsername());
UserEntity userEntity = userMapper.selectOne(lambdaQueryWrapper);
String errMsg = userValidator.validateRegister(userEntity);
if (errMsg != null) return errMsg;
UserEntity newUser = UserEntity.castFromRegisterDto(userDto,passwordEncoder.encode(userDto.getPassword()));
newUser.setNickname("游客"+ ChartsGenerator.generateCode());
transactionTemplate.execute(status -> {
try {
userMapper.insert(newUser);
return true;
} catch (Exception e) {
e.printStackTrace();
status.setRollbackOnly(); // 显式标记回滚
return false;
}
});
return null;
}
@Override
public boolean exist(String username) {
UserEntity userEntity = userMapper.selectOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername, username));
if (userEntity == null) return false;
return true;
}
}

View File

@@ -0,0 +1,37 @@
package com.blog.task;
import com.blog.common.Captcha;
import com.blog.common.CaptchaLimit;
import com.blog.holder.GlobalContextHolder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Map;
@Component
public class GlobalCleanupTask {
@Async
@Scheduled(fixedRate = 60000)
public void purgeCaptcha() {
Map<String, Captcha> captchas = GlobalContextHolder.getGlobalContext().getCaptchas();
captchas.forEach((k,v)->{
if(v.getTtl()< Instant.now().getEpochSecond()){
captchas.remove(k);
}
});
}
@Async
@Scheduled(fixedRate = 60000)
public void purgeIpLimit() {
Map<String, CaptchaLimit> ipLimitPool = GlobalContextHolder.getGlobalContext().getIpLimitPool();
ipLimitPool.forEach((k,v)->{
if(v.getTtl()< Instant.now().getEpochSecond()){
ipLimitPool.remove(k);
}
});
}
}

View File

@@ -0,0 +1,18 @@
package com.blog.utils;
import java.util.Random;
public class ChartsGenerator {
private static final String CHAR_POOL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
public static String generateCode() {
Random random = new Random();
StringBuilder sb = new StringBuilder(6);
for (int i = 0; i < 6; i++) {
int index = random.nextInt(CHAR_POOL.length());
sb.append(CHAR_POOL.charAt(index));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,36 @@
package com.blog.vaildator;
import com.blog.dto.UserLoginDto;
import com.blog.entity.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class UserValidator {
@Autowired
private PasswordEncoder passwordEncoder;
public String validateUser(UserLoginDto user, UserEntity userEntity) {
if (userEntity == null) {
return "用户名不存在";
}
boolean isMatch = passwordEncoder.matches(user.getPassword(), userEntity.getPassword());
if (!isMatch) {
return "密码错误";
}
if (userEntity.getEnabled() == 0) return "账号已被禁用";
if (userEntity.getReleaseDate() != null && userEntity.getReleaseDate().isAfter(LocalDateTime.now())) {
return ("账号封禁中,解禁日期:" + userEntity.getReleaseDate());
}
return null;
}
public String validateRegister(UserEntity userEntity) {
if(userEntity!=null){
return "用户名已存在";
}
return null;
}
}