feat: 优化登录功能;
删除不用的函数和冗余代码,将用户业务功能放在service下;Minio更名为MinioService将业务类和服务类拆分
This commit is contained in:
parent
a1e85e65f9
commit
0570ddd249
|
@ -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;
|
||||
|
|
|
@ -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<LoginVo> login(@Valid @RequestBody LoginDto loginDto) {
|
||||
LoginVo loginVo = userService.login(loginDto);
|
||||
return Result.success(loginVo);
|
||||
}
|
||||
|
||||
@Operation(summary = "登录发送邮件验证码", description = "登录发送邮件验证码")
|
||||
@PostMapping("public/sendLoginEmail")
|
||||
public Result<String> 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<RefreshTokenVo> refreshToken(@Valid @RequestBody RefreshTokenDto dto) {
|
||||
RefreshTokenVo vo = userService.refreshToken(dto);
|
||||
return Result.success(vo);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取本地登录用户信息", description = "获取用户信息从Redis中获取")
|
||||
@GetMapping("private/userinfo")
|
||||
public Result<LoginVo> userinfo() {
|
||||
LoginVo vo = BaseContext.getLoginVo();
|
||||
return Result.success(vo);
|
||||
}
|
||||
|
||||
@Operation(summary = "退出登录", description = "退出登录")
|
||||
@PostMapping("private/logout")
|
||||
public Result<String> logout() {
|
||||
userService.logout();
|
||||
return Result.success(ResultCodeEnum.LOGOUT_SUCCESS);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// 管理用户CURD
|
||||
// -----------------------------------------
|
||||
@Operation(summary = "分页查询", description = "分页查询用户信息", tags = "user::query")
|
||||
@GetMapping("{page}/{limit}")
|
||||
public Result<PageResult<AdminUserVo>> 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<Object> 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<String> updateUserByAdmin(
|
||||
@Valid AdminUserUpdateDto dto,
|
||||
@RequestPart(value = "avatar", required = false) MultipartFile avatar) {
|
||||
if (avatar != null) {
|
||||
dto.setAvatar(avatar);
|
||||
}
|
||||
public Result<String> 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<String> forcedOfflineByAdmin(@RequestBody Long id) {
|
||||
userService.forcedOfflineByAdmin(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// 普通用户
|
||||
// -----------------------------------------
|
||||
@Operation(summary = "更新本地用户信息", description = "更新本地用户信息,需要更新Redis中的内容")
|
||||
@PutMapping("private/update/userinfo")
|
||||
public Result<String> updateAdminUserByLocalUser(@Valid @RequestBody AdminUserUpdateByLocalUserDto dto) {
|
||||
|
|
|
@ -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<LoginVo> login(@Valid @RequestBody LoginDto loginDto) {
|
||||
LoginVo loginVo = userLoginService.login(loginDto);
|
||||
return Result.success(loginVo);
|
||||
}
|
||||
|
||||
@Operation(summary = "登录发送邮件验证码", description = "登录发送邮件验证码")
|
||||
@PostMapping("public/sendLoginEmail")
|
||||
public Result<String> 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<RefreshTokenVo> refreshToken(@Valid @RequestBody RefreshTokenDto dto) {
|
||||
RefreshTokenVo vo = userLoginService.refreshToken(dto);
|
||||
return Result.success(vo);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取本地登录用户信息", description = "获取用户信息从Redis中获取")
|
||||
@GetMapping("private/userinfo")
|
||||
public Result<LoginVo> userinfo() {
|
||||
LoginVo vo = BaseContext.getLoginVo();
|
||||
return Result.success(vo);
|
||||
}
|
||||
|
||||
@Operation(summary = "退出登录", description = "退出登录")
|
||||
@PostMapping("private/logout")
|
||||
public Result<String> logout() {
|
||||
userLoginService.logout();
|
||||
return Result.success(ResultCodeEnum.LOGOUT_SUCCESS);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
public abstract class AbstractPermissionCheckHandler {
|
||||
|
||||
private AbstractPermissionCheckHandler abstractPermissionCheckHandler;
|
||||
|
||||
public AbstractPermissionCheckHandler(AbstractPermissionCheckHandler abstractPermissionCheckHandler) {
|
||||
this.abstractPermissionCheckHandler = abstractPermissionCheckHandler;
|
||||
}
|
||||
|
||||
abstract protected void checkPermission(String requestUrl);
|
||||
}
|
|
@ -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();
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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<String, Object> redisTemplate;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// Set<String> keys = redisTemplate.keys("admin::login_info::*");
|
||||
// for (String key : keys) {
|
||||
// System.out.println(key);
|
||||
// }
|
||||
|
||||
Map<String, Object> adminLoginInfoWithScan = getAdminLoginInfoWithScan();
|
||||
JSONObject adminLoginInfo = new JSONObject(adminLoginInfoWithScan);
|
||||
System.out.println(adminLoginInfo);
|
||||
}
|
||||
|
||||
public Map<String, Object> getAdminLoginInfoWithScan() {
|
||||
String pattern = "admin::login_info::*";
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 使用scan命令迭代查找
|
||||
ScanOptions options = ScanOptions.scanOptions().match(pattern).count(100).build();
|
||||
Cursor<String> 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<String, Object> redisTemplate;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
String prefix = RedisUserConstant.getAdminUserEmailCodePrefix("");
|
||||
Set<String> keys = redisTemplate.keys(prefix);
|
||||
for (String key : keys) {
|
||||
System.out.println(key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
|
||||
*
|
||||
* <p>实现头像URL的统一存储和访问格式转换:</p>
|
||||
*
|
||||
* <ol>
|
||||
* <li><b>存储处理</b>:将带HTTP前缀的头像URL转换为数据库存储格式
|
||||
* <ul>
|
||||
* <li>匹配正则表达式:^https?://.*?/(.*)</li>
|
||||
* <li>提取路径部分:matcher.group(1)</li>
|
||||
* <li>转换为存储格式:"/" + 提取的路径</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li><b>访问处理</b>:返回带HTTP前缀的完整访问URL</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>典型用例:</p>
|
||||
* <pre>
|
||||
* 输入:"http|s://example.com/images/avatar.jpg"
|
||||
* 存储:"images/avatar.jpg"
|
||||
* 访问:"http|s://xxx/images/avatar.jpg"
|
||||
* </pre>
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* <p>处理逻辑:</p>
|
||||
* <ol>
|
||||
* <li>当头像为空或与默认头像相同时,返回系统默认头像</li>
|
||||
* <li>尝试移除头像URL中的HTTP协议和域名部分(如果存在)</li>
|
||||
* <li>最终返回MinIO存储中的完整对象访问路径</li>
|
||||
* </ol>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.services.config.minio;
|
||||
package cn.bunny.services.minio;
|
||||
|
||||
import io.minio.BucketExistsArgs;
|
||||
import io.minio.MakeBucketArgs;
|
|
@ -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("创建失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<MessageReceivedMapper, MessageReceived> implements MessageReceivedService {
|
||||
|
||||
@Resource
|
||||
private UserUtil userUtil;
|
||||
private MinioHelper minioHelper;
|
||||
|
||||
/**
|
||||
* 管理员管理用户消息接收分页查询
|
||||
|
@ -58,7 +58,7 @@ public class MessageReceivedServiceImpl extends ServiceImpl<MessageReceivedMappe
|
|||
|
||||
// 设置封面返回内容
|
||||
String cover = vo.getCover();
|
||||
cover = userUtil.checkGetUserAvatar(cover);
|
||||
cover = minioHelper.getUserAvatar(cover);
|
||||
vo.setCover(cover);
|
||||
return vo;
|
||||
}).toList();
|
||||
|
@ -113,7 +113,7 @@ public class MessageReceivedServiceImpl extends ServiceImpl<MessageReceivedMappe
|
|||
|
||||
// 设置封面返回内容
|
||||
String cover = vo.getCover();
|
||||
cover = userUtil.checkGetUserAvatar(cover);
|
||||
cover = minioHelper.getUserAvatar(cover);
|
||||
vo.setCover(cover);
|
||||
return vo;
|
||||
}).toList();
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package cn.bunny.services.service.message.impl;
|
||||
|
||||
import cn.bunny.services.context.BaseContext;
|
||||
import cn.bunny.services.domain.common.model.entity.BaseEntity;
|
||||
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.MessageAddDto;
|
||||
import cn.bunny.services.domain.system.message.dto.MessageDto;
|
||||
import cn.bunny.services.domain.system.message.dto.MessageUpdateDto;
|
||||
|
@ -10,16 +13,13 @@ import cn.bunny.services.domain.system.message.vo.MessageDetailVo;
|
|||
import cn.bunny.services.domain.system.message.vo.MessageReceivedWithMessageVo;
|
||||
import cn.bunny.services.domain.system.message.vo.MessageReceivedWithUserVo;
|
||||
import cn.bunny.services.domain.system.message.vo.MessageVo;
|
||||
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.MessageMapper;
|
||||
import cn.bunny.services.mapper.message.MessageReceivedMapper;
|
||||
import cn.bunny.services.mapper.system.UserMapper;
|
||||
import cn.bunny.services.minio.MinioHelper;
|
||||
import cn.bunny.services.service.message.MessageReceivedService;
|
||||
import cn.bunny.services.service.message.MessageService;
|
||||
import cn.bunny.services.utils.system.UserUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
|
@ -47,17 +47,14 @@ import java.util.stream.Collectors;
|
|||
@Transactional
|
||||
public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> 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<MessageMapper, Message> 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<MessageMapper, Message> impl
|
|||
|
||||
// 设置封面返回内容
|
||||
String cover = dto.getCover();
|
||||
dto.setCover(userUtil.checkGetUserAvatar(cover));
|
||||
dto.setCover(minioHelper.getUserAvatar(cover));
|
||||
|
||||
// 更新内容
|
||||
Message message = new Message();
|
||||
|
|
|
@ -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<AdminUser> {
|
||||
|
||||
/**
|
||||
* 前台用户登录接口
|
||||
*
|
||||
* @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();
|
||||
|
||||
}
|
|
@ -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<AdminUser> {
|
||||
|
||||
/**
|
||||
* 前台用户登录接口
|
||||
*
|
||||
* @param loginDto 登录参数
|
||||
* @return 登录后结果返回
|
||||
*/
|
||||
LoginVo login(LoginDto loginDto);
|
||||
|
||||
/**
|
||||
* * 获取用户信息列表
|
||||
*
|
||||
|
@ -60,27 +52,6 @@ public interface UserService extends IService<AdminUser> {
|
|||
*/
|
||||
void deleteUserByAdmin(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 登录发送邮件验证码
|
||||
*
|
||||
* @param email 邮箱
|
||||
*/
|
||||
void sendLoginEmail(@NotNull String email);
|
||||
|
||||
/**
|
||||
* 刷新用户token
|
||||
*
|
||||
* @param dto 请求token
|
||||
* @return 刷新token返回内容
|
||||
*/
|
||||
@NotNull
|
||||
RefreshTokenVo refreshToken(@NotNull RefreshTokenDto dto);
|
||||
|
||||
/**
|
||||
* * 退出登录
|
||||
*/
|
||||
void logout();
|
||||
|
||||
/**
|
||||
* * 获取用户信息
|
||||
*
|
||||
|
|
|
@ -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<String, Object> redisTemplate;
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
@Resource
|
||||
private UserLoginLogMapper userLoginLogMapper;
|
||||
@Resource
|
||||
private RoleMapper roleMapper;
|
||||
@Resource
|
||||
private PermissionMapper permissionMapper;
|
||||
@Resource
|
||||
private MinioHelper minioHelper;
|
||||
|
||||
/**
|
||||
* 构建用户登录返回对象(LoginVo)
|
||||
*
|
||||
* <p>主要处理流程:</p>
|
||||
* <ol>
|
||||
* <li><b>参数校验</b>:检查用户对象是否为空</li>
|
||||
* <li><b>权限处理</b>:
|
||||
* <ul>
|
||||
* <li>查询用户角色和权限数据</li>
|
||||
* <li>非管理员用户:从数据库加载权限信息</li>
|
||||
* <li>管理员用户:通过RoleUtil.checkAdmin()自动设置管理员权限</li>
|
||||
* <li>(可选)对角色和权限列表去重</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li><b>信息装配</b>:
|
||||
* <ul>
|
||||
* <li>记录用户IP等访问信息</li>
|
||||
* <li>使用BeanUtils.copyProperties()复制用户基础属性</li>
|
||||
* <li>设置记住我功能及token过期时间</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li><b>缓存处理</b>:将完整用户信息存入Redis</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>注意事项:</p>
|
||||
* <ul>
|
||||
* <li>属性复制操作放在流程最后,确保所有字段正确同步</li>
|
||||
* <li>IP信息需要同时更新到用户实体和返回对象</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param user 用户实体对象(不可为空)
|
||||
* @param readMeDay 记住我时长(单位:天)
|
||||
* @return 完整的登录响应对象
|
||||
* @throws IllegalArgumentException 当用户对象为空时抛出
|
||||
*/
|
||||
public LoginVo buildLoginUserVo(AdminUser user, long readMeDay) {
|
||||
String username = user.getUsername();
|
||||
Long userId = user.getId();
|
||||
|
||||
// 用户角色
|
||||
List<String> roles = new ArrayList<>(roleMapper.selectListByUserId(userId).stream().map(Role::getRoleCode).toList());
|
||||
|
||||
// 判断是否是 admin 如果是admin 赋予所有权限
|
||||
List<String> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户登录日志内容
|
||||
* <p>
|
||||
* 该方法用于将管理员用户信息复制到用户登录日志对象中,同时处理特殊字段映射关系。
|
||||
* <p>
|
||||
* 实现说明:
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, LoginStrategy> strategies;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public LoginContext(Map<String, LoginStrategy> strategies, PasswordEncoder passwordEncoder) {
|
||||
public LoginContext(Map<String, LoginStrategy> 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<FilesMapper, Files> 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<FilesMapper, Files> implements
|
|||
public void addFiles(FilesAddDto dto) {
|
||||
List<Files> 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<FilesMapper, Files> 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<FilesMapper, Files> 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<FilesMapper, Files> 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<FilesMapper, Files> implements
|
|||
}).toList();
|
||||
|
||||
// 删除目标文件
|
||||
minioUtil.removeObjects(list);
|
||||
minioService.removeObjects(list);
|
||||
|
||||
// 删除数据库内容
|
||||
removeByIds(ids);
|
||||
|
@ -229,7 +234,7 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> 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();
|
||||
|
|
|
@ -94,7 +94,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
|||
* @return 所有角色列表
|
||||
*/
|
||||
@Override
|
||||
@Cacheable(cacheNames = "role", key = "'allRole'", cacheManager = "cacheManagerWithMouth")
|
||||
@Cacheable(cacheNames = "role", key = "'roleList'", cacheManager = "cacheManagerWithMouth")
|
||||
public List<RoleVo> roleList() {
|
||||
return list().stream().map(role -> {
|
||||
RoleVo roleVo = new RoleVo();
|
||||
|
@ -155,7 +155,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> 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<RoleMapper, Role> 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<RoleMapper, Role> 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<Role> roleList = list(Wrappers.<Role>lambdaQuery().eq(Role::getId, dto.getId()));
|
||||
|
@ -213,7 +213,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
|||
* @param ids 删除id列表
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = "role", key = "'allRole'", beforeInvocation = true)
|
||||
@CacheEvict(cacheNames = "role", key = "'roleList'", beforeInvocation = true)
|
||||
public void deleteRole(List<Long> ids) {
|
||||
// 判断数据请求是否为空
|
||||
if (ids.isEmpty()) throw new AuthCustomerException(ResultCodeEnum.REQUEST_IS_EMPTY);
|
||||
|
|
|
@ -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<UserMapper, AdminUser> implements UserLoginService {
|
||||
@Resource
|
||||
private UserLoginHelper userloginHelper;
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
@Resource
|
||||
private EmailTemplateMapper emailTemplateMapper;
|
||||
@Resource
|
||||
private ConcreteSenderEmailTemplate concreteSenderEmailTemplate;
|
||||
|
||||
/**
|
||||
* 前台用户登录接口
|
||||
* 这里不用判断用户是否为空,因为在登录时已经校验过了
|
||||
* <p>
|
||||
* 抛出异常使用自带的 UsernameNotFoundException 或者自己封装<br/>
|
||||
* 但是这两个效果传入参数都是一样的,所以全部使用 UsernameNotFoundException
|
||||
* </p>
|
||||
*
|
||||
* @param loginDto 登录参数
|
||||
* @return 登录后结果返回
|
||||
*/
|
||||
@Override
|
||||
public LoginVo login(LoginDto loginDto) {
|
||||
// 初始化所有策略(可扩展)
|
||||
HashMap<String, LoginStrategy> 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<EmailTemplate> 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<String, Object> 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.<AdminUser>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.<AdminUser>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);
|
||||
}
|
||||
}
|
|
@ -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<UserRoleMapper, UserRole> implements UserRoleService {
|
||||
|
||||
@Resource
|
||||
private UserRoleMapper userRoleMapper;
|
||||
|
||||
private UserLoginHelper userloginHelper;
|
||||
@Resource
|
||||
private UserUtil userUtil;
|
||||
|
||||
private UserRoleMapper userRoleMapper;
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
|
@ -94,7 +91,7 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<UserMapper, AdminUser> implements UserService {
|
||||
|
||||
@Resource
|
||||
private UserUtil userUtil;
|
||||
private UserLoginHelper userloginHelper;
|
||||
@Resource
|
||||
private ConcreteSenderEmailTemplate concreteSenderEmailTemplate;
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> 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;
|
||||
|
||||
/**
|
||||
* 前台用户登录接口
|
||||
* 这里不用判断用户是否为空,因为在登录时已经校验过了
|
||||
* <p>
|
||||
* 抛出异常使用自带的 UsernameNotFoundException 或者自己封装<br/>
|
||||
* 但是这两个效果传入参数都是一样的,所以全部使用 UsernameNotFoundException
|
||||
* </p>
|
||||
*
|
||||
* @param loginDto 登录参数
|
||||
* @return 登录后结果返回
|
||||
*/
|
||||
@Override
|
||||
public LoginVo login(LoginDto loginDto) {
|
||||
Long readMeDay = loginDto.getReadMeDay();
|
||||
|
||||
// 初始化所有策略(可扩展)
|
||||
HashMap<String, LoginStrategy> 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<EmailTemplate> 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<String, Object> 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.<AdminUser>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.<AdminUser>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<UserMapper, AdminUser> 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<UserMapper, AdminUser> 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<UserMapper, AdminUser> implemen
|
|||
AdminUser user = getOne(Wrappers.<AdminUser>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<UserMapper, AdminUser> 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<UserMapper, AdminUser> implemen
|
|||
List<AdminUserVo> 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<UserMapper, AdminUser> 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<UserMapper, AdminUser> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 判断是否是管理员
|
||||
* 判断用户是否具有管理员权限
|
||||
* <p>
|
||||
* 管理员判定规则:
|
||||
* 1. 系统默认管理员:用户ID为1的预设管理员账号(用户名为Administrator)
|
||||
* 2. 角色授权管理员:用户角色列表包含"admin"角色的账号
|
||||
* <p>
|
||||
* 权限控制说明:
|
||||
* - 管理员权限用于前端按钮级权限控制,支持以下通配符格式:
|
||||
* - "*::*::*":全部模块的全部操作权限
|
||||
* - "*::*" :指定模块的全部操作权限
|
||||
* - "*" :基础通配权限
|
||||
* - 若无细粒度按钮控制需求,可不设置具体权限
|
||||
* 详细查看
|
||||
* <a href="https://pure-admin.cn/pages/RBAC/#%E7%AC%AC%E4%BA%8C%E7%A7%8D%E6%A8%A1%E5%BC%8F-%E7%99%BB%E5%BD%95%E6%8E%A5%E5%8F%A3%E8%BF%94%E5%9B%9E%E6%8C%89%E9%92%AE%E7%BA%A7%E5%88%AB%E6%9D%83%E9%99%90">
|
||||
* 查看前端权限设置
|
||||
* </a>
|
||||
*
|
||||
* @param roleList 角色代码列表
|
||||
* @param permissions 权限列表
|
||||
* @param adminUser 用户信息
|
||||
* @return 是否是管理员
|
||||
* @param roleList 用户角色编码列表(可能包含"admin"角色)
|
||||
* @param permissions 用户权限列表(用于前端按钮控制)
|
||||
* @param adminUser 用户实体对象(需包含userId字段)
|
||||
* @return true-是管理员,false-非管理员
|
||||
*/
|
||||
public static boolean checkAdmin(List<String> roleList, List<String> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, Object> 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<String> roles = new ArrayList<>(roleMapper.selectListByUserId(userId).stream().map(Role::getRoleCode).toList());
|
||||
List<String> 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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue