对象存储-初始化
This commit is contained in:
35
src/main/java/com/blog/aspect/CommonAspect.java
Normal file
35
src/main/java/com/blog/aspect/CommonAspect.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.blog.aspect;
|
||||
|
||||
import com.blog.common.User;
|
||||
import com.blog.common.constant.UserConstant;
|
||||
import com.blog.holder.RequestImplicitContextHolder;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
public class CommonAspect {
|
||||
|
||||
@Order(100)
|
||||
@Around("execution(* com.blog.controller.*.*(..))"+
|
||||
"&& !within(com.blog.controller.GlobalExceptionHandler)")
|
||||
public Object buildRequestImplicitContext(ProceedingJoinPoint joinPoint){
|
||||
try {
|
||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
if(principal!=null && !principal.equals(UserConstant.ANONYMOUS_USER) && principal instanceof User){
|
||||
User currentUser = (User) principal;
|
||||
RequestImplicitContextHolder.setUserId(currentUser.getUserId());
|
||||
RequestImplicitContextHolder.setUsername(currentUser.getUsername());
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
RequestImplicitContextHolder.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@ package com.blog.aspect;
|
||||
|
||||
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 org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -15,7 +17,9 @@ import java.time.Instant;
|
||||
@Component
|
||||
public class UserAspect {
|
||||
|
||||
@Around("execution(* com.blog.controller.UserController.register(..))")
|
||||
@Order(101)
|
||||
@Around("execution(* com.blog.controller.UserController.register(..))"+
|
||||
"|| execution(* com.blog.controller.UserController.login(..))")
|
||||
public Object registerBefore(ProceedingJoinPoint joinPoint) {
|
||||
Object[] args = joinPoint.getArgs();
|
||||
for (Object arg : args) {
|
||||
@@ -26,7 +30,27 @@ public class UserAspect {
|
||||
if(captcha.getTtl()< Instant.now().getEpochSecond()){
|
||||
return R.err("验证码已过期");
|
||||
}
|
||||
if(!captcha.getText().equals(userRegisterDto.getVerificationCode())){
|
||||
if(!captcha.getText().equalsIgnoreCase(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("无效的验证码");
|
||||
}
|
||||
if(arg instanceof UserLoginDto){
|
||||
UserLoginDto userRegisterDto = (UserLoginDto)arg;
|
||||
Captcha captcha = GlobalContextHolder.getGlobalContext().getCaptchas().get(userRegisterDto.getKey());
|
||||
if(captcha != null){
|
||||
if(captcha.getTtl()< Instant.now().getEpochSecond()){
|
||||
return R.err("验证码已过期");
|
||||
}
|
||||
if(!captcha.getText().equalsIgnoreCase(userRegisterDto.getVerificationCode())){
|
||||
return R.err("无效的验证码");
|
||||
}
|
||||
try {
|
||||
|
||||
12
src/main/java/com/blog/common/User.java
Normal file
12
src/main/java/com/blog/common/User.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.blog.common;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class User {
|
||||
|
||||
private Long userId;
|
||||
private String username;
|
||||
}
|
||||
12
src/main/java/com/blog/common/UserSession.java
Normal file
12
src/main/java/com/blog/common/UserSession.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.blog.common;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
public class UserSession {
|
||||
|
||||
private Long userId;
|
||||
private Long ttl;
|
||||
}
|
||||
27
src/main/java/com/blog/common/constant/UserConstant.java
Normal file
27
src/main/java/com/blog/common/constant/UserConstant.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.blog.common.constant;
|
||||
|
||||
public class UserConstant {
|
||||
|
||||
// 刷新头
|
||||
public static final String REFRESH_TOKEN = "refresh_token";
|
||||
|
||||
// 匿名用户
|
||||
public static final String ANONYMOUS_USER = "anonymousUser";
|
||||
|
||||
// 权限头
|
||||
public static final String AUTHORIZATION = "Authorization";
|
||||
|
||||
// 权限类型
|
||||
public static final String BEARER = "Bearer";
|
||||
|
||||
// 用户id
|
||||
public static final String USER_ID = "userId";
|
||||
|
||||
public static final String NICKNAME = "nickname";
|
||||
|
||||
public static final String ACCESS_TOKENS = "accessToken";
|
||||
|
||||
public static final String REFRESH_TOKENS = "refreshToken";
|
||||
public static final String TOKEN_TYPES = "tokenType";
|
||||
public static final String USERNAME = "username";
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.blog.common.constant.message.error;
|
||||
|
||||
public class UserErrorMessage {
|
||||
|
||||
public static final String INVALID_REFRESH_TOKEN = "无效的 refresh_token";
|
||||
public static final String INVALID_USER = "无效的用户";
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.blog.common.constant.message.success;
|
||||
|
||||
public class SpaceSuccessMessage {
|
||||
|
||||
public static final String MY_SPACE = "获取空间成功";
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.blog.common.constant.message.success;
|
||||
|
||||
public class UserSuccessMessage {
|
||||
|
||||
public static final String REFRESH_TOKEN_SUCCESS = "刷新令牌成功";
|
||||
public static final String GET_ARTICLES_SUCCESS = "获取文章成功";
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.blog.config;
|
||||
|
||||
import com.blog.common.R;
|
||||
import com.blog.filter.JwtAuthenticationFilter;
|
||||
import com.blog.filter.TokenBucketFilter;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -14,6 +15,7 @@ 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.access.intercept.AuthorizationFilter;
|
||||
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||
|
||||
@Configuration
|
||||
@@ -26,6 +28,9 @@ public class AuthorizationServerConfig {
|
||||
@Autowired
|
||||
private TokenBucketFilter tokenBucketFilter;
|
||||
|
||||
@Autowired
|
||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
// 默认使用 BCrypt,它自带随机盐机制
|
||||
@@ -36,11 +41,16 @@ public class AuthorizationServerConfig {
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.addFilterBefore(tokenBucketFilter, SecurityContextHolderFilter.class)
|
||||
.addFilterBefore(jwtAuthenticationFilter, AuthorizationFilter.class)
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/user/login",
|
||||
"/user/captcha/**",
|
||||
"/user/exist/**",
|
||||
"/user/register").permitAll()
|
||||
"/user/register",
|
||||
"/user/refreshToken",
|
||||
"/swagger-ui/**",
|
||||
"/v3/api-docs/**",
|
||||
"/doc").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(AbstractHttpConfigurer::disable)
|
||||
|
||||
34
src/main/java/com/blog/config/MinioConfig.java
Normal file
34
src/main/java/com/blog/config/MinioConfig.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.blog.config;
|
||||
|
||||
import io.minio.MinioClient;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@Data
|
||||
public class MinioConfig {
|
||||
@Value("${minio.endpoint}")
|
||||
private String endpoint;
|
||||
@Value("${minio.accessKey}")
|
||||
private String accessKey;
|
||||
@Value("${minio.secretKey}")
|
||||
private String secretKey;
|
||||
@Value("${minio.bucketName}")
|
||||
private String bucketName;
|
||||
|
||||
@Value("${minio.defaultBucketName}")
|
||||
private String defaultBucketName;
|
||||
|
||||
@Value("${minio.defaultPublicBucketName}")
|
||||
private String defaultPublicBucketName;
|
||||
|
||||
@Bean
|
||||
public MinioClient minioClient() {
|
||||
return MinioClient.builder()
|
||||
.endpoint(endpoint)
|
||||
.credentials(accessKey, secretKey)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
19
src/main/java/com/blog/config/MybatisPlusConfig.java
Normal file
19
src/main/java/com/blog/config/MybatisPlusConfig.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.blog.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
|
||||
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
25
src/main/java/com/blog/config/PipelineConfig.java
Normal file
25
src/main/java/com/blog/config/PipelineConfig.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.blog.config;
|
||||
|
||||
import com.blog.pipeline.RegisterPipeline;
|
||||
import com.blog.processor.user.PathbuildProcessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Configuration
|
||||
public class PipelineConfig {
|
||||
|
||||
@Autowired
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
@Bean
|
||||
public RegisterPipeline registerPipeline(PathbuildProcessor pathbuildProcessor) {
|
||||
return RegisterPipeline.builder()
|
||||
.transactionTemplate(transactionTemplate)
|
||||
.processList(Arrays.asList(pathbuildProcessor))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
21
src/main/java/com/blog/config/SwaggerConfig.java
Normal file
21
src/main/java/com/blog/config/SwaggerConfig.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.blog.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI blogOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("Blog 系统 API 文档")
|
||||
.description("基于 Spring Boot 3 的博客管理系统接口")
|
||||
.version("v1.0.0")
|
||||
.license(new License().name("Apache 2.0")));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.blog.context;
|
||||
|
||||
import com.blog.common.Captcha;
|
||||
import com.blog.common.CaptchaLimit;
|
||||
import com.blog.common.UserSession;
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import lombok.Data;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -16,12 +17,18 @@ public class GlobalContext {
|
||||
// 全局限流:每秒允许 10 个请求
|
||||
private final RateLimiter globalLimiter = RateLimiter.create(100.0);
|
||||
|
||||
private Map<String, Captcha> captchas;
|
||||
// 验证码池
|
||||
private final Map<String, Captcha> captchas;
|
||||
|
||||
private Map<String, CaptchaLimit> ipLimitPool;
|
||||
// ip池
|
||||
private final Map<String, CaptchaLimit> ipLimitPool;
|
||||
|
||||
// userId映射map
|
||||
private final Map<String, UserSession> userSessionMap;
|
||||
|
||||
public GlobalContext() {
|
||||
captchas = new ConcurrentHashMap<>();
|
||||
ipLimitPool = new ConcurrentHashMap<>();
|
||||
userSessionMap = new ConcurrentHashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
18
src/main/java/com/blog/context/RequestContext.java
Normal file
18
src/main/java/com/blog/context/RequestContext.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.blog.context;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class RequestContext {
|
||||
|
||||
Map<String, Object> params;
|
||||
|
||||
public Object getReqValue(String key) {
|
||||
return params.get(key);
|
||||
}
|
||||
|
||||
}
|
||||
27
src/main/java/com/blog/context/RequestImplicitContext.java
Normal file
27
src/main/java/com/blog/context/RequestImplicitContext.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.blog.context;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class RequestImplicitContext {
|
||||
|
||||
private String traceId;
|
||||
|
||||
private Long userId;
|
||||
private String username;
|
||||
|
||||
private Map<String,Object> resContextMap;
|
||||
|
||||
private Map<String,Object> reqContextMap;
|
||||
|
||||
private List<String> errorMessages;
|
||||
|
||||
private List<String> warningMessages;
|
||||
|
||||
private Object data;
|
||||
}
|
||||
16
src/main/java/com/blog/controller/ArticleController.java
Normal file
16
src/main/java/com/blog/controller/ArticleController.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.blog.controller;
|
||||
|
||||
import com.blog.service.ArticleService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/article")
|
||||
public class ArticleController {
|
||||
|
||||
@Autowired
|
||||
private ArticleService articleService;
|
||||
|
||||
|
||||
}
|
||||
33
src/main/java/com/blog/controller/SpaceController.java
Normal file
33
src/main/java/com/blog/controller/SpaceController.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package com.blog.controller;
|
||||
|
||||
import com.blog.common.R;
|
||||
import com.blog.common.constant.message.success.SpaceSuccessMessage;
|
||||
import com.blog.holder.RequestImplicitContextHolder;
|
||||
import com.blog.service.FileService;
|
||||
import com.blog.service.PathService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/space")
|
||||
public class SpaceController {
|
||||
|
||||
@Autowired
|
||||
private FileService fileService;
|
||||
|
||||
@Autowired
|
||||
private PathService pathService;
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@GetMapping("")
|
||||
public R<Object> mySpace() {
|
||||
|
||||
if (pathService.listPath(RequestImplicitContextHolder.getUserId())) {
|
||||
return R.ok(SpaceSuccessMessage.MY_SPACE,RequestImplicitContextHolder.getData());
|
||||
}
|
||||
return R.err(RequestImplicitContextHolder.popErrorMessage());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,18 +1,29 @@
|
||||
package com.blog.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.blog.common.Captcha;
|
||||
import com.blog.common.R;
|
||||
import com.blog.common.constant.UserConstant;
|
||||
import com.blog.common.constant.message.success.UserSuccessMessage;
|
||||
import com.blog.context.RequestContext;
|
||||
import com.blog.dto.UserLoginDto;
|
||||
import com.blog.dto.UserRegisterDto;
|
||||
import com.blog.holder.GlobalContextHolder;
|
||||
import com.blog.holder.RequestImplicitContextHolder;
|
||||
import com.blog.service.ArticleService;
|
||||
import com.blog.service.UserService;
|
||||
import com.wf.captcha.GifCaptcha;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "用户管理", description = "用户相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
public class UserController {
|
||||
@@ -20,12 +31,37 @@ public class UserController {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private ArticleService articleService;
|
||||
|
||||
@Operation(summary = "注册", description = "注册新用户")
|
||||
@PostMapping("/register")
|
||||
public R register(@RequestBody @Valid UserRegisterDto userDto) {
|
||||
userService.register(userDto);
|
||||
return login(userDto);
|
||||
public<T> T register(@RequestBody @Valid UserRegisterDto userDto) {
|
||||
String errMsg = userService.register(userDto);
|
||||
if(errMsg != null){
|
||||
return (T) R.err(errMsg);
|
||||
}
|
||||
if(RequestImplicitContextHolder.errorCount()>0){
|
||||
return (T) R.err(RequestImplicitContextHolder.popErrorMessage());
|
||||
}
|
||||
return (T) login(userDto);
|
||||
}
|
||||
@Operation(summary = "我的文章", description = "获取当前用户的文章")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@GetMapping("/article")
|
||||
public R myarticles(@RequestParam("current") int currentPage, @RequestParam("size") int pageSize) {
|
||||
if(pageSize==0||pageSize>20){
|
||||
pageSize=20;
|
||||
}
|
||||
Long userId = RequestImplicitContextHolder.getUserId();
|
||||
if (articleService.getarticles(userId,Page.of(currentPage,pageSize))){
|
||||
return R.ok(UserSuccessMessage.GET_ARTICLES_SUCCESS,RequestImplicitContextHolder.getData());
|
||||
}
|
||||
return R.err(RequestImplicitContextHolder.popErrorMessage());
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "用户名是否存在", description = "判断用户是否存在")
|
||||
@GetMapping("/exist/{username}")
|
||||
public R exist(@PathVariable("username") String username) {
|
||||
boolean exist = userService.exist(username);
|
||||
@@ -35,11 +71,24 @@ public class UserController {
|
||||
return R.err("用户不存在");
|
||||
}
|
||||
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@GetMapping("/test")
|
||||
public R test() {
|
||||
return R.ok("test");
|
||||
}
|
||||
|
||||
@Operation(summary = "刷新令牌", description = "刷新令牌")
|
||||
@PostMapping("/refreshToken")
|
||||
public R refreshToken(@RequestBody String refreshToken) {
|
||||
RequestContext requestContext = RequestContext.builder().params(Map.of(UserConstant.REFRESH_TOKEN, refreshToken)).build();
|
||||
if (userService.refreshToken(requestContext)) {
|
||||
return R.ok(UserSuccessMessage.REFRESH_TOKEN_SUCCESS,RequestImplicitContextHolder.getData());
|
||||
}
|
||||
return R.err(RequestImplicitContextHolder.popErrorMessage());
|
||||
}
|
||||
|
||||
@Operation(summary = "登录", description = "登录")
|
||||
@PostMapping("/login")
|
||||
public R login(@Valid @RequestBody UserLoginDto user) {
|
||||
Object res = userService.login(user);
|
||||
@@ -49,6 +98,7 @@ public class UserController {
|
||||
return R.ok("登录成功", res);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取验证码", description = "获取验证码")
|
||||
@GetMapping("/captcha/{captchaId}")
|
||||
public R captcha(@PathVariable("captchaId") String captchaId) throws Exception {
|
||||
if (captchaId.isBlank() || captchaId.length() > 36) {
|
||||
|
||||
@@ -13,6 +13,9 @@ public class UserLoginDto {
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Size(min = 6, max = 32, message = "密码长度必须在 6 到 32 位之间")
|
||||
private String password;
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String verificationCode;
|
||||
|
||||
|
||||
@NotBlank(message = "key 不能为空")
|
||||
private String key;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,4 @@ public class UserRegisterDto extends UserLoginDto {
|
||||
@NotBlank(message = "邀请码不能为空")
|
||||
private String inviteCode;
|
||||
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String verificationCode;
|
||||
|
||||
@NotBlank(message = "key 不能为空")
|
||||
private String key;
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
64
src/main/java/com/blog/filter/JwtAuthenticationFilter.java
Normal file
64
src/main/java/com/blog/filter/JwtAuthenticationFilter.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.blog.filter;
|
||||
|
||||
import com.blog.common.User;
|
||||
import com.blog.common.UserSession;
|
||||
import com.blog.common.constant.UserConstant;
|
||||
import com.blog.helper.UserHelper;
|
||||
import com.blog.holder.GlobalContextHolder;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private UserHelper userHelper; // 假设你有一个处理 JWT 的工具类
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
// 1. 从 Header 中获取 Authorization
|
||||
String authHeader = request.getHeader(UserConstant.AUTHORIZATION);
|
||||
|
||||
// 2. 校验格式 (匹配你前端传来的 tokenType + accessToken)
|
||||
if (authHeader == null || !authHeader.startsWith(UserConstant.BEARER+" ")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 截取真正的 Token 字符串
|
||||
String jwt = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
// 4. 解析 JWT (此处可结合你的令牌桶或缓存逻辑)
|
||||
if (userHelper.validateToken(jwt)) {
|
||||
Claims claims = userHelper.parseToken(jwt);
|
||||
UserSession session = GlobalContextHolder.getGlobalContext().getUserSessionMap().get(claims.get(UserConstant.USER_ID).toString());
|
||||
// 5. 构建认证对象并存入上下文
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(User.builder().userId(session.getUserId()).username(claims.get(UserConstant.USERNAME).toString()).build(), null, new ArrayList<>());
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Token 过期或非法,清理上下文
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
// 6. 继续执行后面的过滤器
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -41,5 +41,6 @@ public class TokenBucketFilter extends OncePerRequestFilter {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
// 使用内置 JSON 或直接写字符串
|
||||
response.getWriter().write(objectMapper.writeValueAsString(R.err("系统繁忙,请稍后再试")));
|
||||
response.getWriter().flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
package com.blog.helper;
|
||||
|
||||
import com.blog.common.UserSession;
|
||||
import com.blog.common.constant.UserConstant;
|
||||
import com.blog.entity.UserEntity;
|
||||
import com.blog.holder.GlobalContextHolder;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@Component
|
||||
public class UserHelper {
|
||||
public Map<String, String> obtainJWT(UserEntity userEntity) {
|
||||
String userId = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
GlobalContextHolder.getGlobalContext().getUserSessionMap().put(userId, UserSession.builder().userId(userEntity.getId()).ttl(Instant.now().getEpochSecond()+(2*60*60)).build());
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userEntity.getId());
|
||||
claims.put("username", userEntity.getUsername());
|
||||
claims.put("nickname", userEntity.getNickname());
|
||||
claims.put(UserConstant.USER_ID, userId);
|
||||
claims.put(UserConstant.USERNAME, userEntity.getUsername());
|
||||
|
||||
String accessToken = createToken(claims, 2*60);
|
||||
|
||||
// 3. 签发 Refresh Token (有效期长,如 7 天)
|
||||
// 提示:Refresh Token 通常可以只存一个 userId,减少体积
|
||||
String refreshToken = createToken(Map.of("userId", userEntity.getId()), 1 * 24 * 60);
|
||||
String refreshToken = createToken(Map.of(UserConstant.USERNAME, userEntity.getUsername()), 6 * 60);
|
||||
|
||||
Map<String, String> tokens = Map.of(
|
||||
"accessToken", accessToken,
|
||||
"refreshToken", refreshToken,
|
||||
"tokenType", "Bearer"
|
||||
UserConstant.ACCESS_TOKENS, accessToken,
|
||||
UserConstant.REFRESH_TOKENS, refreshToken,
|
||||
UserConstant.TOKEN_TYPES, UserConstant.BEARER
|
||||
);
|
||||
|
||||
return tokens;
|
||||
@@ -48,4 +55,24 @@ public class UserHelper {
|
||||
.signWith(KEY, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public Claims parseToken(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(KEY)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Token 是否有效
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Claims claims = parseToken(token);
|
||||
return !claims.getExpiration().before(new Date());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.blog.holder;
|
||||
|
||||
import com.blog.context.RequestImplicitContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class RequestImplicitContextHolder {
|
||||
|
||||
private static final ThreadLocal<RequestImplicitContext> CONTEXT = ThreadLocal.withInitial(() -> RequestImplicitContext.builder()
|
||||
.resContextMap(new ConcurrentHashMap<>())
|
||||
.resContextMap(new ConcurrentHashMap<>())
|
||||
.errorMessages(new ArrayList<>())
|
||||
.warningMessages(new ArrayList<>())
|
||||
.build());
|
||||
|
||||
public static void setReq(String key, Object value) {
|
||||
CONTEXT.get().getReqContextMap().put(key, value);
|
||||
}
|
||||
|
||||
public static void pushErrorMessage(String errorMessage) {
|
||||
CONTEXT.get().getErrorMessages().add(errorMessage);
|
||||
}
|
||||
public static void pushWarningMessage(String warningMessage) {
|
||||
CONTEXT.get().getWarningMessages().add(warningMessage);
|
||||
}
|
||||
public static void setData(Object data) {
|
||||
CONTEXT.get().setData(data);
|
||||
}
|
||||
public static Object getData() {
|
||||
return CONTEXT.get().getData();
|
||||
}
|
||||
public static String popErrorMessage() {
|
||||
return CONTEXT.get().getErrorMessages().get(0);
|
||||
}
|
||||
public static int errorCount() {
|
||||
return CONTEXT.get().getErrorMessages().size();
|
||||
}
|
||||
public static int warningCount() {
|
||||
return CONTEXT.get().getWarningMessages().size();
|
||||
}
|
||||
|
||||
public static void setUsername(String username) {
|
||||
CONTEXT.get().setUsername(username);
|
||||
}
|
||||
public static String getUsername() {
|
||||
return CONTEXT.get().getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据
|
||||
*/
|
||||
public static Object getReq(String key) {
|
||||
return CONTEXT.get().getReqContextMap().get(key);
|
||||
}
|
||||
|
||||
public static void setRes(String key, Object value) {
|
||||
CONTEXT.get().getResContextMap().put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据
|
||||
*/
|
||||
public static Object getRes(String key) {
|
||||
return CONTEXT.get().getResContextMap().get(key);
|
||||
}
|
||||
|
||||
public static Long getUserId() {
|
||||
return CONTEXT.get().getUserId();
|
||||
}
|
||||
|
||||
public static void setUserId(Long userId) {
|
||||
CONTEXT.get().setUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 彻底清除,防止线程池内存泄漏(核心步骤)
|
||||
*/
|
||||
public static void clear() {
|
||||
CONTEXT.remove();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.blog.initialization;
|
||||
package com.blog.initializer;
|
||||
|
||||
import com.blog.context.GlobalContext;
|
||||
import com.blog.holder.GlobalContextHolder;
|
||||
@@ -6,7 +6,7 @@ import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class GlobalContextInitialization {
|
||||
public class GlobalContextInitializer {
|
||||
|
||||
@PostConstruct
|
||||
public void initGlobalContext(){
|
||||
78
src/main/java/com/blog/initializer/MinioInitializer.java
Normal file
78
src/main/java/com/blog/initializer/MinioInitializer.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package com.blog.initializer;
|
||||
|
||||
import com.blog.config.MinioConfig;
|
||||
import io.minio.BucketExistsArgs;
|
||||
import io.minio.MakeBucketArgs;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.SetBucketPolicyArgs;
|
||||
import io.minio.errors.*;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class MinioInitializer {
|
||||
|
||||
private final List<String> buckets;
|
||||
|
||||
@Autowired
|
||||
private MinioClient minioClient;
|
||||
|
||||
@Autowired
|
||||
private MinioConfig minioConfig;
|
||||
|
||||
public MinioInitializer() {
|
||||
buckets = new ArrayList<>();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
buckets.add(minioConfig.getBucketName());
|
||||
buckets.add(minioConfig.getDefaultBucketName());
|
||||
buckets.add(minioConfig.getDefaultPublicBucketName());
|
||||
try {
|
||||
for (String bucket : buckets) {
|
||||
boolean exists;
|
||||
exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
|
||||
if(!exists) {
|
||||
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
|
||||
}
|
||||
}
|
||||
setBucketPublicReadOnly(minioConfig.getDefaultPublicBucketName());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("MinIO 桶初始化失败", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setBucketPublicReadOnly(String bucketName) {
|
||||
// 构造 JSON 策略字符串
|
||||
// 注意:Resource 部分必须包含 "arn:aws:s3:::桶名" 和 "arn:aws:s3:::桶名/*"
|
||||
String policy = "{\n" +
|
||||
" \"Version\": \"2012-10-17\",\n" +
|
||||
" \"Statement\": [\n" +
|
||||
" {\n" +
|
||||
" \"Effect\": \"Allow\",\n" +
|
||||
" \"Principal\": {\"AWS\": [\"*\"]},\n" +
|
||||
" \"Action\": [\"s3:GetBucketLocation\", \"s3:GetObject\"],\n" +
|
||||
" \"Resource\": [\"arn:aws:s3:::" + bucketName + "\", \"arn:aws:s3:::" + bucketName + "/*\"]\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}";
|
||||
|
||||
try {
|
||||
minioClient.setBucketPolicy(
|
||||
SetBucketPolicyArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.config(policy)
|
||||
.build()
|
||||
);
|
||||
System.out.println("桶策略已更新:公开只读");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("配置桶策略失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public class IpLimitInterceptor implements HandlerInterceptor {
|
||||
// --- 允许访问:更新上下文 ---
|
||||
// 使用简单的对象封装或直接存 Long 均可
|
||||
if (limit == null||limit.getTtl()<nowSed) {
|
||||
pool.put(ip, CaptchaLimit.builder().ttl(nowSed +30).build());
|
||||
pool.put(ip, CaptchaLimit.builder().ttl(nowSed +5).build());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -46,7 +46,8 @@ public class IpLimitInterceptor implements HandlerInterceptor {
|
||||
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秒内请勿重复操作")));
|
||||
response.getWriter().write(objectMapper.writeValueAsString(R.err("5秒内请勿重复操作")));
|
||||
response.getWriter().flush();
|
||||
}
|
||||
|
||||
private String getClientIp(HttpServletRequest request) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
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> {
|
||||
}
|
||||
40
src/main/java/com/blog/pipeline/RegisterPipeline.java
Normal file
40
src/main/java/com/blog/pipeline/RegisterPipeline.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.blog.pipeline;
|
||||
|
||||
import com.blog.dto.UserRegisterDto;
|
||||
import com.blog.processor.Processor;
|
||||
import lombok.Builder;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@Builder
|
||||
public class RegisterPipeline {
|
||||
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
private List<Processor<UserRegisterDto>> processList;
|
||||
|
||||
public boolean start(UserRegisterDto userRegisterDto) {
|
||||
AtomicBoolean result = new AtomicBoolean(false);
|
||||
transactionTemplate.execute(status -> {
|
||||
try {
|
||||
for (Processor<UserRegisterDto> processor : processList) {
|
||||
boolean processed = processor.process(userRegisterDto);
|
||||
if (!processed) {
|
||||
status.setRollbackOnly();
|
||||
result.set(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
result.set(true);
|
||||
return true;
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
result.set(false);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return result.get();
|
||||
}
|
||||
}
|
||||
8
src/main/java/com/blog/processor/Processor.java
Normal file
8
src/main/java/com/blog/processor/Processor.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package com.blog.processor;
|
||||
|
||||
public interface Processor<T> {
|
||||
|
||||
boolean process(T t);
|
||||
|
||||
boolean rollback(T t);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.blog.processor.user;
|
||||
|
||||
import com.blog.config.MinioConfig;
|
||||
import com.blog.dto.UserRegisterDto;
|
||||
import com.blog.entity.PathEntity;
|
||||
import com.blog.holder.RequestImplicitContextHolder;
|
||||
import com.blog.mapper.PathMapper;
|
||||
import com.blog.processor.Processor;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.PutObjectArgs;
|
||||
import io.minio.errors.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
@Component
|
||||
public class PathbuildProcessor implements Processor<UserRegisterDto> {
|
||||
|
||||
@Autowired
|
||||
private MinioClient minioClient;
|
||||
|
||||
@Autowired
|
||||
private PathMapper pathMapper;
|
||||
|
||||
@Autowired
|
||||
private MinioConfig minioConfig;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean process(UserRegisterDto userRegisterDto) {
|
||||
|
||||
PathEntity pathEntity = PathEntity.builder()
|
||||
.userId(RequestImplicitContextHolder.getUserId())
|
||||
.pathName(userRegisterDto.getUsername())
|
||||
.build();
|
||||
pathMapper.insert(pathEntity);
|
||||
try {
|
||||
minioClient.putObject(
|
||||
PutObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.object(userRegisterDto.getUsername())
|
||||
.stream(new ByteArrayInputStream(new byte[] {}), 0, -1)
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
RequestImplicitContextHolder.pushErrorMessage(e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rollback(UserRegisterDto userRegisterDto) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.blog.task;
|
||||
|
||||
import com.blog.common.Captcha;
|
||||
import com.blog.common.CaptchaLimit;
|
||||
import com.blog.common.UserSession;
|
||||
import com.blog.holder.GlobalContextHolder;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
@@ -34,4 +35,15 @@ public class GlobalCleanupTask {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Async
|
||||
@Scheduled(fixedRate = 60000)
|
||||
public void purgeOnline() {
|
||||
Map<String, UserSession> ipLimitPool = GlobalContextHolder.getGlobalContext().getUserSessionMap();
|
||||
ipLimitPool.forEach((k,v)->{
|
||||
if(v.getTtl()< Instant.now().getEpochSecond()){
|
||||
ipLimitPool.remove(k);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package com.blog.vaildator;
|
||||
package com.blog.validator;
|
||||
import com.blog.dto.UserLoginDto;
|
||||
import com.blog.entity.InviteCodeEntity;
|
||||
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;
|
||||
import java.time.Instant;
|
||||
|
||||
@Component
|
||||
public class UserValidator {
|
||||
|
||||
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
public String validateUser(UserLoginDto user, UserEntity userEntity) {
|
||||
@@ -21,16 +24,26 @@ public class UserValidator {
|
||||
return "密码错误";
|
||||
}
|
||||
if (userEntity.getEnabled() == 0) return "账号已被禁用";
|
||||
if (userEntity.getReleaseDate() != null && userEntity.getReleaseDate().isAfter(LocalDateTime.now())) {
|
||||
if (userEntity.getReleaseDate() != null && userEntity.getReleaseDate().toInstant().isAfter(Instant.now())) {
|
||||
return ("账号封禁中,解禁日期:" + userEntity.getReleaseDate());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String validateRegister(UserEntity userEntity) {
|
||||
public String validateRegister(UserEntity userEntity, InviteCodeEntity inviteCodeEntity) {
|
||||
if(userEntity!=null){
|
||||
return "用户名已存在";
|
||||
}
|
||||
|
||||
if(inviteCodeEntity==null){
|
||||
return "无效的邀请码";
|
||||
}
|
||||
if(inviteCodeEntity.getExpireTime()!=null&&inviteCodeEntity.getExpireTime().toInstant().isBefore(Instant.now())){
|
||||
return "邀请码已过期";
|
||||
}
|
||||
if(inviteCodeEntity.getMaxUses()<=inviteCodeEntity.getUsedCount()){
|
||||
return "邀请码使用次数已用尽";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user