diff --git a/.gitignore b/.gitignore index 25e5e64..28c791c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,9 @@ build/ ### mybatis plus generator -/generator/ \ No newline at end of file +/generator/ + +### Uploads ### +/uploads/ +### Logs ### +/logs/ \ No newline at end of file diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java index b0e7c23..5fe0811 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java @@ -6,7 +6,7 @@ import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; -@FeignClient(name = "user-service", path = "/api/v1/user") +@FeignClient(name = "aioj-user-service", path = "/api/v1/user") public interface UserClient { @GetMapping("/inner/get-by-username") diff --git a/aioj-backend-auth/src/main/resources/application-dev.yml b/aioj-backend-auth/src/main/resources/application-dev.yml index 4880e1b..5854faf 100644 --- a/aioj-backend-auth/src/main/resources/application-dev.yml +++ b/aioj-backend-auth/src/main/resources/application-dev.yml @@ -1,6 +1,6 @@ spring: application: - name: auth-service + name: aioj-auth-service data: redis: host: 10.0.0.10 diff --git a/aioj-backend-auth/src/main/resources/application.yml b/aioj-backend-auth/src/main/resources/application.yml index 914ff8a..3ef233c 100644 --- a/aioj-backend-auth/src/main/resources/application.yml +++ b/aioj-backend-auth/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: application: - name: auth-service + name: aioj-auth-service profiles: active: @env@ devtools: @@ -36,4 +36,7 @@ jwt: enabled: true secret: "12345678901234567890123456789012" # 至少32字节!! access-expire: 900000 # 24小时 - refresh-expire: 604800000 # 7天 \ No newline at end of file + refresh-expire: 604800000 # 7天 +logging: + file: + path: ./logs/${spring.application.name} diff --git a/aioj-backend-auth/src/main/resources/logback-spring.xml b/aioj-backend-auth/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..8c32daa --- /dev/null +++ b/aioj-backend-auth/src/main/resources/logback-spring.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + UTF-8 + + + + + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.log + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.%d{yyyy-MM-dd}.%i.log.gz + 100MB + 31 + 100GB + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + INFO + + + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.log + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.%d{yyyy-MM-dd}.%i.log.gz + 100MB + 31 + 100GB + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + ERROR + ACCEPT + DENY + + + + + + 0 + 512 + + + + 0 + 512 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aioj-backend-file-service/src/main/resources/application.yml b/aioj-backend-file-service/src/main/resources/application.yml index 9860021..17be844 100644 --- a/aioj-backend-file-service/src/main/resources/application.yml +++ b/aioj-backend-file-service/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: application: - name: file-service + name: aioj-file-service profiles: active: @env@ servlet: diff --git a/aioj-backend-file-service/src/main/resources/logback-spring.xml b/aioj-backend-file-service/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..9c5ece5 --- /dev/null +++ b/aioj-backend-file-service/src/main/resources/logback-spring.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + UTF-8 + + + + + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.log + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.%d{yyyy-MM-dd}.%i.log.gz + 100MB + 31 + 100GB + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + INFO + + + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.log + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.%d{yyyy-MM-dd}.%i.log.gz + 100MB + 31 + 100GB + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + ERROR + ACCEPT + DENY + + + + + + 0 + 512 + + + + 0 + 512 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/filter/AuthGlobalFilter.java b/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/filter/AuthGlobalFilter.java index c12689d..5cf1775 100644 --- a/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/filter/AuthGlobalFilter.java +++ b/aioj-backend-gateway/src/main/java/cn/meowrain/aioj/backend/gateway/filter/AuthGlobalFilter.java @@ -105,7 +105,7 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered { private Mono validateToken(String token) { return webClientBuilder.build() .post() - .uri("lb://auth-service/api/v1/auth/validate") + .uri("lb://aioj-auth-service/api/v1/auth/validate") .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) .contentType(MediaType.APPLICATION_JSON) .retrieve() diff --git a/aioj-backend-gateway/src/main/resources/application.yml b/aioj-backend-gateway/src/main/resources/application.yml index 78bdf50..f2bc956 100644 --- a/aioj-backend-gateway/src/main/resources/application.yml +++ b/aioj-backend-gateway/src/main/resources/application.yml @@ -2,8 +2,9 @@ server: port: 18085 error: include-stacktrace: never - spring: + application: + name: aioj-backend-gateway profiles: active: @env@ cloud: @@ -13,28 +14,28 @@ spring: routes: # auth服务 Swagger 文档路由 - id: auth-service-doc - uri: lb://auth-service + uri: lb://aioj-auth-service predicates: - Path=/auth-service/** filters: - StripPrefix=1 # user服务 Swagger 文档路由 - id: user-service-doc - uri: lb://user-service + uri: lb://aioj-user-service predicates: - Path=/user-service/** filters: - StripPrefix=1 # auth服务 Swagger 文档路由 - id: file-service-doc - uri: lb://file-service + uri: lb://aioj-file-service predicates: - Path=/file-service/** filters: - StripPrefix=1 - # auth业务接口 + # auth业务接口路由 - id: auth-service - uri: lb://auth-service + uri: lb://aioj-auth-service predicates: - Path=/api/v1/auth/** filters: @@ -46,7 +47,7 @@ spring: firstBackoff: 50ms maxBackoff: 500ms - id: user-service - uri: lb://user-service + uri: lb://aioj-user-service predicates: - Path=/api/v1/user/** filters: @@ -58,7 +59,7 @@ spring: firstBackoff: 50ms maxBackoff: 500ms - id: file-service - uri: lb://file-service + uri: lb://aioj-file-service predicates: - Path=/api/v1/file/** filters: @@ -71,13 +72,14 @@ spring: maxBackoff: 500ms # 文件访问路由(公开,直接转发不去前缀) - id: file-access - uri: lb://file-service + uri: lb://aioj-file-service predicates: - Path=/api/file/** # 设置应用启动后的就绪探针 lifecycle: timeout-per-shutdown-phase: 30s + aioj-backend-gateway: # 白名单配置 white-list: @@ -93,3 +95,6 @@ aioj: log: enabled: true max-length: 20000 +logging: + file: + path: ./logs/${spring.application.name} diff --git a/aioj-backend-gateway/src/main/resources/logback-spring.xml b/aioj-backend-gateway/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..d2eb726 --- /dev/null +++ b/aioj-backend-gateway/src/main/resources/logback-spring.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + UTF-8 + + + + + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.log + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.%d{yyyy-MM-dd}.%i.log.gz + 100MB + 31 + 100GB + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + INFO + + + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.log + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.%d{yyyy-MM-dd}.%i.log.gz + 100MB + 31 + 100GB + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + ERROR + ACCEPT + DENY + + + + + + 0 + 512 + + + + 0 + 512 + + + + + + + + + + + + + + + + + + + + + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/auth.log + + ${FILE_LOG_PATTERN} + UTF-8 + + + + + + \ No newline at end of file diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/config/AsyncConfig.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/config/AsyncConfig.java new file mode 100644 index 0000000..d985b08 --- /dev/null +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/config/AsyncConfig.java @@ -0,0 +1,40 @@ +package cn.meowrain.aioj.backend.userservice.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 异步配置类 + * + * @author meowrain + * @since 2026-01-18 + */ +@Slf4j +@Configuration +@EnableAsync +public class AsyncConfig { + + @Bean("emailExecutor") + public Executor emailExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + // 核心线程数 + executor.setCorePoolSize(2); + // 最大线程数 + executor.setMaxPoolSize(5); + // 队列容量 + executor.setQueueCapacity(100); + // 线程名前缀 + executor.setThreadNamePrefix("email-async-"); + // 拒绝策略:由调用线程执行 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + log.info("邮件异步线程池初始化完成: coreSize=2, maxSize=5, queueCapacity=100"); + return executor; + } +} diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/controller/UserController.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/controller/UserController.java index 958f77b..7affa75 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/controller/UserController.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/controller/UserController.java @@ -1,13 +1,10 @@ package cn.meowrain.aioj.backend.userservice.controller; -import cn.meowrain.aioj.backend.framework.core.utils.ContextHolderUtils; import cn.meowrain.aioj.backend.framework.core.web.Result; import cn.meowrain.aioj.backend.framework.core.web.Results; -import cn.meowrain.aioj.backend.userservice.dto.req.AvatarUpdateRequestDTO; -import cn.meowrain.aioj.backend.userservice.dto.req.BindEmailRequest; -import cn.meowrain.aioj.backend.userservice.dto.req.EmailSendCodeRequestDTO; -import cn.meowrain.aioj.backend.userservice.dto.req.UserRegisterRequestDTO; +import cn.meowrain.aioj.backend.userservice.dto.req.*; import cn.meowrain.aioj.backend.userservice.dto.resp.UserAuthRespDTO; +import cn.meowrain.aioj.backend.userservice.dto.resp.UserProfileRespDTO; import cn.meowrain.aioj.backend.userservice.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -15,7 +12,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; @RequiredArgsConstructor @RestController() @@ -60,7 +56,7 @@ public class UserController { @Operation(summary = "发送验证码", description = "根据用户注册的邮箱发送验证码") @GetMapping("/email/send-code") public Result getVerifyCode(@Parameter(description = "邮箱信息", required = true) - @Valid @RequestParam EmailSendCodeRequestDTO request) { + @Valid @ModelAttribute EmailSendCodeRequestDTO request) { userService.sendEmailCode(request.getEmail()); return Results.success(null); } @@ -73,8 +69,8 @@ public class UserController { */ @Operation(summary = "绑定邮箱", description = "根据用户注册的邮箱绑定邮箱") @PostMapping("/email/bind") - public Result bindEmail(@RequestBody BindEmailRequest request) { - userService.bindEmail(request.getEmail(), request.getCode()); + public Result bindEmail(@RequestBody @Valid BindEmailRequest request) { + userService.bindEmail(request.getEmail(), request.getVerifyCode()); return Results.success(null); } @@ -86,20 +82,22 @@ public class UserController { @Operation(summary = "解绑邮箱", description = "根据用户注册的邮箱解绑邮箱") @PostMapping("/email/unbind") public Result unbindEmail() { - userService.unbindEmail(ContextHolderUtils.getCurrentUserId()); + userService.unbindEmail(); return Results.success(null); } @Operation(summary = "个人资料管理", description = "获取完整个人资料") @GetMapping("/profile") - public Result getUserProfile() { - return Results.success(); + public Result getUserProfile() { + UserProfileRespDTO userProfileRespDTO = userService.getUserProfile(); + return Results.success(userProfileRespDTO); } @Operation(summary = "个人资料管理-更新个人资料", description = "更新个人资料") @PutMapping("/profile") - public Result updateUserProfile() { + public Result updateUserProfile(@RequestBody UserProfileUpdateRequestDTO dto) { + userService.updateUserProfile(dto); return Results.success(); } @@ -110,8 +108,10 @@ public class UserController { return Results.success(); } + @PutMapping("/password") @Operation(summary = "个人资料管理-修改密码",description = "修改密码") - public Result changePassword() { + public Result changePassword(@RequestBody ChangePasswordRequestDTO dto) { + userService.changePassword(dto); return Results.success(); } } diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/BindEmailRequest.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/BindEmailRequest.java index 9bd9684..44eb37f 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/BindEmailRequest.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/BindEmailRequest.java @@ -12,5 +12,5 @@ public class BindEmailRequest { @Schema(description = "邮箱",example = "123@qq.com") private String email; @Schema(description = "验证码",example = "123456") - private String code; + private String verifyCode; } diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/ChangePasswordRequestDTO.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/ChangePasswordRequestDTO.java new file mode 100644 index 0000000..7fae58d --- /dev/null +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/ChangePasswordRequestDTO.java @@ -0,0 +1,13 @@ +package cn.meowrain.aioj.backend.userservice.dto.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "修改密码请求参数") +public class ChangePasswordRequestDTO { + @Schema(description = "旧密码") + private String oldPassword; + @Schema(description = "新密码") + private String newPassword; +} diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/UserProfileUpdateRequestDTO.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/UserProfileUpdateRequestDTO.java new file mode 100644 index 0000000..c49fbdc --- /dev/null +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/req/UserProfileUpdateRequestDTO.java @@ -0,0 +1,13 @@ +package cn.meowrain.aioj.backend.userservice.dto.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "用户个人资料更新请求DTO") +public class UserProfileUpdateRequestDTO { + @Schema(description = "用户昵称") + private String userName; + @Schema(description = "用户简介") + private String userProfile; +} diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/resp/UserProfileRespDTO.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/resp/UserProfileRespDTO.java new file mode 100644 index 0000000..a8f755c --- /dev/null +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/dto/resp/UserProfileRespDTO.java @@ -0,0 +1,10 @@ +package cn.meowrain.aioj.backend.userservice.dto.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "用户个人资料响应DTO") +public class UserProfileRespDTO { + +} diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/EmailService.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/EmailService.java index f1e74c7..f19b589 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/EmailService.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/EmailService.java @@ -10,4 +10,11 @@ public interface EmailService { * @param email 收件人邮箱 */ void sendVerifyCode(String email); + + /** + * 获取邮箱验证码 + * @param email 收件人邮箱 + * @return 验证码 + */ + String getVerifyCode(String email); } diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/UserService.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/UserService.java index 84ec10a..6fa0fee 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/UserService.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/UserService.java @@ -2,8 +2,11 @@ package cn.meowrain.aioj.backend.userservice.service; import cn.meowrain.aioj.backend.userservice.dao.entity.User; +import cn.meowrain.aioj.backend.userservice.dto.req.ChangePasswordRequestDTO; +import cn.meowrain.aioj.backend.userservice.dto.req.UserProfileUpdateRequestDTO; import cn.meowrain.aioj.backend.userservice.dto.req.UserRegisterRequestDTO; import cn.meowrain.aioj.backend.userservice.dto.resp.UserAuthRespDTO; +import cn.meowrain.aioj.backend.userservice.dto.resp.UserProfileRespDTO; import com.baomidou.mybatisplus.extension.service.IService; public interface UserService extends IService { @@ -42,7 +45,7 @@ public interface UserService extends IService { /** * 解绑邮箱 */ - void unbindEmail(Long userId); + void unbindEmail(); /** * 设置用户头像 @@ -50,4 +53,19 @@ public interface UserService extends IService { * @return */ void setProfileAvatar(Long fileId); + + /** + * 修改密码 + */ + void changePassword(ChangePasswordRequestDTO dto); + + /** + * 更新用户个人资料 + */ + void updateUserProfile(UserProfileUpdateRequestDTO dto); + + /** + * 获取用户个人资料 + */ + UserProfileRespDTO getUserProfile(); } diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/EmailServiceImpl.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/EmailServiceImpl.java index f865215..e7101f9 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/EmailServiceImpl.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/EmailServiceImpl.java @@ -1,5 +1,6 @@ package cn.meowrain.aioj.backend.userservice.service.impl; +import cn.meowrain.aioj.backend.framework.core.exception.ClientException; import cn.meowrain.aioj.backend.framework.core.exception.ServiceException; import cn.meowrain.aioj.backend.userservice.common.constants.RedisKeyConstants; import cn.meowrain.aioj.backend.userservice.service.EmailService; @@ -11,6 +12,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.io.UnsupportedEncodingException; @@ -95,6 +97,7 @@ public class EmailServiceImpl implements EmailService { } + @Async("emailExecutor") @Override public void sendVerifyCode(String email) { // 生成验证码 @@ -124,4 +127,22 @@ public class EmailServiceImpl implements EmailService { } } + + /** + * 获取邮箱验证码 + * @param email 收件人邮箱 + * @return 验证码 + */ + @Override + public String getVerifyCode(String email) { + String redisKey = String.format(RedisKeyConstants.EMAIL_CODE_PREFIX,email); + // 从redis里面获取验证码,用Object接收,因为redis里面可能存储的是null 比如过期这种情况 + Object verifyCodeInSystem = redisTemplate.opsForValue().get(redisKey); + if(verifyCodeInSystem == null) { + throw new ClientException("验证码不存在或已过期"); + } + // 转换为字符串 + return verifyCodeInSystem.toString(); + } + } diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/UserServiceImpl.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/UserServiceImpl.java index 4d963c2..d88afac 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/UserServiceImpl.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/UserServiceImpl.java @@ -10,8 +10,11 @@ import cn.meowrain.aioj.backend.userservice.dao.entity.User; import cn.meowrain.aioj.backend.userservice.dao.mapper.UserMapper; import cn.meowrain.aioj.backend.userservice.dto.chains.context.UserRegisterRequestParamVerifyContext; +import cn.meowrain.aioj.backend.userservice.dto.req.ChangePasswordRequestDTO; +import cn.meowrain.aioj.backend.userservice.dto.req.UserProfileUpdateRequestDTO; import cn.meowrain.aioj.backend.userservice.dto.req.UserRegisterRequestDTO; import cn.meowrain.aioj.backend.userservice.dto.resp.UserAuthRespDTO; +import cn.meowrain.aioj.backend.userservice.dto.resp.UserProfileRespDTO; import cn.meowrain.aioj.backend.userservice.service.EmailService; import cn.meowrain.aioj.backend.userservice.service.UserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -29,90 +32,187 @@ import java.util.Date; @Slf4j public class UserServiceImpl extends ServiceImpl implements UserService { - private final UserRegisterRequestParamVerifyContext userRegisterRequestParamVerifyContext; + private final UserRegisterRequestParamVerifyContext userRegisterRequestParamVerifyContext; - private final EmailService emailService; + private final EmailService emailService; - @Override - public Long userRegister(UserRegisterRequestDTO request) { - UserAuthRespDTO authInfoByUserAccount = findAuthInfoByUserAccount(request.getUserAccount()); - if (authInfoByUserAccount != null) { - throw new ClientException("重复创建用户"); - } + @Override + public Long userRegister(UserRegisterRequestDTO request) { + UserAuthRespDTO authInfoByUserAccount = findAuthInfoByUserAccount(request.getUserAccount()); + if (authInfoByUserAccount != null) { + throw new ClientException("重复创建用户"); + } - log.info("进行用户注册"); - userRegisterRequestParamVerifyContext.handler(ChainMarkEnums.USER_REGISTER_REQ_PARAM_VERIFY.getMarkName(), - request); - // 使用 BCrypt 加密密码 - Date now = new Date(); - String salt = BCrypt.gensalt(); - String encryptPassword = BCrypt.hashpw(request.getUserPassword(), salt); - User user = new User().setUserAccount(request.getUserAccount()) - .setUserPassword(encryptPassword) - .setUserRole("user") - .setCreateTime(now) - .setUpdateTime(now); - try { - // 需要修改表,使得用户名是唯一的 - this.save(user); - } - catch (DuplicateKeyException e) { - log.error("重复创建用户"); - throw new ServiceException("用户名已存在", ErrorCode.SYSTEM_ERROR); - } + log.info("进行用户注册"); + userRegisterRequestParamVerifyContext.handler(ChainMarkEnums.USER_REGISTER_REQ_PARAM_VERIFY.getMarkName(), + request); + // 使用 BCrypt 加密密码 + Date now = new Date(); + String salt = BCrypt.gensalt(); + String encryptPassword = BCrypt.hashpw(request.getUserPassword(), salt); + User user = new User().setUserAccount(request.getUserAccount()) + .setUserPassword(encryptPassword) + .setUserRole("user") + .setCreateTime(now) + .setUpdateTime(now); + try { + // 需要修改表,使得用户名是唯一的 + this.save(user); + } catch (DuplicateKeyException e) { + log.error("重复创建用户"); + throw new ServiceException("用户名已存在", ErrorCode.SYSTEM_ERROR); + } - return user.getId(); - } + return user.getId(); + } - @Override - public UserAuthRespDTO findAuthInfoByUserAccount(String userAccount) { - User one = this.lambdaQuery().eq(User::getUserAccount, userAccount).one(); - UserAuthRespDTO userAuthDTO = new UserAuthRespDTO(); - if (one != null) { - BeanUtils.copyProperties(one, userAuthDTO); - return userAuthDTO; - } - return null; - } + @Override + public UserAuthRespDTO findAuthInfoByUserAccount(String userAccount) { + User one = this.lambdaQuery().eq(User::getUserAccount, userAccount).one(); + UserAuthRespDTO userAuthDTO = new UserAuthRespDTO(); + if (one != null) { + BeanUtils.copyProperties(one, userAuthDTO); + return userAuthDTO; + } + return null; + } - @Override - public UserAuthRespDTO findAuthInfoByUserId(Long userId) { - User one = this.lambdaQuery().eq(User::getId, userId).one(); - UserAuthRespDTO userAuthDTO = new UserAuthRespDTO(); - if (one != null) { - BeanUtils.copyProperties(one, userAuthDTO); - return userAuthDTO; - } - return null; + @Override + public UserAuthRespDTO findAuthInfoByUserId(Long userId) { + User one = this.lambdaQuery().eq(User::getId, userId).one(); + UserAuthRespDTO userAuthDTO = new UserAuthRespDTO(); + if (one != null) { + BeanUtils.copyProperties(one, userAuthDTO); + return userAuthDTO; + } + return null; - } + } - @Override - public void sendEmailCode(String email) { - emailService.sendVerifyCode(email); - } + @Override + public void sendEmailCode(String email) { + emailService.sendVerifyCode(email); + } - @Override - public void bindEmail(String email, String code) { - Long currentUserId = ContextHolderUtils.getCurrentUserId(); + @Override + public void bindEmail(String email, String code) { + if (email == null || code == null) { + throw new ClientException("邮箱或验证码不能为空"); + } + Long currentUserId = ContextHolderUtils.getCurrentUserId(); + // 检查用户是否存在 + User user = this.lambdaQuery().eq(User::getId, currentUserId).one(); + if (user == null) { + throw new ClientException("用户不存在"); + } - } + // 验证邮箱验证码 + // 从redis里面获取验证码 + String verifyCodeInSystem = emailService.getVerifyCode(email); + // 验证验证码是否匹配 + if (!verifyCodeInSystem.equals(code)) { + throw new ClientException("验证码错误,请重新输入"); + } - @Override - public void unbindEmail(Long userId) { - User one = this.lambdaQuery().eq(User::getId, userId).one(); + // 绑定邮箱 + user.setUserEmail(email); + user.setUpdateTime(new Date()); + this.updateById(user); + } - } + @Override + public void unbindEmail() { + Long currentUserId = ContextHolderUtils.getCurrentUserId(); + User user = this.lambdaQuery().eq(User::getId, currentUserId).one(); + if (user == null) { + throw new ClientException("用户不存在"); + } - @Transactional(rollbackFor = Exception.class) - @Override - public void setProfileAvatar(Long fileId) { - Long currentUserId = ContextHolderUtils.getCurrentUserId(); - User user = this.lambdaQuery().eq(User::getId, currentUserId).one(); - user.setUserAvatar(fileId); - user.setUpdateTime(new Date()); - this.updateById(user); - } + if (user.getUserEmail() == null) { + throw new ClientException("邮箱未绑定"); + } + // 这种方法避免了查询整个对象,并且能精确控制 NULL 值的更新。 + boolean success = this.lambdaUpdate() + // 设置 userEmail 为 NULL + .set(User::getUserEmail, null) + // 设置 updateTime 为当前时间 + .set(User::getUpdateTime, new Date()) + // 筛选条件:用户ID + .eq(User::getId, currentUserId) + // 执行更新 + .update(); + + if (!success) { + throw new ClientException("解绑失败,请重试"); + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void setProfileAvatar(Long fileId) { + Long currentUserId = ContextHolderUtils.getCurrentUserId(); + User user = this.lambdaQuery().eq(User::getId, currentUserId).one(); + user.setUserAvatar(fileId); + user.setUpdateTime(new Date()); + this.updateById(user); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void changePassword(ChangePasswordRequestDTO dto) { + Long currentUserId = ContextHolderUtils.getCurrentUserId(); + User user = this.lambdaQuery().eq(User::getId, currentUserId).one(); + + // 检查用户是否存在 + if (user == null) { + throw new ClientException("用户不存在"); + } + + // 检查旧密码是否正确 + String oldPassword = user.getUserPassword(); + if (!BCrypt.checkpw(dto.getOldPassword(), oldPassword)) { + throw new ClientException("旧密码错误"); + } + + // 检查新密码是否与旧密码相同 + if (BCrypt.checkpw(dto.getNewPassword(), oldPassword)) { + throw new ClientException("新密码不能与旧密码相同"); + } + + + // 加密新密码 + Date now = new Date(); + String salt = BCrypt.gensalt(); + String encryptPassword = BCrypt.hashpw(dto.getNewPassword(), salt); + // 更新用户密码 + user.setUserPassword(encryptPassword); + user.setUpdateTime(now); + this.updateById(user); + } + + @Override + public void updateUserProfile(UserProfileUpdateRequestDTO dto) { + Long currentUserId = ContextHolderUtils.getCurrentUserId(); + User user = this.lambdaQuery().eq(User::getId, currentUserId).one(); + if (user == null) { + throw new ClientException("用户不存在"); + } + // 更新用户个人资料 + user.setUserName(dto.getUserName()); + user.setUserProfile(dto.getUserProfile()); + user.setUpdateTime(new Date()); + this.updateById(user); + } + + /** + * 获取用户个人资料 + * @return 用户个人资料 + */ + @Override + public UserProfileRespDTO getUserProfile() { + // TODO: 待实现 从数据库查询用户个人资料 + return null; + } } diff --git a/aioj-backend-user-service/src/main/resources/application.yml b/aioj-backend-user-service/src/main/resources/application.yml index acad1cb..bb01c8f 100644 --- a/aioj-backend-user-service/src/main/resources/application.yml +++ b/aioj-backend-user-service/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: application: - name: user-service + name: aioj-user-service profiles: active: @env@ server: @@ -42,4 +42,7 @@ jwt: aioj: log: enabled: true - max-length: 20000 \ No newline at end of file + max-length: 20000 +logging: + file: + path: ./logs/${spring.application.name} diff --git a/aioj-backend-user-service/src/main/resources/logback-spring.xml b/aioj-backend-user-service/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..8c32daa --- /dev/null +++ b/aioj-backend-user-service/src/main/resources/logback-spring.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + UTF-8 + + + + + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.log + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.%d{yyyy-MM-dd}.%i.log.gz + 100MB + 31 + 100GB + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + INFO + + + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.log + + ${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.%d{yyyy-MM-dd}.%i.log.gz + 100MB + 31 + 100GB + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + ERROR + ACCEPT + DENY + + + + + + 0 + 512 + + + + 0 + 512 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file