From 0570ddd249a15f4da4dbe4642281ec8c5a56818f Mon Sep 17 00:00:00 2001 From: bunny <1319900154@qq.com> Date: Thu, 1 May 2025 11:57:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E5=8A=9F=E8=83=BD;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除不用的函数和冗余代码,将用户业务功能放在service下;Minio更名为MinioService将业务类和服务类拆分 --- .../bunny/services/aop/JobExecuteAspect.java | 2 +- .../controller/system/UserController.java | 72 +--- .../system/UserLoginController.java | 63 ++++ .../java/AbstractPermissionCheckHandler.java | 10 + .../cn/bunny/services/config/WebConfig.java | 34 +- .../controller/system/UserControllerTest.java | 58 ++++ .../bunny/services/utils/TokenUtilsTest.java | 6 +- .../src/test/java/system/UserServiceTest.java | 33 ++ .../domain/common/constant/UserConstant.java | 3 + .../common/{model/dto => }/ip/IpEntity.java | 2 +- .../quartz/ScheduleExecuteLogJson.java | 2 +- .../{model/dto => }/security/TokenInfo.java | 2 +- .../mail/MailSenderConfiguration.java | 2 +- .../{config => }/mail/template-propties | 0 .../cn/bunny/services/minio/MinioHelper.java | 111 ++++++ .../{config => }/minio/MinioProperties.java | 2 +- .../MinioService.java} | 35 +- .../java/cn/bunny/services/utils/IpUtil.java | 2 +- ...CustomAuthorizationManagerServiceImpl.java | 2 +- .../service/TokenValidationService.java | 2 +- .../impl/MessageReceivedServiceImpl.java | 14 +- .../message/impl/MessageServiceImpl.java | 19 +- .../service/system/UserLoginService.java | 42 +++ .../services/service/system/UserService.java | 39 +-- .../system/helper/UserLoginHelper.java | 189 +++++++++++ .../helper}/login/DefaultLoginStrategy.java | 14 +- .../helper}/login/EmailLoginStrategy.java | 18 +- .../system/helper}/login/LoginContext.java | 19 +- .../system/helper}/login/LoginStrategy.java | 10 +- .../system/helper}/login/ReadMe.md | 0 .../service/system/impl/FilesServiceImpl.java | 25 +- .../service/system/impl/RoleServiceImpl.java | 10 +- .../system/impl/UserLoginServiceImpl.java | 184 ++++++++++ .../system/impl/UserRoleServiceImpl.java | 17 +- .../service/system/impl/UserServiceImpl.java | 248 +++++--------- .../email/AbstractSenderEmailTemplate.java | 4 +- .../bunny/services/utils/system/RoleUtil.java | 30 +- .../bunny/services/utils/system/UserUtil.java | 316 ------------------ 38 files changed, 948 insertions(+), 693 deletions(-) create mode 100644 auh-api/src/main/java/cn/bunny/services/controller/system/UserLoginController.java create mode 100644 auh-api/src/test/java/AbstractPermissionCheckHandler.java create mode 100644 auh-api/src/test/java/cn/bunny/services/controller/system/UserControllerTest.java create mode 100644 auh-api/src/test/java/system/UserServiceTest.java rename auth-core/src/main/java/cn/bunny/services/domain/common/{model/dto => }/ip/IpEntity.java (90%) rename auth-core/src/main/java/cn/bunny/services/domain/common/{model/dto => }/quartz/ScheduleExecuteLogJson.java (93%) rename auth-core/src/main/java/cn/bunny/services/domain/common/{model/dto => }/security/TokenInfo.java (91%) rename auth-core/src/main/java/cn/bunny/services/{config => }/mail/MailSenderConfiguration.java (98%) rename auth-core/src/main/java/cn/bunny/services/{config => }/mail/template-propties (100%) create mode 100644 auth-core/src/main/java/cn/bunny/services/minio/MinioHelper.java rename auth-core/src/main/java/cn/bunny/services/{config => }/minio/MinioProperties.java (98%) rename auth-core/src/main/java/cn/bunny/services/{config/minio/MinioUtil.java => minio/MinioService.java} (91%) create mode 100644 service/src/main/java/cn/bunny/services/service/system/UserLoginService.java create mode 100644 service/src/main/java/cn/bunny/services/service/system/helper/UserLoginHelper.java rename service/src/main/java/cn/bunny/services/{utils => service/system/helper}/login/DefaultLoginStrategy.java (78%) rename service/src/main/java/cn/bunny/services/{utils => service/system/helper}/login/EmailLoginStrategy.java (83%) rename service/src/main/java/cn/bunny/services/{utils => service/system/helper}/login/LoginContext.java (66%) rename service/src/main/java/cn/bunny/services/{utils => service/system/helper}/login/LoginStrategy.java (59%) rename service/src/main/java/cn/bunny/services/{utils => service/system/helper}/login/ReadMe.md (100%) create mode 100644 service/src/main/java/cn/bunny/services/service/system/impl/UserLoginServiceImpl.java delete mode 100644 service/src/main/java/cn/bunny/services/utils/system/UserUtil.java diff --git a/auh-api/src/main/java/cn/bunny/services/aop/JobExecuteAspect.java b/auh-api/src/main/java/cn/bunny/services/aop/JobExecuteAspect.java index 1cd4188..4657691 100644 --- a/auh-api/src/main/java/cn/bunny/services/aop/JobExecuteAspect.java +++ b/auh-api/src/main/java/cn/bunny/services/aop/JobExecuteAspect.java @@ -3,7 +3,7 @@ package cn.bunny.services.aop; import cn.bunny.services.domain.common.constant.LocalDateTimeConstant; import cn.bunny.services.domain.common.enums.JobEnums; import cn.bunny.services.domain.system.log.entity.ScheduleExecuteLog; -import cn.bunny.services.domain.common.model.dto.quartz.ScheduleExecuteLogJson; +import cn.bunny.services.domain.common.quartz.ScheduleExecuteLogJson; import cn.bunny.services.mapper.log.ScheduleExecuteLogMapper; import com.alibaba.fastjson2.JSON; import jakarta.annotation.Resource; diff --git a/auh-api/src/main/java/cn/bunny/services/controller/system/UserController.java b/auh-api/src/main/java/cn/bunny/services/controller/system/UserController.java index d86830e..38c78b8 100644 --- a/auh-api/src/main/java/cn/bunny/services/controller/system/UserController.java +++ b/auh-api/src/main/java/cn/bunny/services/controller/system/UserController.java @@ -1,16 +1,15 @@ package cn.bunny.services.controller.system; -import cn.bunny.services.context.BaseContext; -import cn.bunny.services.domain.common.model.vo.LoginVo; import cn.bunny.services.domain.common.model.vo.result.PageResult; import cn.bunny.services.domain.common.model.vo.result.Result; import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; -import cn.bunny.services.domain.system.system.dto.user.*; +import cn.bunny.services.domain.system.system.dto.user.AdminUserAddDto; +import cn.bunny.services.domain.system.system.dto.user.AdminUserDto; +import cn.bunny.services.domain.system.system.dto.user.AdminUserUpdateByLocalUserDto; +import cn.bunny.services.domain.system.system.dto.user.AdminUserUpdateDto; import cn.bunny.services.domain.system.system.entity.AdminUser; import cn.bunny.services.domain.system.system.vo.user.AdminUserVo; -import cn.bunny.services.domain.system.system.vo.user.RefreshTokenVo; import cn.bunny.services.domain.system.system.vo.user.UserVo; -import cn.bunny.services.exception.AuthCustomerException; import cn.bunny.services.service.system.UserService; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.v3.oas.annotations.Operation; @@ -18,7 +17,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -33,49 +31,6 @@ public class UserController { @Resource private UserService userService; - // ----------------------------------------- - // 用户登录和退出 - // ----------------------------------------- - @Operation(summary = "用户登录", description = "前端用户登录") - @PostMapping("login") - public Result login(@Valid @RequestBody LoginDto loginDto) { - LoginVo loginVo = userService.login(loginDto); - return Result.success(loginVo); - } - - @Operation(summary = "登录发送邮件验证码", description = "登录发送邮件验证码") - @PostMapping("public/sendLoginEmail") - public Result sendLoginEmail(String email) { - if (!StringUtils.hasText(email)) throw new AuthCustomerException(ResultCodeEnum.REQUEST_IS_EMPTY); - - userService.sendLoginEmail(email); - return Result.success(ResultCodeEnum.EMAIL_CODE_SEND_SUCCESS); - } - - @Operation(summary = "刷新token", description = "刷新用户token") - @PostMapping("private/refreshToken") - public Result refreshToken(@Valid @RequestBody RefreshTokenDto dto) { - RefreshTokenVo vo = userService.refreshToken(dto); - return Result.success(vo); - } - - @Operation(summary = "获取本地登录用户信息", description = "获取用户信息从Redis中获取") - @GetMapping("private/userinfo") - public Result userinfo() { - LoginVo vo = BaseContext.getLoginVo(); - return Result.success(vo); - } - - @Operation(summary = "退出登录", description = "退出登录") - @PostMapping("private/logout") - public Result logout() { - userService.logout(); - return Result.success(ResultCodeEnum.LOGOUT_SUCCESS); - } - - // ----------------------------------------- - // 管理用户CURD - // ----------------------------------------- @Operation(summary = "分页查询", description = "分页查询用户信息", tags = "user::query") @GetMapping("{page}/{limit}") public Result> getUserPageByAdmin( @@ -89,21 +44,19 @@ public class UserController { return Result.success(pageResult); } - @Operation(summary = "添加", description = "添加用户信息", tags = "user::add") + @Operation(summary = "添加用户", description = "添加用户信息", tags = "user::add") @PostMapping() public Result addUserByAdmin(@Valid @RequestBody AdminUserAddDto dto) { userService.addUserByAdmin(dto); return Result.success(ResultCodeEnum.ADD_SUCCESS); } - @Operation(summary = "更新", description = "更新用户信息,需要更新Redis中的内容", tags = "user::update") + @Operation(summary = "更新用户", description = "更新用户信息,需要更新Redis中的内容", tags = "user::update") @PutMapping() - public Result updateUserByAdmin( - @Valid AdminUserUpdateDto dto, - @RequestPart(value = "avatar", required = false) MultipartFile avatar) { - if (avatar != null) { - dto.setAvatar(avatar); - } + public Result updateUserByAdmin(@Valid AdminUserUpdateDto dto, + @RequestPart(value = "avatar", required = false) MultipartFile avatar) { + if (avatar != null) dto.setAvatar(avatar); + userService.updateUserByAdmin(dto); return Result.success(ResultCodeEnum.UPDATE_SUCCESS); } @@ -129,16 +82,13 @@ public class UserController { return Result.success(voList); } - @Operation(summary = "强制退出", description = "强制退出", tags = "user::update") + @Operation(summary = "强制退出用户", description = "强制退出", tags = "user::update") @PutMapping("forcedOffline") public Result forcedOfflineByAdmin(@RequestBody Long id) { userService.forcedOfflineByAdmin(id); return Result.success(); } - // ----------------------------------------- - // 普通用户 - // ----------------------------------------- @Operation(summary = "更新本地用户信息", description = "更新本地用户信息,需要更新Redis中的内容") @PutMapping("private/update/userinfo") public Result updateAdminUserByLocalUser(@Valid @RequestBody AdminUserUpdateByLocalUserDto dto) { diff --git a/auh-api/src/main/java/cn/bunny/services/controller/system/UserLoginController.java b/auh-api/src/main/java/cn/bunny/services/controller/system/UserLoginController.java new file mode 100644 index 0000000..0fdbc44 --- /dev/null +++ b/auh-api/src/main/java/cn/bunny/services/controller/system/UserLoginController.java @@ -0,0 +1,63 @@ +package cn.bunny.services.controller.system; + +import cn.bunny.services.context.BaseContext; +import cn.bunny.services.domain.common.model.vo.LoginVo; +import cn.bunny.services.domain.common.model.vo.result.Result; +import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; +import cn.bunny.services.domain.system.system.dto.user.LoginDto; +import cn.bunny.services.domain.system.system.dto.user.RefreshTokenDto; +import cn.bunny.services.domain.system.system.vo.user.RefreshTokenVo; +import cn.bunny.services.exception.AuthCustomerException; +import cn.bunny.services.service.system.UserLoginService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "用户登录", description = "用户登录相关接口") +@RestController +@RequestMapping("/api/user") +public class UserLoginController { + + @Resource + private UserLoginService userLoginService; + + @Operation(summary = "用户登录", description = "前端用户登录") + @PostMapping("login") + public Result login(@Valid @RequestBody LoginDto loginDto) { + LoginVo loginVo = userLoginService.login(loginDto); + return Result.success(loginVo); + } + + @Operation(summary = "登录发送邮件验证码", description = "登录发送邮件验证码") + @PostMapping("public/sendLoginEmail") + public Result sendLoginEmail(String email) { + if (!StringUtils.hasText(email)) throw new AuthCustomerException(ResultCodeEnum.REQUEST_IS_EMPTY); + + userLoginService.sendLoginEmail(email); + return Result.success(ResultCodeEnum.EMAIL_CODE_SEND_SUCCESS); + } + + @Operation(summary = "刷新token", description = "刷新用户token") + @PostMapping("private/refreshToken") + public Result refreshToken(@Valid @RequestBody RefreshTokenDto dto) { + RefreshTokenVo vo = userLoginService.refreshToken(dto); + return Result.success(vo); + } + + @Operation(summary = "获取本地登录用户信息", description = "获取用户信息从Redis中获取") + @GetMapping("private/userinfo") + public Result userinfo() { + LoginVo vo = BaseContext.getLoginVo(); + return Result.success(vo); + } + + @Operation(summary = "退出登录", description = "退出登录") + @PostMapping("private/logout") + public Result logout() { + userLoginService.logout(); + return Result.success(ResultCodeEnum.LOGOUT_SUCCESS); + } +} diff --git a/auh-api/src/test/java/AbstractPermissionCheckHandler.java b/auh-api/src/test/java/AbstractPermissionCheckHandler.java new file mode 100644 index 0000000..dbc3fa1 --- /dev/null +++ b/auh-api/src/test/java/AbstractPermissionCheckHandler.java @@ -0,0 +1,10 @@ +public abstract class AbstractPermissionCheckHandler { + + private AbstractPermissionCheckHandler abstractPermissionCheckHandler; + + public AbstractPermissionCheckHandler(AbstractPermissionCheckHandler abstractPermissionCheckHandler) { + this.abstractPermissionCheckHandler = abstractPermissionCheckHandler; + } + + abstract protected void checkPermission(String requestUrl); +} diff --git a/auh-api/src/test/java/cn/bunny/services/config/WebConfig.java b/auh-api/src/test/java/cn/bunny/services/config/WebConfig.java index db1b5f1..63dc5d6 100644 --- a/auh-api/src/test/java/cn/bunny/services/config/WebConfig.java +++ b/auh-api/src/test/java/cn/bunny/services/config/WebConfig.java @@ -8,20 +8,20 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; -@TestConfiguration -public class WebConfig { - @Value("${server.port}") - private String port; - - @Autowired - private TokenUtilsTest tokenUtils; - - @Bean - public RestTemplate restTemplate(RestTemplateBuilder builder) { - String token = tokenUtils.getToken(); - return builder.rootUri("http://localhost:" + port) - .defaultHeader("token", token) - .defaultHeader("Content-Type", "application/json") - .build(); - } -} +// @TestConfiguration +// public class WebConfig { +// @Value("${server.port}") +// private String port; +// +// @Autowired +// private TokenUtilsTest tokenUtils; +// +// @Bean +// public RestTemplate restTemplate(RestTemplateBuilder builder) { +// String token = tokenUtils.getToken(); +// return builder.rootUri("http://localhost:" + port) +// .defaultHeader("token", token) +// .defaultHeader("Content-Type", "application/json") +// .build(); +// } +// } diff --git a/auh-api/src/test/java/cn/bunny/services/controller/system/UserControllerTest.java b/auh-api/src/test/java/cn/bunny/services/controller/system/UserControllerTest.java new file mode 100644 index 0000000..c5470a9 --- /dev/null +++ b/auh-api/src/test/java/cn/bunny/services/controller/system/UserControllerTest.java @@ -0,0 +1,58 @@ +package cn.bunny.services.controller.system; + +import cn.bunny.services.domain.common.constant.RedisUserConstant; +import com.alibaba.fastjson2.JSONObject; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class UserControllerTest { + @Resource + private RedisTemplate redisTemplate; + + @Test + void test() { + // Set keys = redisTemplate.keys("admin::login_info::*"); + // for (String key : keys) { + // System.out.println(key); + // } + + Map adminLoginInfoWithScan = getAdminLoginInfoWithScan(); + JSONObject adminLoginInfo = new JSONObject(adminLoginInfoWithScan); + System.out.println(adminLoginInfo); + } + + public Map getAdminLoginInfoWithScan() { + String pattern = "admin::login_info::*"; + Map result = new HashMap<>(); + + // 使用scan命令迭代查找 + ScanOptions options = ScanOptions.scanOptions().match(pattern).count(100).build(); + Cursor cursor = redisTemplate.scan(options); + + while (cursor.hasNext()) { + String key = cursor.next(); + Object value = redisTemplate.opsForValue().get(key); + result.put(key, value); + } + + try { + cursor.close(); + } catch (Exception e) { + // 处理异常 + } + + return result; + } +} \ No newline at end of file diff --git a/auh-api/src/test/java/cn/bunny/services/utils/TokenUtilsTest.java b/auh-api/src/test/java/cn/bunny/services/utils/TokenUtilsTest.java index 16db80a..587d563 100644 --- a/auh-api/src/test/java/cn/bunny/services/utils/TokenUtilsTest.java +++ b/auh-api/src/test/java/cn/bunny/services/utils/TokenUtilsTest.java @@ -1,9 +1,9 @@ package cn.bunny.services.utils; -import cn.bunny.services.domain.system.system.entity.AdminUser; import cn.bunny.services.domain.common.model.vo.LoginVo; +import cn.bunny.services.domain.system.system.entity.AdminUser; import cn.bunny.services.mapper.system.UserMapper; -import cn.bunny.services.utils.system.UserUtil; +import cn.bunny.services.service.system.helper.UserLoginHelper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -11,7 +11,7 @@ import org.springframework.stereotype.Component; @Component public class TokenUtilsTest { @Autowired - private UserUtil userUtil; + private UserLoginHelper userUtil; @Autowired private UserMapper userMapper; diff --git a/auh-api/src/test/java/system/UserServiceTest.java b/auh-api/src/test/java/system/UserServiceTest.java new file mode 100644 index 0000000..53f61b9 --- /dev/null +++ b/auh-api/src/test/java/system/UserServiceTest.java @@ -0,0 +1,33 @@ +package system; + +import cn.bunny.services.controller.system.UserController; +import cn.bunny.services.domain.common.constant.RedisUserConstant; +import cn.bunny.services.service.system.impl.UserServiceImpl; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.RedisConnectionUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.test.context.web.WebAppConfiguration; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(classes = UserServiceImpl.class) +class UserServiceTest { + + @Resource + private RedisTemplate redisTemplate; + + @Test + void test() { + String prefix = RedisUserConstant.getAdminUserEmailCodePrefix(""); + Set keys = redisTemplate.keys(prefix); + for (String key : keys) { + System.out.println(key); + } + } +} \ No newline at end of file diff --git a/auth-core/src/main/java/cn/bunny/services/domain/common/constant/UserConstant.java b/auth-core/src/main/java/cn/bunny/services/domain/common/constant/UserConstant.java index d0447b8..ed0d11d 100644 --- a/auth-core/src/main/java/cn/bunny/services/domain/common/constant/UserConstant.java +++ b/auth-core/src/main/java/cn/bunny/services/domain/common/constant/UserConstant.java @@ -6,4 +6,7 @@ import lombok.Data; public class UserConstant { public static final String USER_AVATAR = "https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132"; public static final String PERSON_DESCRIPTION = "这个人很懒没有介绍..."; + public static final String LOGIN = "login"; + public static final String LOGOUT = "logout"; + public static final String FORCE_LOGOUT = "force_logout"; } diff --git a/auth-core/src/main/java/cn/bunny/services/domain/common/model/dto/ip/IpEntity.java b/auth-core/src/main/java/cn/bunny/services/domain/common/ip/IpEntity.java similarity index 90% rename from auth-core/src/main/java/cn/bunny/services/domain/common/model/dto/ip/IpEntity.java rename to auth-core/src/main/java/cn/bunny/services/domain/common/ip/IpEntity.java index ed59452..8560f34 100644 --- a/auth-core/src/main/java/cn/bunny/services/domain/common/model/dto/ip/IpEntity.java +++ b/auth-core/src/main/java/cn/bunny/services/domain/common/ip/IpEntity.java @@ -1,4 +1,4 @@ -package cn.bunny.services.domain.common.model.dto.ip; +package cn.bunny.services.domain.common.ip; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; diff --git a/auth-core/src/main/java/cn/bunny/services/domain/common/model/dto/quartz/ScheduleExecuteLogJson.java b/auth-core/src/main/java/cn/bunny/services/domain/common/quartz/ScheduleExecuteLogJson.java similarity index 93% rename from auth-core/src/main/java/cn/bunny/services/domain/common/model/dto/quartz/ScheduleExecuteLogJson.java rename to auth-core/src/main/java/cn/bunny/services/domain/common/quartz/ScheduleExecuteLogJson.java index d4eb275..9c6b213 100644 --- a/auth-core/src/main/java/cn/bunny/services/domain/common/model/dto/quartz/ScheduleExecuteLogJson.java +++ b/auth-core/src/main/java/cn/bunny/services/domain/common/quartz/ScheduleExecuteLogJson.java @@ -1,4 +1,4 @@ -package cn.bunny.services.domain.common.model.dto.quartz; +package cn.bunny.services.domain.common.quartz; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; diff --git a/auth-core/src/main/java/cn/bunny/services/domain/common/model/dto/security/TokenInfo.java b/auth-core/src/main/java/cn/bunny/services/domain/common/security/TokenInfo.java similarity index 91% rename from auth-core/src/main/java/cn/bunny/services/domain/common/model/dto/security/TokenInfo.java rename to auth-core/src/main/java/cn/bunny/services/domain/common/security/TokenInfo.java index 19dc6ce..b06da81 100644 --- a/auth-core/src/main/java/cn/bunny/services/domain/common/model/dto/security/TokenInfo.java +++ b/auth-core/src/main/java/cn/bunny/services/domain/common/security/TokenInfo.java @@ -1,4 +1,4 @@ -package cn.bunny.services.domain.common.model.dto.security; +package cn.bunny.services.domain.common.security; import cn.bunny.services.domain.common.model.vo.LoginVo; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/auth-core/src/main/java/cn/bunny/services/config/mail/MailSenderConfiguration.java b/auth-core/src/main/java/cn/bunny/services/mail/MailSenderConfiguration.java similarity index 98% rename from auth-core/src/main/java/cn/bunny/services/config/mail/MailSenderConfiguration.java rename to auth-core/src/main/java/cn/bunny/services/mail/MailSenderConfiguration.java index 1aa5024..43ae9ba 100644 --- a/auth-core/src/main/java/cn/bunny/services/config/mail/MailSenderConfiguration.java +++ b/auth-core/src/main/java/cn/bunny/services/mail/MailSenderConfiguration.java @@ -1,4 +1,4 @@ -package cn.bunny.services.config.mail; +package cn.bunny.services.mail; import cn.bunny.services.domain.common.model.dto.email.EmailSend; import cn.bunny.services.domain.common.model.dto.email.EmailSendInit; diff --git a/auth-core/src/main/java/cn/bunny/services/config/mail/template-propties b/auth-core/src/main/java/cn/bunny/services/mail/template-propties similarity index 100% rename from auth-core/src/main/java/cn/bunny/services/config/mail/template-propties rename to auth-core/src/main/java/cn/bunny/services/mail/template-propties diff --git a/auth-core/src/main/java/cn/bunny/services/minio/MinioHelper.java b/auth-core/src/main/java/cn/bunny/services/minio/MinioHelper.java new file mode 100644 index 0000000..73c5f47 --- /dev/null +++ b/auth-core/src/main/java/cn/bunny/services/minio/MinioHelper.java @@ -0,0 +1,111 @@ +package cn.bunny.services.minio; + +import cn.bunny.services.domain.common.constant.UserConstant; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +@Component +public class MinioHelper { + + @Resource + private MinioProperties properties; + + /** + * 格式化用户头像URL + * + *

实现头像URL的统一存储和访问格式转换:

+ * + *
    + *
  1. 存储处理:将带HTTP前缀的头像URL转换为数据库存储格式 + *
      + *
    • 匹配正则表达式:^https?://.*?/(.*)
    • + *
    • 提取路径部分:matcher.group(1)
    • + *
    • 转换为存储格式:"/" + 提取的路径
    • + *
    + *
  2. + *
  3. 访问处理:返回带HTTP前缀的完整访问URL
  4. + *
+ * + *

典型用例:

+ *
+     * 输入:"http|s://example.com/images/avatar.jpg"
+     * 存储:"images/avatar.jpg"
+     * 访问:"http|s://xxx/images/avatar.jpg"
+     * 
+ * + * @param avatar 头像URL(可能包含HTTP前缀或数据库存储格式) + * @return 格式化后的头像URL(确保包含HTTP前缀) + * @throws PatternSyntaxException 当正则表达式匹配失败时抛出 + * @throws IllegalArgumentException 当头像参数为空时抛出 + */ + public String formatUserAvatar(String avatar) { + // 如果用户没有头像或者用户头像和默认头像相同,返回默认头像 + String userAvatar = UserConstant.USER_AVATAR; + if (!StringUtils.hasText(avatar) || avatar.equals(userAvatar)) return userAvatar; + + // 替换前端发送的host前缀,将其删除,只保留路径名称 + String regex = "^https?://.*?/(.*)"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(avatar); + + // 如果没有匹配 + if (!matcher.matches()) return avatar; + + // 匹配后返回内容 + return "/" + matcher.group(1); + } + + /** + * 检查并格式化用户头像URL + * + *

处理逻辑:

+ *
    + *
  1. 当头像为空或与默认头像相同时,返回系统默认头像
  2. + *
  3. 尝试移除头像URL中的HTTP协议和域名部分(如果存在)
  4. + *
  5. 最终返回MinIO存储中的完整对象访问路径
  6. + *
+ * + * @param avatar 用户头像URL,可能为以下格式: + * - 空/null(使用默认头像) + * - 完整HTTP URL(如"http|s://example.com/images/1.jpg") + * - MinIO对象路径(如"images/1.jpg") + * @return 格式化后的头像URL,保证是可访问的完整路径 + * - 默认头像(当输入无效时) + * - MinIO完整访问路径(处理成功时) + * @see UserConstant#USER_AVATAR 默认头像常量 + * @see #getObjectNameFullPath MinIO路径处理方法 + */ + public String getUserAvatar(String avatar) { + // 如果用户没有头像或者用户头像和默认头像相同,返回默认头像 + String userAvatar = UserConstant.USER_AVATAR; + if (!StringUtils.hasText(avatar) || avatar.equals(userAvatar)) return userAvatar; + + // 替换前端发送的host前缀,将其删除,只保留路径名称 + String regex = "^https?://.*?/(.*)"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(avatar); + + // 如果没有匹配 + if (matcher.matches()) return avatar; + + // 匹配后返回内容 + return getObjectNameFullPath(avatar); + } + + /** + * 获取Minio全路径名,Object带有桶名称 + * + * @param objectName 对象名称 + * @return 全路径 + */ + public String getObjectNameFullPath(String objectName) { + String url = properties.getEndpointUrl(); + + return url + objectName; + } +} diff --git a/auth-core/src/main/java/cn/bunny/services/config/minio/MinioProperties.java b/auth-core/src/main/java/cn/bunny/services/minio/MinioProperties.java similarity index 98% rename from auth-core/src/main/java/cn/bunny/services/config/minio/MinioProperties.java rename to auth-core/src/main/java/cn/bunny/services/minio/MinioProperties.java index 3007dda..31ce8fd 100644 --- a/auth-core/src/main/java/cn/bunny/services/config/minio/MinioProperties.java +++ b/auth-core/src/main/java/cn/bunny/services/minio/MinioProperties.java @@ -1,4 +1,4 @@ -package cn.bunny.services.config.minio; +package cn.bunny.services.minio; import io.minio.BucketExistsArgs; import io.minio.MakeBucketArgs; diff --git a/auth-core/src/main/java/cn/bunny/services/config/minio/MinioUtil.java b/auth-core/src/main/java/cn/bunny/services/minio/MinioService.java similarity index 91% rename from auth-core/src/main/java/cn/bunny/services/config/minio/MinioUtil.java rename to auth-core/src/main/java/cn/bunny/services/minio/MinioService.java index 769721e..d915596 100644 --- a/auth-core/src/main/java/cn/bunny/services/config/minio/MinioUtil.java +++ b/auth-core/src/main/java/cn/bunny/services/minio/MinioService.java @@ -1,14 +1,15 @@ -package cn.bunny.services.config.minio; +package cn.bunny.services.minio; -import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.domain.common.constant.MinioConstant; import cn.bunny.services.domain.common.model.dto.file.MinioFilePath; +import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.exception.AuthCustomerException; import io.minio.*; import io.minio.messages.DeleteError; import io.minio.messages.DeleteObject; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; @@ -23,17 +24,14 @@ import java.util.UUID; * Minio操作工具类 简化操作步骤 * 自定义封装 */ -@Component @Slf4j -public class MinioUtil { - private final MinioProperties properties; +@Service +public class MinioService { + @Resource + private MinioProperties properties; - private final MinioClient minioClient; - - public MinioUtil(MinioProperties properties, MinioClient minioClient) { - this.properties = properties; - this.minioClient = minioClient; - } + @Resource + private MinioClient minioClient; /** * 获取Minio文件路径 @@ -112,18 +110,6 @@ public class MinioUtil { throw new AuthCustomerException(ResultCodeEnum.GET_BUCKET_EXCEPTION); } - /** - * 获取Minio全路径名,Object带有桶名称 - * - * @param objectName 对象名称 - * @return 全路径 - */ - public String getObjectNameFullPath(String objectName) { - String url = properties.getEndpointUrl(); - - return url + objectName; - } - /** * 上传文件 * @@ -211,4 +197,5 @@ public class MinioUtil { throw new AuthCustomerException("创建失败"); } } + } diff --git a/auth-core/src/main/java/cn/bunny/services/utils/IpUtil.java b/auth-core/src/main/java/cn/bunny/services/utils/IpUtil.java index 5fc9a62..63e06d1 100644 --- a/auth-core/src/main/java/cn/bunny/services/utils/IpUtil.java +++ b/auth-core/src/main/java/cn/bunny/services/utils/IpUtil.java @@ -1,6 +1,6 @@ package cn.bunny.services.utils; -import cn.bunny.services.domain.common.model.dto.ip.IpEntity; +import cn.bunny.services.domain.common.ip.IpEntity; import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; diff --git a/service/src/main/java/cn/bunny/services/security/service/CustomAuthorizationManagerServiceImpl.java b/service/src/main/java/cn/bunny/services/security/service/CustomAuthorizationManagerServiceImpl.java index b2d724d..15976a2 100644 --- a/service/src/main/java/cn/bunny/services/security/service/CustomAuthorizationManagerServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/security/service/CustomAuthorizationManagerServiceImpl.java @@ -1,7 +1,7 @@ package cn.bunny.services.security.service; import cn.bunny.services.context.BaseContext; -import cn.bunny.services.domain.common.model.dto.security.TokenInfo; +import cn.bunny.services.domain.common.security.TokenInfo; import cn.bunny.services.domain.common.model.vo.LoginVo; import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.security.exception.CustomAuthenticationException; diff --git a/service/src/main/java/cn/bunny/services/security/service/TokenValidationService.java b/service/src/main/java/cn/bunny/services/security/service/TokenValidationService.java index 4a321e4..33745d2 100644 --- a/service/src/main/java/cn/bunny/services/security/service/TokenValidationService.java +++ b/service/src/main/java/cn/bunny/services/security/service/TokenValidationService.java @@ -1,7 +1,7 @@ package cn.bunny.services.security.service; import cn.bunny.services.domain.common.constant.RedisUserConstant; -import cn.bunny.services.domain.common.model.dto.security.TokenInfo; +import cn.bunny.services.domain.common.security.TokenInfo; import cn.bunny.services.domain.common.model.vo.LoginVo; import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.security.exception.CustomAuthenticationException; diff --git a/service/src/main/java/cn/bunny/services/service/message/impl/MessageReceivedServiceImpl.java b/service/src/main/java/cn/bunny/services/service/message/impl/MessageReceivedServiceImpl.java index aabe99a..323a89f 100644 --- a/service/src/main/java/cn/bunny/services/service/message/impl/MessageReceivedServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/message/impl/MessageReceivedServiceImpl.java @@ -1,5 +1,8 @@ package cn.bunny.services.service.message.impl; +import cn.bunny.services.context.BaseContext; +import cn.bunny.services.domain.common.model.vo.result.PageResult; +import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.domain.system.message.dto.MessageReceivedDto; import cn.bunny.services.domain.system.message.dto.MessageReceivedUpdateDto; import cn.bunny.services.domain.system.message.dto.MessageUserDto; @@ -7,13 +10,10 @@ import cn.bunny.services.domain.system.message.entity.Message; import cn.bunny.services.domain.system.message.entity.MessageReceived; import cn.bunny.services.domain.system.message.vo.MessageReceivedWithMessageVo; import cn.bunny.services.domain.system.message.vo.MessageUserVo; -import cn.bunny.services.domain.common.model.vo.result.PageResult; -import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; -import cn.bunny.services.context.BaseContext; import cn.bunny.services.exception.AuthCustomerException; import cn.bunny.services.mapper.message.MessageReceivedMapper; +import cn.bunny.services.minio.MinioHelper; import cn.bunny.services.service.message.MessageReceivedService; -import cn.bunny.services.utils.system.UserUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -39,7 +39,7 @@ import java.util.List; public class MessageReceivedServiceImpl extends ServiceImpl implements MessageReceivedService { @Resource - private UserUtil userUtil; + private MinioHelper minioHelper; /** * 管理员管理用户消息接收分页查询 @@ -58,7 +58,7 @@ public class MessageReceivedServiceImpl extends ServiceImpl implements MessageService { - @Resource - private UserUtil userUtil; - @Resource private MessageReceivedMapper messageReceivedMapper; - @Resource private UserMapper userMapper; - @Resource private MessageReceivedService messageReceivedService; + @Resource + private MinioHelper minioHelper; /** * 分页查询发送消息 @@ -152,7 +149,7 @@ public class MessageServiceImpl extends ServiceImpl impl // 设置封面返回内容 String cover = dto.getCover(); - dto.setCover(userUtil.checkGetUserAvatar(cover)); + dto.setCover(minioHelper.getUserAvatar(cover)); // 先保存消息数据,之后拿到保存消息的id Message message = new Message(); @@ -198,7 +195,7 @@ public class MessageServiceImpl extends ServiceImpl impl // 设置封面返回内容 String cover = dto.getCover(); - dto.setCover(userUtil.checkGetUserAvatar(cover)); + dto.setCover(minioHelper.getUserAvatar(cover)); // 更新内容 Message message = new Message(); diff --git a/service/src/main/java/cn/bunny/services/service/system/UserLoginService.java b/service/src/main/java/cn/bunny/services/service/system/UserLoginService.java new file mode 100644 index 0000000..049d4d3 --- /dev/null +++ b/service/src/main/java/cn/bunny/services/service/system/UserLoginService.java @@ -0,0 +1,42 @@ +package cn.bunny.services.service.system; + +import cn.bunny.services.domain.common.model.vo.LoginVo; +import cn.bunny.services.domain.system.system.dto.user.LoginDto; +import cn.bunny.services.domain.system.system.dto.user.RefreshTokenDto; +import cn.bunny.services.domain.system.system.entity.AdminUser; +import cn.bunny.services.domain.system.system.vo.user.RefreshTokenVo; +import com.baomidou.mybatisplus.extension.service.IService; +import org.jetbrains.annotations.NotNull; + +public interface UserLoginService extends IService { + + /** + * 前台用户登录接口 + * + * @param loginDto 登录参数 + * @return 登录后结果返回 + */ + LoginVo login(LoginDto loginDto); + + /** + * 登录发送邮件验证码 + * + * @param email 邮箱 + */ + void sendLoginEmail(@NotNull String email); + + /** + * 刷新用户token + * + * @param dto 请求token + * @return 刷新token返回内容 + */ + @NotNull + RefreshTokenVo refreshToken(@NotNull RefreshTokenDto dto); + + /** + * * 退出登录 + */ + void logout(); + +} diff --git a/service/src/main/java/cn/bunny/services/service/system/UserService.java b/service/src/main/java/cn/bunny/services/service/system/UserService.java index 407fa3e..60a39c6 100644 --- a/service/src/main/java/cn/bunny/services/service/system/UserService.java +++ b/service/src/main/java/cn/bunny/services/service/system/UserService.java @@ -1,16 +1,16 @@ package cn.bunny.services.service.system; -import cn.bunny.services.domain.system.system.dto.user.*; +import cn.bunny.services.domain.common.model.vo.result.PageResult; +import cn.bunny.services.domain.system.system.dto.user.AdminUserAddDto; +import cn.bunny.services.domain.system.system.dto.user.AdminUserDto; +import cn.bunny.services.domain.system.system.dto.user.AdminUserUpdateByLocalUserDto; +import cn.bunny.services.domain.system.system.dto.user.AdminUserUpdateDto; import cn.bunny.services.domain.system.system.entity.AdminUser; import cn.bunny.services.domain.system.system.vo.user.AdminUserVo; -import cn.bunny.services.domain.system.system.vo.user.RefreshTokenVo; import cn.bunny.services.domain.system.system.vo.user.UserVo; -import cn.bunny.services.domain.common.model.vo.LoginVo; -import cn.bunny.services.domain.common.model.vo.result.PageResult; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import jakarta.validation.Valid; -import org.jetbrains.annotations.NotNull; import java.util.List; @@ -24,14 +24,6 @@ import java.util.List; */ public interface UserService extends IService { - /** - * 前台用户登录接口 - * - * @param loginDto 登录参数 - * @return 登录后结果返回 - */ - LoginVo login(LoginDto loginDto); - /** * * 获取用户信息列表 * @@ -60,27 +52,6 @@ public interface UserService extends IService { */ void deleteUserByAdmin(List ids); - /** - * 登录发送邮件验证码 - * - * @param email 邮箱 - */ - void sendLoginEmail(@NotNull String email); - - /** - * 刷新用户token - * - * @param dto 请求token - * @return 刷新token返回内容 - */ - @NotNull - RefreshTokenVo refreshToken(@NotNull RefreshTokenDto dto); - - /** - * * 退出登录 - */ - void logout(); - /** * * 获取用户信息 * diff --git a/service/src/main/java/cn/bunny/services/service/system/helper/UserLoginHelper.java b/service/src/main/java/cn/bunny/services/service/system/helper/UserLoginHelper.java new file mode 100644 index 0000000..dc7816b --- /dev/null +++ b/service/src/main/java/cn/bunny/services/service/system/helper/UserLoginHelper.java @@ -0,0 +1,189 @@ +package cn.bunny.services.service.system.helper; + +import cn.bunny.services.domain.common.constant.LocalDateTimeConstant; +import cn.bunny.services.domain.common.constant.RedisUserConstant; +import cn.bunny.services.domain.common.constant.UserConstant; +import cn.bunny.services.domain.common.model.vo.LoginVo; +import cn.bunny.services.domain.system.log.entity.UserLoginLog; +import cn.bunny.services.domain.system.system.entity.AdminUser; +import cn.bunny.services.domain.system.system.entity.Permission; +import cn.bunny.services.domain.system.system.entity.Role; +import cn.bunny.services.mapper.log.UserLoginLogMapper; +import cn.bunny.services.mapper.system.PermissionMapper; +import cn.bunny.services.mapper.system.RoleMapper; +import cn.bunny.services.mapper.system.UserMapper; +import cn.bunny.services.minio.MinioHelper; +import cn.bunny.services.utils.IpUtil; +import cn.bunny.services.utils.JwtTokenUtil; +import cn.bunny.services.utils.system.RoleUtil; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@Transactional +public class UserLoginHelper { + + @Resource + private RedisTemplate redisTemplate; + @Resource + private UserMapper userMapper; + @Resource + private UserLoginLogMapper userLoginLogMapper; + @Resource + private RoleMapper roleMapper; + @Resource + private PermissionMapper permissionMapper; + @Resource + private MinioHelper minioHelper; + + /** + * 构建用户登录返回对象(LoginVo) + * + *

主要处理流程:

+ *
    + *
  1. 参数校验:检查用户对象是否为空
  2. + *
  3. 权限处理: + *
      + *
    • 查询用户角色和权限数据
    • + *
    • 非管理员用户:从数据库加载权限信息
    • + *
    • 管理员用户:通过RoleUtil.checkAdmin()自动设置管理员权限
    • + *
    • (可选)对角色和权限列表去重
    • + *
    + *
  4. + *
  5. 信息装配: + *
      + *
    • 记录用户IP等访问信息
    • + *
    • 使用BeanUtils.copyProperties()复制用户基础属性
    • + *
    • 设置记住我功能及token过期时间
    • + *
    + *
  6. + *
  7. 缓存处理:将完整用户信息存入Redis
  8. + *
+ * + *

注意事项:

+ *
    + *
  • 属性复制操作放在流程最后,确保所有字段正确同步
  • + *
  • IP信息需要同时更新到用户实体和返回对象
  • + *
+ * + * @param user 用户实体对象(不可为空) + * @param readMeDay 记住我时长(单位:天) + * @return 完整的登录响应对象 + * @throws IllegalArgumentException 当用户对象为空时抛出 + */ + public LoginVo buildLoginUserVo(AdminUser user, long readMeDay) { + String username = user.getUsername(); + Long userId = user.getId(); + + // 用户角色 + List roles = new ArrayList<>(roleMapper.selectListByUserId(userId).stream().map(Role::getRoleCode).toList()); + + // 判断是否是 admin 如果是admin 赋予所有权限 + List permissions = new ArrayList<>(); + boolean isAdmin = RoleUtil.checkAdmin(roles, permissions, user); + if (!isAdmin) { + permissions = permissionMapper.selectListByUserId(userId).stream() + .map(Permission::getPowerCode) + .toList(); + } + // 为这两个去重 + permissions = permissions.stream().distinct().toList(); + roles = roles.stream().distinct().toList(); + + // 获取IP地址并更新用户登录信息, + String ipAddr = IpUtil.getCurrentUserIpAddress().getIpAddr(); + String ipRegion = IpUtil.getCurrentUserIpAddress().getIpRegion(); + // 设置用户IP地址,并更新用户信息 + user.setIpAddress(ipAddr); + user.setIpRegion(ipRegion); + userMapper.updateById(user); + + LoginVo loginVo = new LoginVo(); + BeanUtils.copyProperties(user, loginVo); + loginVo.setPersonDescription(user.getSummary()); + loginVo.setRoles(roles); + loginVo.setPermissions(permissions); + + // 使用用户名创建token + String token = JwtTokenUtil.createToken(userId, username, (int) readMeDay); + loginVo.setToken(token); + loginVo.setRefreshToken(token); + loginVo.setReadMeDay(readMeDay); + + // 计算过期时间,并格式化返回 + LocalDateTime localDateTime = LocalDateTime.now(); + LocalDateTime plusDay = localDateTime.plusDays(readMeDay); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD_HH_MM_SS_SLASH); + String expires = plusDay.format(dateTimeFormatter); + loginVo.setExpires(expires); + + // 设置用户头像 + String userAvatar = minioHelper.getUserAvatar(user.getAvatar()); + loginVo.setAvatar(userAvatar); + + // 将用户登录保存在用户登录日志表中 + setUserLoginLog(user, token, UserConstant.LOGIN); + + // 将信息保存在Redis中,一定要确保用户名是唯一的 + String loginInfoPrefix = RedisUserConstant.getAdminLoginInfoPrefix(username); + redisTemplate.opsForValue().set(loginInfoPrefix, loginVo, readMeDay, TimeUnit.DAYS); + + return loginVo; + } + + /** + * 设置用户登录日志内容 + *

+ * 该方法用于将管理员用户信息复制到用户登录日志对象中,同时处理特殊字段映射关系。 + *

+ * 实现说明: + * 1. 使用BeanUtils.copyProperties()复制属性时,会自动将AdminUser.id复制到UserLoginLog.id + * 2. 由于UserLoginLog实际需要的是userId字段而非id字段,需要特殊处理: + * - 先进行属性复制 + * - 然后将UserLoginLog.userId设置为AdminUser.id + * - 最后将UserLoginLog.id显式设为null(避免自动生成的id被覆盖) + * + * @param user 管理员用户实体对象,包含用户基本信息 + * @param token 本次登录/退出的认证令牌 + * @param type 操作类型(LOGIN-登录/LOGOUT-退出) + */ + public void setUserLoginLog(AdminUser user, String token, String type) { + UserLoginLog userLoginLog = new UserLoginLog(); + BeanUtils.copyProperties(user, userLoginLog); + userLoginLog.setUserId(user.getId()); + userLoginLog.setId(null); + userLoginLog.setToken(token); + userLoginLog.setType(type); + + // 当前请求request + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + HttpServletRequest request = requestAttributes.getRequest(); + + // 获取User-Agent + String userAgent = request.getHeader("User-Agent"); + userLoginLog.setUserAgent(userAgent); + + // 获取X-Requested-With + String xRequestedWith = request.getHeader("X-Requested-With"); + userLoginLog.setXRequestedWith(xRequestedWith); + } + + userLoginLogMapper.insert(userLoginLog); + } + +} diff --git a/service/src/main/java/cn/bunny/services/utils/login/DefaultLoginStrategy.java b/service/src/main/java/cn/bunny/services/service/system/helper/login/DefaultLoginStrategy.java similarity index 78% rename from service/src/main/java/cn/bunny/services/utils/login/DefaultLoginStrategy.java rename to service/src/main/java/cn/bunny/services/service/system/helper/login/DefaultLoginStrategy.java index 5f7aa9d..9962ed9 100644 --- a/service/src/main/java/cn/bunny/services/utils/login/DefaultLoginStrategy.java +++ b/service/src/main/java/cn/bunny/services/service/system/helper/login/DefaultLoginStrategy.java @@ -1,4 +1,4 @@ -package cn.bunny.services.utils.login; +package cn.bunny.services.service.system.helper.login; import cn.bunny.services.domain.system.system.dto.user.LoginDto; import cn.bunny.services.domain.system.system.entity.AdminUser; @@ -31,4 +31,16 @@ public class DefaultLoginStrategy implements LoginStrategy { queryWrapper.eq(AdminUser::getUsername, username); return userMapper.selectOne(queryWrapper); } + + /** + * 登录完成后的内容 + * + * @param loginDto 登录参数 + * @param adminUser 用户 + */ + @Override + public void authenticateAfter(LoginDto loginDto, AdminUser adminUser) { + + } + } \ No newline at end of file diff --git a/service/src/main/java/cn/bunny/services/utils/login/EmailLoginStrategy.java b/service/src/main/java/cn/bunny/services/service/system/helper/login/EmailLoginStrategy.java similarity index 83% rename from service/src/main/java/cn/bunny/services/utils/login/EmailLoginStrategy.java rename to service/src/main/java/cn/bunny/services/service/system/helper/login/EmailLoginStrategy.java index 113ecc0..1c7d3b8 100644 --- a/service/src/main/java/cn/bunny/services/utils/login/EmailLoginStrategy.java +++ b/service/src/main/java/cn/bunny/services/service/system/helper/login/EmailLoginStrategy.java @@ -1,9 +1,9 @@ -package cn.bunny.services.utils.login; +package cn.bunny.services.service.system.helper.login; import cn.bunny.services.domain.common.constant.RedisUserConstant; +import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.domain.system.system.dto.user.LoginDto; import cn.bunny.services.domain.system.system.entity.AdminUser; -import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.mapper.system.UserMapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.springframework.data.redis.core.RedisTemplate; @@ -53,4 +53,18 @@ public class EmailLoginStrategy implements LoginStrategy { queryWrapper.eq(AdminUser::getEmail, username); return userMapper.selectOne(queryWrapper); } + + /** + * 登录完成后的内容 + * + * @param loginDto 登录参数 + * @param adminUser 用户 + */ + @Override + public void authenticateAfter(LoginDto loginDto, AdminUser adminUser) { + // 将Redis中验证码删除 + String emailCodePrefix = RedisUserConstant.getAdminUserEmailCodePrefix(loginDto.getUsername()); + redisTemplate.delete(emailCodePrefix); + } + } diff --git a/service/src/main/java/cn/bunny/services/utils/login/LoginContext.java b/service/src/main/java/cn/bunny/services/service/system/helper/login/LoginContext.java similarity index 66% rename from service/src/main/java/cn/bunny/services/utils/login/LoginContext.java rename to service/src/main/java/cn/bunny/services/service/system/helper/login/LoginContext.java index 3b4aafa..2f67cae 100644 --- a/service/src/main/java/cn/bunny/services/utils/login/LoginContext.java +++ b/service/src/main/java/cn/bunny/services/service/system/helper/login/LoginContext.java @@ -1,9 +1,8 @@ -package cn.bunny.services.utils.login; +package cn.bunny.services.service.system.helper.login; import cn.bunny.services.domain.system.system.dto.user.LoginDto; import cn.bunny.services.domain.system.system.entity.AdminUser; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Map; @@ -13,11 +12,9 @@ import java.util.Map; public class LoginContext { private final Map strategies; - private final PasswordEncoder passwordEncoder; - public LoginContext(Map strategies, PasswordEncoder passwordEncoder) { + public LoginContext(Map strategies) { this.strategies = strategies; - this.passwordEncoder = passwordEncoder; } /** @@ -37,4 +34,16 @@ public class LoginContext { return strategy.authenticate(loginDto); } + + /** + * 登录完成后的内容 + * + * @param loginDto 登录参数 + */ + public void loginAfter(LoginDto loginDto, AdminUser adminUser) { + String type = loginDto.getType(); + LoginStrategy strategy = strategies.get(type); + + strategy.authenticateAfter(loginDto, adminUser); + } } \ No newline at end of file diff --git a/service/src/main/java/cn/bunny/services/utils/login/LoginStrategy.java b/service/src/main/java/cn/bunny/services/service/system/helper/login/LoginStrategy.java similarity index 59% rename from service/src/main/java/cn/bunny/services/utils/login/LoginStrategy.java rename to service/src/main/java/cn/bunny/services/service/system/helper/login/LoginStrategy.java index 84a4b12..29d832f 100644 --- a/service/src/main/java/cn/bunny/services/utils/login/LoginStrategy.java +++ b/service/src/main/java/cn/bunny/services/service/system/helper/login/LoginStrategy.java @@ -1,4 +1,4 @@ -package cn.bunny.services.utils.login; +package cn.bunny.services.service.system.helper.login; import cn.bunny.services.domain.system.system.dto.user.LoginDto; @@ -16,4 +16,12 @@ public interface LoginStrategy { * @return 鉴定身份验证 */ AdminUser authenticate(LoginDto loginDto); + + /** + * 登录完成后的内容 + * + * @param loginDto 登录参数 + * @param adminUser + */ + void authenticateAfter(LoginDto loginDto, AdminUser adminUser); } diff --git a/service/src/main/java/cn/bunny/services/utils/login/ReadMe.md b/service/src/main/java/cn/bunny/services/service/system/helper/login/ReadMe.md similarity index 100% rename from service/src/main/java/cn/bunny/services/utils/login/ReadMe.md rename to service/src/main/java/cn/bunny/services/service/system/helper/login/ReadMe.md diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/FilesServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/FilesServiceImpl.java index 9d182d6..844b912 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/FilesServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/FilesServiceImpl.java @@ -1,11 +1,9 @@ package cn.bunny.services.service.system.impl; -import cn.bunny.services.config.minio.MinioProperties; -import cn.bunny.services.config.minio.MinioUtil; import cn.bunny.services.context.BaseContext; +import cn.bunny.services.domain.common.model.dto.file.MinioFilePath; import cn.bunny.services.domain.common.model.vo.result.PageResult; import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; -import cn.bunny.services.domain.common.model.dto.file.MinioFilePath; import cn.bunny.services.domain.system.files.dto.FileUploadDto; import cn.bunny.services.domain.system.files.dto.FilesAddDto; import cn.bunny.services.domain.system.files.dto.FilesDto; @@ -15,6 +13,9 @@ import cn.bunny.services.domain.system.files.vo.FileInfoVo; import cn.bunny.services.domain.system.files.vo.FilesVo; import cn.bunny.services.exception.AuthCustomerException; import cn.bunny.services.mapper.system.FilesMapper; +import cn.bunny.services.minio.MinioHelper; +import cn.bunny.services.minio.MinioProperties; +import cn.bunny.services.minio.MinioService; import cn.bunny.services.service.system.FilesService; import cn.bunny.services.utils.FileUtil; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -59,11 +60,15 @@ public class FilesServiceImpl extends ServiceImpl implements private MinioProperties properties; @Resource - private MinioUtil minioUtil; + private MinioService minioService; + @Resource + private MinioHelper minioHelper; + @Resource private FilesMapper filesMapper; + /** * * 系统文件表 服务实现类 * @@ -92,7 +97,7 @@ public class FilesServiceImpl extends ServiceImpl implements public void addFiles(FilesAddDto dto) { List list = dto.getFiles().stream().map(file -> { try { - MinioFilePath minioFilePath = minioUtil.uploadObjectReturnFilePath(file, dto.getFilepath()); + MinioFilePath minioFilePath = minioService.uploadObjectReturnFilePath(file, dto.getFilepath()); Files files = new Files(); files.setFileType(file.getContentType()); @@ -124,7 +129,7 @@ public class FilesServiceImpl extends ServiceImpl implements if (file != null) { // 文件路径 String filePath = files.getFilepath().replace("/" + properties.getBucketName() + "/", ""); - minioUtil.updateFile(properties.getBucketName(), filePath, file); + minioService.updateFile(properties.getBucketName(), filePath, file); // 设置文件信息 files.setFileSize(file.getSize()); @@ -160,7 +165,7 @@ public class FilesServiceImpl extends ServiceImpl implements String filename = file.getOriginalFilename(); // 上传文件 - MinioFilePath minioFIlePath = minioUtil.uploadObjectReturnFilePath(file, type); + MinioFilePath minioFIlePath = minioService.uploadObjectReturnFilePath(file, type); String bucketNameFilepath = minioFIlePath.getBucketNameFilepath(); // 盘读研数据是否过大 @@ -183,7 +188,7 @@ public class FilesServiceImpl extends ServiceImpl implements .fileSize(fileSize) .fileType(contentType) .filename(filename) - .url(minioUtil.getObjectNameFullPath(bucketNameFilepath)) + .url(minioHelper.getObjectNameFullPath(bucketNameFilepath)) .build(); } @@ -205,7 +210,7 @@ public class FilesServiceImpl extends ServiceImpl implements }).toList(); // 删除目标文件 - minioUtil.removeObjects(list); + minioService.removeObjects(list); // 删除数据库内容 removeByIds(ids); @@ -229,7 +234,7 @@ public class FilesServiceImpl extends ServiceImpl implements String filepath = files.getFilepath(); int end = filepath.indexOf("/", 1); filepath = filepath.substring(end + 1); - byte[] bytes = minioUtil.getBucketObjectByte(filepath); + byte[] bytes = minioService.getBucketObjectByte(filepath); // 设置响应头 HttpHeaders headers = new HttpHeaders(); diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/RoleServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/RoleServiceImpl.java index e01cc09..db8d726 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/RoleServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/RoleServiceImpl.java @@ -94,7 +94,7 @@ public class RoleServiceImpl extends ServiceImpl implements Ro * @return 所有角色列表 */ @Override - @Cacheable(cacheNames = "role", key = "'allRole'", cacheManager = "cacheManagerWithMouth") + @Cacheable(cacheNames = "role", key = "'roleList'", cacheManager = "cacheManagerWithMouth") public List roleList() { return list().stream().map(role -> { RoleVo roleVo = new RoleVo(); @@ -155,7 +155,7 @@ public class RoleServiceImpl extends ServiceImpl implements Ro * @param file Excel文件 */ @Override - @CacheEvict(cacheNames = "role", key = "'allRole'", beforeInvocation = true) + @CacheEvict(cacheNames = "role", key = "'roleList'", beforeInvocation = true) public void updateRoleByFile(MultipartFile file) { if (file == null) { throw new AuthCustomerException(ResultCodeEnum.REQUEST_IS_EMPTY); @@ -176,7 +176,7 @@ public class RoleServiceImpl extends ServiceImpl implements Ro * @param dto 角色添加 */ @Override - @CacheEvict(cacheNames = "role", key = "'allRole'", beforeInvocation = true) + @CacheEvict(cacheNames = "role", key = "'roleList'", beforeInvocation = true) public void addRole(@Valid RoleAddDto dto) { Role role = new Role(); BeanUtils.copyProperties(dto, role); @@ -189,7 +189,7 @@ public class RoleServiceImpl extends ServiceImpl implements Ro * @param dto 角色更新 */ @Override - @CacheEvict(cacheNames = "role", key = "'allRole'", beforeInvocation = true) + @CacheEvict(cacheNames = "role", key = "'roleList'", beforeInvocation = true) public void updateRole(@Valid RoleUpdateDto dto) { // 查询更新的角色是否存在 List roleList = list(Wrappers.lambdaQuery().eq(Role::getId, dto.getId())); @@ -213,7 +213,7 @@ public class RoleServiceImpl extends ServiceImpl implements Ro * @param ids 删除id列表 */ @Override - @CacheEvict(cacheNames = "role", key = "'allRole'", beforeInvocation = true) + @CacheEvict(cacheNames = "role", key = "'roleList'", beforeInvocation = true) public void deleteRole(List ids) { // 判断数据请求是否为空 if (ids.isEmpty()) throw new AuthCustomerException(ResultCodeEnum.REQUEST_IS_EMPTY); diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/UserLoginServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/UserLoginServiceImpl.java new file mode 100644 index 0000000..9b2b6b9 --- /dev/null +++ b/service/src/main/java/cn/bunny/services/service/system/impl/UserLoginServiceImpl.java @@ -0,0 +1,184 @@ +package cn.bunny.services.service.system.impl; + +import cn.bunny.services.context.BaseContext; +import cn.bunny.services.domain.common.constant.RedisUserConstant; +import cn.bunny.services.domain.common.constant.UserConstant; +import cn.bunny.services.domain.common.enums.EmailTemplateEnums; +import cn.bunny.services.domain.common.enums.LoginEnums; +import cn.bunny.services.domain.common.model.vo.LoginVo; +import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; +import cn.bunny.services.domain.system.email.entity.EmailTemplate; +import cn.bunny.services.domain.system.system.dto.user.LoginDto; +import cn.bunny.services.domain.system.system.dto.user.RefreshTokenDto; +import cn.bunny.services.domain.system.system.entity.AdminUser; +import cn.bunny.services.domain.system.system.vo.user.RefreshTokenVo; +import cn.bunny.services.exception.AuthCustomerException; +import cn.bunny.services.mapper.configuration.EmailTemplateMapper; +import cn.bunny.services.mapper.system.UserMapper; +import cn.bunny.services.service.system.UserLoginService; +import cn.bunny.services.service.system.helper.UserLoginHelper; +import cn.bunny.services.service.system.helper.login.DefaultLoginStrategy; +import cn.bunny.services.service.system.helper.login.EmailLoginStrategy; +import cn.bunny.services.service.system.helper.login.LoginContext; +import cn.bunny.services.service.system.helper.login.LoginStrategy; +import cn.bunny.services.utils.IpUtil; +import cn.bunny.services.utils.JwtTokenUtil; +import cn.bunny.services.utils.email.ConcreteSenderEmailTemplate; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.CircleCaptcha; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.annotation.Resource; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +@Service +public class UserLoginServiceImpl extends ServiceImpl implements UserLoginService { + @Resource + private UserLoginHelper userloginHelper; + @Resource + private RedisTemplate redisTemplate; + @Resource + private PasswordEncoder passwordEncoder; + @Resource + private UserMapper userMapper; + @Resource + private EmailTemplateMapper emailTemplateMapper; + @Resource + private ConcreteSenderEmailTemplate concreteSenderEmailTemplate; + + /** + * 前台用户登录接口 + * 这里不用判断用户是否为空,因为在登录时已经校验过了 + *

+ * 抛出异常使用自带的 UsernameNotFoundException 或者自己封装
+ * 但是这两个效果传入参数都是一样的,所以全部使用 UsernameNotFoundException + *

+ * + * @param loginDto 登录参数 + * @return 登录后结果返回 + */ + @Override + public LoginVo login(LoginDto loginDto) { + // 初始化所有策略(可扩展) + HashMap loginStrategyHashMap = new HashMap<>(); + // 默认的登录方式 + loginStrategyHashMap.put(LoginEnums.default_STRATEGY.getValue(), new DefaultLoginStrategy(userMapper)); + // 注册邮箱 + loginStrategyHashMap.put(LoginEnums.EMAIL_STRATEGY.getValue(), new EmailLoginStrategy(redisTemplate, userMapper)); + + // 使用登录上下文调用登录策略 + LoginContext loginContext = new LoginContext(loginStrategyHashMap); + AdminUser user = loginContext.executeStrategy(loginDto); + + // 验证登录逻辑 + if (user == null) throw new UsernameNotFoundException(ResultCodeEnum.USER_IS_EMPTY.getMessage()); + + // 数据库密码 + String dbPassword = user.getPassword(); + String password = loginDto.getPassword(); + if (!passwordEncoder.matches(password, dbPassword)) { + throw new UsernameNotFoundException(ResultCodeEnum.LOGIN_ERROR.getMessage()); + } + + // 判断用户是否禁用 + if (user.getStatus()) { + throw new UsernameNotFoundException(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED.getMessage()); + } + + // 登录结束后的操作 + loginContext.loginAfter(loginDto, user); + + user.setUpdateUser(user.getId()); + user.setCreateUser(user.getId()); + return userloginHelper.buildLoginUserVo(user, loginDto.getReadMeDay()); + } + + /** + * 登录发送邮件验证码 + * + * @param email 邮箱 + */ + @Override + public void sendLoginEmail(@NotNull String email) { + // 查询验证码邮件模板 + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(EmailTemplate::getIsDefault, true); + lambdaQueryWrapper.eq(EmailTemplate::getType, EmailTemplateEnums.VERIFICATION_CODE.getType()); + EmailTemplate emailTemplate = emailTemplateMapper.selectOne(lambdaQueryWrapper); + + // 生成验证码 + CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 2); + String emailCode = captcha.getCode(); + + // 需要替换模板内容 + HashMap hashMap = new HashMap<>(); + hashMap.put("#title#", "BunnyAdmin"); + hashMap.put("#verifyCode#", emailCode); + hashMap.put("#expires#", 15); + hashMap.put("#sendEmailUser#", emailTemplate.getEmailUser()); + hashMap.put("#companyName#", "BunnyAdmin"); + + // 发送邮件 + concreteSenderEmailTemplate.sendEmailTemplate(email, emailTemplate, hashMap); + + // 在Redis中存储验证码 + redisTemplate.opsForValue().set(RedisUserConstant.getAdminUserEmailCodePrefix(email), emailCode, RedisUserConstant.REDIS_EXPIRATION_TIME, TimeUnit.MINUTES); + } + + /** + * 刷新用户token + * + * @param dto 请求token + * @return 刷新token返回内容 + */ + @NotNull + @Override + public RefreshTokenVo refreshToken(@NotNull RefreshTokenDto dto) { + Long userId = JwtTokenUtil.getUserId(dto.getRefreshToken()); + AdminUser adminUser = getOne(Wrappers.lambdaQuery().eq(AdminUser::getId, userId)); + + // 用户存在且没有禁用 + if (adminUser == null) throw new AuthCustomerException(ResultCodeEnum.USER_IS_EMPTY); + if (adminUser.getStatus()) throw new AuthCustomerException(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED); + + LoginVo buildUserVo = userloginHelper.buildLoginUserVo(adminUser, dto.getReadMeDay()); + RefreshTokenVo refreshTokenVo = new RefreshTokenVo(); + BeanUtils.copyProperties(buildUserVo, refreshTokenVo); + + return refreshTokenVo; + } + + /** + * 退出登录 + */ + @Override + public void logout() { + // 获取上下文对象中的用户ID和用户token + LoginVo loginVo = BaseContext.getLoginVo(); + String token = loginVo.getToken(); + Long userId = BaseContext.getUserId(); + + // 获取IP地址 + String ipAddr = IpUtil.getCurrentUserIpAddress().getIpAddr(); + String ipRegion = IpUtil.getCurrentUserIpAddress().getIpRegion(); + + // 查询用户信息 + AdminUser adminUser = getOne(Wrappers.lambdaQuery().eq(AdminUser::getId, userId)); + adminUser.setIpAddress(ipAddr); + adminUser.setIpRegion(ipRegion); + userloginHelper.setUserLoginLog(adminUser, token, UserConstant.LOGOUT); + + // 删除Redis中用户信息 + String loginInfoPrefix = RedisUserConstant.getAdminLoginInfoPrefix(adminUser.getUsername()); + redisTemplate.delete(loginInfoPrefix); + } +} diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/UserRoleServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/UserRoleServiceImpl.java index 4a63035..05fe733 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/UserRoleServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/UserRoleServiceImpl.java @@ -1,17 +1,17 @@ package cn.bunny.services.service.system.impl; +import cn.bunny.services.context.BaseContext; import cn.bunny.services.domain.common.constant.RedisUserConstant; +import cn.bunny.services.domain.common.model.vo.LoginVo; +import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.domain.system.system.dto.user.AssignRolesToUsersDto; import cn.bunny.services.domain.system.system.entity.AdminUser; import cn.bunny.services.domain.system.system.entity.UserRole; -import cn.bunny.services.domain.common.model.vo.LoginVo; -import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; -import cn.bunny.services.context.BaseContext; import cn.bunny.services.exception.AuthCustomerException; import cn.bunny.services.mapper.system.UserMapper; import cn.bunny.services.mapper.system.UserRoleMapper; import cn.bunny.services.service.system.UserRoleService; -import cn.bunny.services.utils.system.UserUtil; +import cn.bunny.services.service.system.helper.UserLoginHelper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import jakarta.annotation.Resource; @@ -35,14 +35,11 @@ import java.util.concurrent.TimeUnit; public class UserRoleServiceImpl extends ServiceImpl implements UserRoleService { @Resource - private UserRoleMapper userRoleMapper; - + private UserLoginHelper userloginHelper; @Resource - private UserUtil userUtil; - + private UserRoleMapper userRoleMapper; @Resource private UserMapper userMapper; - @Resource private RedisTemplate redisTemplate; @@ -94,7 +91,7 @@ public class UserRoleServiceImpl extends ServiceImpl i // 重新设置Redis中的用户存储信息vo对象 String username = adminUser.getUsername(); - loginVo = userUtil.buildLoginUserVo(adminUser, readMeDay); + loginVo = userloginHelper.buildLoginUserVo(adminUser, readMeDay); redisTemplate.opsForValue().set(RedisUserConstant.getAdminLoginInfoPrefix(username), loginVo, readMeDay, TimeUnit.DAYS); } } diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/UserServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/UserServiceImpl.java index 05165bc..072da50 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/UserServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/UserServiceImpl.java @@ -1,40 +1,34 @@ package cn.bunny.services.service.system.impl; import cn.bunny.services.context.BaseContext; +import cn.bunny.services.domain.common.constant.MinioConstant; import cn.bunny.services.domain.common.constant.RedisUserConstant; -import cn.bunny.services.domain.common.enums.EmailTemplateEnums; -import cn.bunny.services.domain.common.enums.LoginEnums; -import cn.bunny.services.domain.common.model.vo.LoginVo; +import cn.bunny.services.domain.common.constant.UserConstant; import cn.bunny.services.domain.common.model.vo.result.PageResult; import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; -import cn.bunny.services.domain.system.email.entity.EmailTemplate; +import cn.bunny.services.domain.system.files.dto.FileUploadDto; +import cn.bunny.services.domain.system.files.vo.FileInfoVo; import cn.bunny.services.domain.system.log.entity.UserLoginLog; -import cn.bunny.services.domain.system.system.dto.user.*; +import cn.bunny.services.domain.system.system.dto.user.AdminUserAddDto; +import cn.bunny.services.domain.system.system.dto.user.AdminUserDto; +import cn.bunny.services.domain.system.system.dto.user.AdminUserUpdateByLocalUserDto; +import cn.bunny.services.domain.system.system.dto.user.AdminUserUpdateDto; import cn.bunny.services.domain.system.system.entity.AdminUser; import cn.bunny.services.domain.system.system.entity.Role; import cn.bunny.services.domain.system.system.entity.UserDept; import cn.bunny.services.domain.system.system.views.ViewUserDept; import cn.bunny.services.domain.system.system.vo.user.AdminUserVo; -import cn.bunny.services.domain.system.system.vo.user.RefreshTokenVo; import cn.bunny.services.domain.system.system.vo.user.UserVo; import cn.bunny.services.exception.AuthCustomerException; -import cn.bunny.services.mapper.configuration.EmailTemplateMapper; import cn.bunny.services.mapper.log.UserLoginLogMapper; import cn.bunny.services.mapper.system.RoleMapper; import cn.bunny.services.mapper.system.UserDeptMapper; import cn.bunny.services.mapper.system.UserMapper; import cn.bunny.services.mapper.system.UserRoleMapper; +import cn.bunny.services.minio.MinioHelper; +import cn.bunny.services.service.system.FilesService; import cn.bunny.services.service.system.UserService; -import cn.bunny.services.utils.IpUtil; -import cn.bunny.services.utils.JwtTokenUtil; -import cn.bunny.services.utils.email.ConcreteSenderEmailTemplate; -import cn.bunny.services.utils.login.DefaultLoginStrategy; -import cn.bunny.services.utils.login.EmailLoginStrategy; -import cn.bunny.services.utils.login.LoginContext; -import cn.bunny.services.utils.login.LoginStrategy; -import cn.bunny.services.utils.system.UserUtil; -import cn.hutool.captcha.CaptchaUtil; -import cn.hutool.captcha.CircleCaptcha; +import cn.bunny.services.service.system.helper.UserLoginHelper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -42,18 +36,15 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import jakarta.annotation.Resource; import jakarta.validation.Valid; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.BeanUtils; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; -import java.util.HashMap; import java.util.List; -import java.util.concurrent.TimeUnit; /** @@ -67,150 +58,23 @@ import java.util.concurrent.TimeUnit; public class UserServiceImpl extends ServiceImpl implements UserService { @Resource - private UserUtil userUtil; + private UserLoginHelper userloginHelper; @Resource - private ConcreteSenderEmailTemplate concreteSenderEmailTemplate; + private PasswordEncoder passwordEncoder; @Resource private RedisTemplate redisTemplate; @Resource + private FilesService filesService; + @Resource private UserDeptMapper userDeptMapper; @Resource private UserRoleMapper userRoleMapper; @Resource private UserLoginLogMapper userLoginLogMapper; @Resource - private EmailTemplateMapper emailTemplateMapper; - @Resource private RoleMapper roleMapper; @Resource - private UserMapper userMapper; - @Resource - private PasswordEncoder passwordEncoder; - - /** - * 前台用户登录接口 - * 这里不用判断用户是否为空,因为在登录时已经校验过了 - *

- * 抛出异常使用自带的 UsernameNotFoundException 或者自己封装
- * 但是这两个效果传入参数都是一样的,所以全部使用 UsernameNotFoundException - *

- * - * @param loginDto 登录参数 - * @return 登录后结果返回 - */ - @Override - public LoginVo login(LoginDto loginDto) { - Long readMeDay = loginDto.getReadMeDay(); - - // 初始化所有策略(可扩展) - HashMap loginStrategyHashMap = new HashMap<>(); - // 默认的登录方式 - loginStrategyHashMap.put(LoginEnums.default_STRATEGY.getValue(), new DefaultLoginStrategy(userMapper)); - // 注册邮箱 - loginStrategyHashMap.put(LoginEnums.EMAIL_STRATEGY.getValue(), new EmailLoginStrategy(redisTemplate, userMapper)); - - // 使用登录上下文调用登录策略 - LoginContext loginContext = new LoginContext(loginStrategyHashMap, passwordEncoder); - AdminUser user = loginContext.executeStrategy(loginDto); - - // 验证登录逻辑 - if (user == null) throw new UsernameNotFoundException(ResultCodeEnum.USER_IS_EMPTY.getMessage()); - - // 数据库密码 - String dbPassword = user.getPassword(); - - // 用户登录密码 - String password = loginDto.getPassword(); - - if (!passwordEncoder.matches(password, dbPassword)) { - throw new UsernameNotFoundException(ResultCodeEnum.LOGIN_ERROR.getMessage()); - } - - // 判断用户是否禁用 - if (user.getStatus()) { - throw new UsernameNotFoundException(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED.getMessage()); - } - - return userUtil.buildLoginUserVo(user, readMeDay); - } - - /** - * 登录发送邮件验证码 - * - * @param email 邮箱 - */ - @Override - public void sendLoginEmail(@NotNull String email) { - // 查询验证码邮件模板 - LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); - lambdaQueryWrapper.eq(EmailTemplate::getIsDefault, true); - lambdaQueryWrapper.eq(EmailTemplate::getType, EmailTemplateEnums.VERIFICATION_CODE.getType()); - EmailTemplate emailTemplate = emailTemplateMapper.selectOne(lambdaQueryWrapper); - - // 生成验证码 - CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 2); - String emailCode = captcha.getCode(); - - // 需要替换模板内容 - HashMap hashMap = new HashMap<>(); - hashMap.put("#title#", "BunnyAdmin"); - hashMap.put("#verifyCode#", emailCode); - hashMap.put("#expires#", 15); - hashMap.put("#sendEmailUser#", emailTemplate.getEmailUser()); - hashMap.put("#companyName#", "BunnyAdmin"); - - // 发送邮件 - concreteSenderEmailTemplate.sendEmailTemplate(email, emailTemplate, hashMap); - - // 在Redis中存储验证码 - redisTemplate.opsForValue().set(RedisUserConstant.getAdminUserEmailCodePrefix(email), emailCode, RedisUserConstant.REDIS_EXPIRATION_TIME, TimeUnit.MINUTES); - } - - /** - * 刷新用户token - * - * @param dto 请求token - * @return 刷新token返回内容 - */ - @NotNull - @Override - public RefreshTokenVo refreshToken(@NotNull RefreshTokenDto dto) { - Long userId = JwtTokenUtil.getUserId(dto.getRefreshToken()); - AdminUser adminUser = getOne(Wrappers.lambdaQuery().eq(AdminUser::getId, userId)); - - // 用户存在且没有禁用 - if (adminUser == null) throw new AuthCustomerException(ResultCodeEnum.USER_IS_EMPTY); - if (adminUser.getStatus()) throw new AuthCustomerException(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED); - - LoginVo buildUserVo = userUtil.buildLoginUserVo(adminUser, dto.getReadMeDay()); - RefreshTokenVo refreshTokenVo = new RefreshTokenVo(); - BeanUtils.copyProperties(buildUserVo, refreshTokenVo); - - return refreshTokenVo; - } - - /** - * 退出登录 - */ - @Override - public void logout() { - // 获取上下文对象中的用户ID和用户token - LoginVo loginVo = BaseContext.getLoginVo(); - String token = loginVo.getToken(); - Long userId = BaseContext.getUserId(); - - // 获取IP地址 - String ipAddr = IpUtil.getCurrentUserIpAddress().getIpAddr(); - String ipRegion = IpUtil.getCurrentUserIpAddress().getIpRegion(); - - // 查询用户信息 - AdminUser adminUser = getOne(Wrappers.lambdaQuery().eq(AdminUser::getId, userId)); - UserLoginLog userLoginLog = userUtil.setUserLoginLog(adminUser, token, ipAddr, ipRegion, "logout"); - userLoginLogMapper.insert(userLoginLog); - - // 删除Redis中用户信息 - redisTemplate.delete(RedisUserConstant.getAdminLoginInfoPrefix(adminUser.getUsername())); - } + private MinioHelper minioHelper; /** * * 获取用户信息 @@ -226,18 +90,19 @@ public class UserServiceImpl extends ServiceImpl implemen // 用户是否存在 if (user == null) throw new AuthCustomerException(ResultCodeEnum.DATA_NOT_EXIST); - // 用户头像 String avatar = user.getAvatar(); + UserVo userVo = new UserVo(); BeanUtils.copyProperties(user, userVo); - userVo.setAvatar(userUtil.checkGetUserAvatar(avatar)); + String userAvatar = minioHelper.getUserAvatar(avatar); + userVo.setAvatar(userAvatar); return userVo; } /** - * * 强制退出 + * 强制退出 * * @param id 用户id */ @@ -251,11 +116,9 @@ public class UserServiceImpl extends ServiceImpl implemen // 将用户登录保存在用户登录日志表中 UserLoginLog userLoginLog = new UserLoginLog(); + BeanUtils.copyProperties(adminUser, userLoginLog); userLoginLog.setUserId(adminUser.getId()); - userLoginLog.setIpAddress(adminUser.getIpAddress()); - userLoginLog.setIpRegion(adminUser.getIpRegion()); - userLoginLog.setToken(null); - userLoginLog.setType("forcedOffline"); + userLoginLog.setType(UserConstant.FORCE_LOGOUT); userLoginLogMapper.insert(userLoginLog); // 删除Redis中用户信息 @@ -304,8 +167,9 @@ public class UserServiceImpl extends ServiceImpl implemen AdminUser user = getOne(Wrappers.lambdaQuery().eq(AdminUser::getId, userId)); if (user == null) throw new AuthCustomerException(ResultCodeEnum.USER_IS_EMPTY); - // 检查用户头像 - dto.setAvatar(userUtil.checkPostUserAvatar(dto.getAvatar())); + // 检查用户头像,因为更新用户信息会带着用户之前的信息,如果没有更新头像,前端显示的http:xxx + String userAvatar = minioHelper.formatUserAvatar(dto.getAvatar()); + dto.setAvatar(userAvatar); // 更新用户 AdminUser adminUser = new AdminUser(); @@ -315,7 +179,7 @@ public class UserServiceImpl extends ServiceImpl implemen // 重新生成用户信息到Redis中 BeanUtils.copyProperties(dto, user); - userUtil.buildUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME); + userloginHelper.buildLoginUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME); } /** @@ -363,7 +227,7 @@ public class UserServiceImpl extends ServiceImpl implemen List voList = page.getRecords().stream() .map(adminUser -> { // 如果存在用户头像,则设置用户头像 - String avatar = userUtil.checkGetUserAvatar(adminUser.getAvatar()); + String avatar = minioHelper.getUserAvatar(adminUser.getAvatar()); AdminUserVo adminUserVo = new AdminUserVo(); BeanUtils.copyProperties(adminUser, adminUserVo); @@ -442,15 +306,18 @@ public class UserServiceImpl extends ServiceImpl implemen } // 更新头像 - userUtil.uploadAvatarByAdmin(dto, adminUser); + uploadAvatarByAdmin(dto, adminUser); // 构建用户返回信息,同步到redis - userUtil.buildUserVo(adminUser, RedisUserConstant.REDIS_EXPIRATION_TIME); + userloginHelper.buildLoginUserVo(adminUser, RedisUserConstant.REDIS_EXPIRATION_TIME); // 更新密码,放在最后,如果更新密码就将密码删除 - userUtil.updateUserPasswordByAdmin(adminUser); + updateUserPasswordByAdmin(adminUser); updateById(adminUser); + // 删除Redis中用户信息 + String loginInfoPrefix = RedisUserConstant.getAdminLoginInfoPrefix(adminUser.getUsername()); + redisTemplate.delete(loginInfoPrefix); } /** @@ -471,11 +338,56 @@ public class UserServiceImpl extends ServiceImpl implemen // 逻辑删除 removeByIds(ids); - // 删除用 也要删除对应的 角色和部门,但是如果做的时物理删除就不需要,因为数据库中设置了外键检查,如果删除用户,相关表也会删除 // 删除部门相关 userDeptMapper.deleteBatchIdsByUserIds(ids); // 删除用户角色相关 userRoleMapper.deleteBatchIdsByUserIds(ids); } + + /** + * * 管理员修改管理员用户密码 + * + * @param adminUser 管理员用户修改密码 + */ + private void updateUserPasswordByAdmin(AdminUser adminUser) { + Long userId = adminUser.getId(); + String password = adminUser.getPassword(); + + // 密码更新是否存在 + if (!StringUtils.hasText(password)) return; + + // 对密码加密 + String encode = passwordEncoder.encode(password); + + // 判断新密码是否与旧密码相同 + if (adminUser.getPassword().equals(encode)) { + throw new AuthCustomerException(ResultCodeEnum.UPDATE_NEW_PASSWORD_SAME_AS_OLD_PASSWORD); + } + + // 更新用户密码 + adminUser.setPassword(encode); + adminUser.setId(userId); + } + + /** + * 管理员上传用户头像 + * + * @param dto 管理员用户修改头像 + */ + private void uploadAvatarByAdmin(AdminUserUpdateDto dto, AdminUser adminUser) { + MultipartFile avatar = dto.getAvatar(); + Long userId = dto.getId(); + + // 上传头像是否存在 + if (avatar == null) return; + + // 上传头像 + FileUploadDto uploadDto = FileUploadDto.builder().file(avatar).type(MinioConstant.avatar).build(); + FileInfoVo fileInfoVo = filesService.upload(uploadDto); + + // 更新用户 + adminUser.setId(userId); + adminUser.setAvatar(fileInfoVo.getFilepath()); + } } diff --git a/service/src/main/java/cn/bunny/services/utils/email/AbstractSenderEmailTemplate.java b/service/src/main/java/cn/bunny/services/utils/email/AbstractSenderEmailTemplate.java index a07a0bc..923f927 100644 --- a/service/src/main/java/cn/bunny/services/utils/email/AbstractSenderEmailTemplate.java +++ b/service/src/main/java/cn/bunny/services/utils/email/AbstractSenderEmailTemplate.java @@ -1,12 +1,12 @@ package cn.bunny.services.utils.email; -import cn.bunny.services.config.mail.MailSenderConfiguration; -import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.domain.common.model.dto.email.EmailSend; import cn.bunny.services.domain.common.model.dto.email.EmailSendInit; +import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.domain.system.email.entity.EmailTemplate; import cn.bunny.services.domain.system.email.entity.EmailUsers; import cn.bunny.services.exception.AuthCustomerException; +import cn.bunny.services.mail.MailSenderConfiguration; import cn.bunny.services.mapper.configuration.EmailUsersMapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jakarta.mail.MessagingException; diff --git a/service/src/main/java/cn/bunny/services/utils/system/RoleUtil.java b/service/src/main/java/cn/bunny/services/utils/system/RoleUtil.java index e918b7e..0e17865 100644 --- a/service/src/main/java/cn/bunny/services/utils/system/RoleUtil.java +++ b/service/src/main/java/cn/bunny/services/utils/system/RoleUtil.java @@ -5,6 +5,7 @@ import cn.bunny.services.domain.common.constant.RedisUserConstant; import cn.bunny.services.domain.common.constant.SecurityConfigConstant; import cn.bunny.services.domain.system.system.entity.AdminUser; import cn.bunny.services.mapper.system.UserMapper; +import cn.bunny.services.service.system.helper.UserLoginHelper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jakarta.annotation.Resource; import org.springframework.data.redis.core.RedisTemplate; @@ -22,15 +23,30 @@ public class RoleUtil { private UserMapper userMapper; @Resource - private UserUtil userUtil; + private UserLoginHelper userloginHelper; /** - * 判断是否是管理员 + * 判断用户是否具有管理员权限 + *

+ * 管理员判定规则: + * 1. 系统默认管理员:用户ID为1的预设管理员账号(用户名为Administrator) + * 2. 角色授权管理员:用户角色列表包含"admin"角色的账号 + *

+ * 权限控制说明: + * - 管理员权限用于前端按钮级权限控制,支持以下通配符格式: + * - "*::*::*":全部模块的全部操作权限 + * - "*::*" :指定模块的全部操作权限 + * - "*" :基础通配权限 + * - 若无细粒度按钮控制需求,可不设置具体权限 + * 详细查看 + * + * 查看前端权限设置 + * * - * @param roleList 角色代码列表 - * @param permissions 权限列表 - * @param adminUser 用户信息 - * @return 是否是管理员 + * @param roleList 用户角色编码列表(可能包含"admin"角色) + * @param permissions 用户权限列表(用于前端按钮控制) + * @param adminUser 用户实体对象(需包含userId字段) + * @return true-是管理员,false-非管理员 */ public static boolean checkAdmin(List roleList, List permissions, AdminUser adminUser) { // 判断是否是超级管理员 @@ -87,6 +103,6 @@ public class RoleUtil { Object object = redisTemplate.opsForValue().get(adminLoginInfoPrefix); return object != null; }) - .forEach(user -> userUtil.buildUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME)); + .forEach(user -> userloginHelper.buildLoginUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME)); } } diff --git a/service/src/main/java/cn/bunny/services/utils/system/UserUtil.java b/service/src/main/java/cn/bunny/services/utils/system/UserUtil.java deleted file mode 100644 index b20e2e5..0000000 --- a/service/src/main/java/cn/bunny/services/utils/system/UserUtil.java +++ /dev/null @@ -1,316 +0,0 @@ -package cn.bunny.services.utils.system; - -import cn.bunny.services.config.minio.MinioUtil; -import cn.bunny.services.domain.common.constant.LocalDateTimeConstant; -import cn.bunny.services.domain.common.constant.MinioConstant; -import cn.bunny.services.domain.common.constant.RedisUserConstant; -import cn.bunny.services.domain.common.constant.UserConstant; -import cn.bunny.services.domain.common.model.vo.LoginVo; -import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; -import cn.bunny.services.domain.system.files.dto.FileUploadDto; -import cn.bunny.services.domain.system.files.vo.FileInfoVo; -import cn.bunny.services.domain.system.log.entity.UserLoginLog; -import cn.bunny.services.domain.system.system.dto.user.AdminUserUpdateDto; -import cn.bunny.services.domain.system.system.entity.AdminUser; -import cn.bunny.services.domain.system.system.entity.Permission; -import cn.bunny.services.domain.system.system.entity.Role; -import cn.bunny.services.exception.AuthCustomerException; -import cn.bunny.services.mapper.log.UserLoginLogMapper; -import cn.bunny.services.mapper.system.PermissionMapper; -import cn.bunny.services.mapper.system.RoleMapper; -import cn.bunny.services.mapper.system.UserMapper; -import cn.bunny.services.service.system.FilesService; -import cn.bunny.services.utils.IpUtil; -import cn.bunny.services.utils.JwtTokenUtil; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.beans.BeanUtils; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -@Component -public class UserUtil { - - @Resource - private MinioUtil minioUtil; - @Resource - private PermissionMapper permissionMapper; - @Resource - private RoleMapper roleMapper; - @Resource - private UserMapper userMapper; - @Resource - private UserLoginLogMapper userLoginLogMapper; - @Resource - private RedisTemplate redisTemplate; - @Resource - private PasswordEncoder passwordEncoder; - @Resource - private FilesService filesService; - - /** - * 构建登录用户返回对象 - * - * @param user 用户 - * @param readMeDay 保存登录信息时间 - * @return 登录信息 - */ - @Transactional(rollbackFor = AuthCustomerException.class) - public LoginVo buildLoginUserVo(AdminUser user, long readMeDay) { - Long userId = user.getId(); - String email = user.getEmail(); - String username = user.getUsername(); - - // 使用用户名创建token - String token = JwtTokenUtil.createToken(userId, username, (int) readMeDay); - - // 获取IP地址并更新用户登录信息 - String ipAddr = IpUtil.getCurrentUserIpAddress().getIpAddr(); - String ipRegion = IpUtil.getCurrentUserIpAddress().getIpRegion(); - - // 设置用户IP地址,并更新用户信息 - AdminUser updateUser = new AdminUser(); - updateUser.setId(userId); - updateUser.setIpAddress(ipAddr); - updateUser.setIpRegion(ipRegion); - userMapper.updateById(updateUser); - - // 将用户登录保存在用户登录日志表中 - userLoginLogMapper.insert(setUserLoginLog(user, token, ipAddr, ipRegion, "login")); - - // 设置用户返回信息 - LoginVo loginVo = setLoginVo(user, token, readMeDay, ipAddr, ipRegion); - - // 将Redis中验证码删除 - redisTemplate.delete(RedisUserConstant.getAdminUserEmailCodePrefix(email)); - - // 将信息保存在Redis中,一定要确保用户名是唯一的 - redisTemplate.opsForValue().set(RedisUserConstant.getAdminLoginInfoPrefix(username), loginVo, readMeDay, TimeUnit.DAYS); - - return loginVo; - } - - /** - * * 构建用户返回对象LoginVo - * - * @param user 用户对象 - * @param readMeDay 记住我时间 - */ - public void buildUserVo(AdminUser user, long readMeDay) { - Long userId = user.getId(); - String username = user.getUsername(); - String loginInfoPrefix = RedisUserConstant.getAdminLoginInfoPrefix(username); - - // 使用用户名创建token - String token = JwtTokenUtil.createToken(userId, username, (int) readMeDay); - - // 设置用户返回信息 - LoginVo loginVo = setLoginVo(user, token, readMeDay, user.getIpAddress(), user.getIpRegion()); - - // 将信息保存在Redis中,一定要确保用户名是唯一的 - redisTemplate.opsForValue().set(loginInfoPrefix, loginVo, readMeDay, TimeUnit.DAYS); - } - - - /** - * 设置更新用户设置内容 - * - * @param user AdminUser - * @param token token - * @param readMeDay 记住我天数 - * @param ipAddr IP地址 - * @param ipRegion IP属地 - * @return LoginVo - */ - public LoginVo setLoginVo(AdminUser user, String token, long readMeDay, String ipAddr, String ipRegion) { - Long userId = user.getId(); - - // 判断用户是否有头像,如果没有头像设置默认头像,并且用户头像不能和默认头像相同 - String avatar = checkGetUserAvatar(user.getAvatar()); - - // 查找用户橘色 - List roles = new ArrayList<>(roleMapper.selectListByUserId(userId).stream().map(Role::getRoleCode).toList()); - List permissions = new ArrayList<>(); - - // 判断是否是 admin 如果是admin 赋予所有权限 - boolean isAdmin = RoleUtil.checkAdmin(roles, permissions, user); - if (!isAdmin) { - permissions = permissionMapper.selectListByUserId(userId).stream().map(Permission::getPowerCode).toList(); - } - - // 计算过期时间,并格式化返回 - LocalDateTime localDateTime = LocalDateTime.now(); - LocalDateTime plusDay = localDateTime.plusDays(readMeDay); - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD_HH_MM_SS_SLASH); - String expires = plusDay.format(dateTimeFormatter); - - // 构建返回对象,设置用户需要内容 - LoginVo loginVo = new LoginVo(); - BeanUtils.copyProperties(user, loginVo); - loginVo.setNickname(user.getNickname()); - loginVo.setAvatar(avatar); - loginVo.setToken(token); - loginVo.setRefreshToken(token); - loginVo.setIpAddress(ipAddr); - loginVo.setIpRegion(ipRegion); - loginVo.setRoles(roles); - loginVo.setPermissions(permissions); - loginVo.setUpdateUser(userId); - loginVo.setPersonDescription(user.getSummary()); - loginVo.setExpires(expires); - loginVo.setReadMeDay(readMeDay); - - return loginVo; - } - - /** - * 检查用户头像是否合规 - * - * @param avatar 头像字符串 - * @return 整理好的头像内容 - */ - public String checkPostUserAvatar(String avatar) { - // 如果用户没有头像或者用户头像和默认头像相同,返回默认头像 - String userAvatar = UserConstant.USER_AVATAR; - if (!StringUtils.hasText(avatar) || avatar.equals(userAvatar)) return userAvatar; - - // 替换前端发送的host前缀,将其删除,只保留路径名称 - String regex = "^https?://.*?/(.*)"; - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(avatar); - - // 如果没有匹配 - if (!matcher.matches()) return avatar; - - // 匹配后返回内容 - return "/" + matcher.group(1); - } - - /** - * 检查用户头像是否合规 - * - * @param avatar 头像字符串 - * @return 整理好的头像内容 - */ - public String checkGetUserAvatar(String avatar) { - // 如果用户没有头像或者用户头像和默认头像相同,返回默认头像 - String userAvatar = UserConstant.USER_AVATAR; - if (!StringUtils.hasText(avatar) || avatar.equals(userAvatar)) return userAvatar; - - // 替换前端发送的host前缀,将其删除,只保留路径名称 - String regex = "^https?://.*?/(.*)"; - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(avatar); - - // 如果没有匹配 - if (matcher.matches()) return avatar; - - // 匹配后返回内容 - return minioUtil.getObjectNameFullPath(avatar); - } - - - /** - * 设置用户登录日志内容 - * - * @param user AdminUser - * @param token token - * @param ipAddr IP地址 - * @param ipRegion IP属地 - * @param type 类型登录/退出 - * @return UserLoginLog - */ - public UserLoginLog setUserLoginLog(AdminUser user, String token, String ipAddr, String ipRegion, String type) { - Long userId = user.getId(); - - UserLoginLog userLoginLog = new UserLoginLog(); - userLoginLog.setUsername(user.getUsername()); - userLoginLog.setUserId(userId); - userLoginLog.setIpAddress(ipAddr); - userLoginLog.setIpRegion(ipRegion); - userLoginLog.setToken(token); - userLoginLog.setType(type); - userLoginLog.setCreateUser(userId); - userLoginLog.setUpdateUser(userId); - userLoginLog.setCreateTime(LocalDateTime.now()); - userLoginLog.setUpdateTime(LocalDateTime.now()); - - // 当前请求request - ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (requestAttributes == null) return userLoginLog; - HttpServletRequest request = requestAttributes.getRequest(); - - // 获取User-Agent - String userAgent = request.getHeader("User-Agent"); - userLoginLog.setUserAgent(userAgent); - - // 获取X-Requested-With - String xRequestedWith = request.getHeader("X-Requested-With"); - userLoginLog.setXRequestedWith(xRequestedWith); - - return userLoginLog; - } - - /** - * * 管理员修改管理员用户密码 - * - * @param adminUser 管理员用户修改密码 - */ - public void updateUserPasswordByAdmin(AdminUser adminUser) { - Long userId = adminUser.getId(); - String password = adminUser.getPassword(); - - // 密码更新是否存在 - if (!StringUtils.hasText(password)) return; - - // 对密码加密 - String encode = passwordEncoder.encode(password); - - // 判断新密码是否与旧密码相同 - if (adminUser.getPassword().equals(encode)) - throw new AuthCustomerException(ResultCodeEnum.UPDATE_NEW_PASSWORD_SAME_AS_OLD_PASSWORD); - - // 更新用户密码 - adminUser.setPassword(encode); - adminUser.setId(userId); - - // 删除Redis中用户信息 - String loginInfoPrefix = RedisUserConstant.getAdminLoginInfoPrefix(adminUser.getUsername()); - redisTemplate.delete(loginInfoPrefix); - } - - /** - * 管理员上传用户头像 - * - * @param dto 管理员用户修改头像 - */ - public void uploadAvatarByAdmin(AdminUserUpdateDto dto, AdminUser adminUser) { - MultipartFile avatar = dto.getAvatar(); - Long userId = dto.getId(); - - // 上传头像是否存在 - if (avatar == null) return; - - // 上传头像 - FileUploadDto uploadDto = FileUploadDto.builder().file(avatar).type(MinioConstant.avatar).build(); - FileInfoVo fileInfoVo = filesService.upload(uploadDto); - - // 更新用户 - adminUser.setId(userId); - adminUser.setAvatar(fileInfoVo.getFilepath()); - } -} \ No newline at end of file