Compare commits
No commits in common. "ec1603885ff58ab85229a6a55b7a358e829954e0" and "a1e85e65f9aa6d775d07e55b279226410619af48" have entirely different histories.
ec1603885f
...
a1e85e65f9
|
@ -197,7 +197,7 @@ AntPath详情:https://juejin.cn/spost/7498247273660743732
|
|||
|
||||
### 开发环境
|
||||
|
||||
根据不同docker 启动方式不一样
|
||||
根据不懂docker 启动方式不一样
|
||||
|
||||
```bash
|
||||
# 一键启动依赖服务
|
||||
|
@ -244,10 +244,10 @@ docker compose up -d
|
|||
- [ ] 权限树型结构动态添加、更新、删除
|
||||
- [ ] 用户设置持久化存储到数据库
|
||||
- [ ] 权限弹窗页面优化
|
||||
- [x] 后端文档注释完善
|
||||
- [ ] 后端文档注释完善
|
||||
- [x] 系统监控后端返回403停止请求
|
||||
- [x] 优化用户配置权限逻辑,配置后热更新逻辑等
|
||||
- [x] 完善后端注释,有需要添加ReadMe文档
|
||||
- [ ] 优化用户配置权限逻辑,配置后热更新逻辑等
|
||||
- [ ] 完善后端注释,有需要添加ReadMe文档
|
||||
- [ ] Redis中获取活跃用户
|
||||
|
||||
## 前后端接口规范
|
||||
|
|
|
@ -2,8 +2,8 @@ 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.common.model.dto.quartz.ScheduleExecuteLogJson;
|
||||
import cn.bunny.services.domain.system.log.entity.ScheduleExecuteLog;
|
||||
import cn.bunny.services.domain.common.model.dto.quartz.ScheduleExecuteLogJson;
|
||||
import cn.bunny.services.mapper.log.ScheduleExecuteLogMapper;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import jakarta.annotation.Resource;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package cn.bunny.services.controller.system;
|
||||
|
||||
import cn.bunny.services.aop.scanner.ControllerApiPermissionScanner;
|
||||
import cn.bunny.services.domain.common.model.dto.scanner.ScannerControllerInfoVo;
|
||||
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.common.scanner.ScannerControllerInfoVo;
|
||||
import cn.bunny.services.domain.system.system.dto.power.PermissionAddDto;
|
||||
import cn.bunny.services.domain.system.system.dto.power.PermissionDto;
|
||||
import cn.bunny.services.domain.system.system.dto.power.PermissionUpdateBatchByParentIdDto;
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
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.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.dto.user.*;
|
||||
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;
|
||||
|
@ -17,6 +18,7 @@ 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;
|
||||
|
||||
|
@ -31,6 +33,49 @@ 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(
|
||||
|
@ -44,19 +89,21 @@ 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);
|
||||
}
|
||||
|
@ -82,13 +129,16 @@ 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) {
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.bunny.services.controller;
|
||||
|
||||
import cn.bunny.services.aop.scanner.ControllerApiPermissionScanner;
|
||||
import cn.bunny.services.domain.common.model.dto.scanner.ScannerControllerInfoVo;
|
||||
import cn.bunny.services.domain.common.scanner.ScannerControllerInfoVo;
|
||||
import cn.bunny.services.domain.system.system.entity.Permission;
|
||||
import cn.bunny.services.service.system.PermissionService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
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.common.model.vo.LoginVo;
|
||||
import cn.bunny.services.domain.system.system.entity.AdminUser;
|
||||
import cn.bunny.services.domain.common.model.vo.LoginVo;
|
||||
import cn.bunny.services.mapper.system.UserMapper;
|
||||
import cn.bunny.services.service.system.helper.UserLoginHelper;
|
||||
import cn.bunny.services.utils.system.UserUtil;
|
||||
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 UserLoginHelper userUtil;
|
||||
private UserUtil userUtil;
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
|
|
@ -3,12 +3,16 @@ package impl;
|
|||
import cn.bunny.services.domain.common.model.dto.excel.I18nExcel;
|
||||
import cn.bunny.services.domain.system.i18n.entity.I18n;
|
||||
import cn.bunny.services.mapper.configuration.I18nMapper;
|
||||
import cn.bunny.services.utils.i8n.I18nUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -17,6 +21,34 @@ import java.util.zip.ZipOutputStream;
|
|||
|
||||
public class I18nServiceImplTest extends ServiceImpl<I18nMapper, I18n> {
|
||||
|
||||
@Test
|
||||
void downloadI18n() {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
|
||||
|
||||
// 查找默认语言内容
|
||||
List<I18n> i18nList = list();
|
||||
HashMap<String, Object> hashMap = I18nUtil.getMap(i18nList);
|
||||
|
||||
hashMap.forEach((k, v) -> {
|
||||
try {
|
||||
ZipEntry zipEntry = new ZipEntry(k + ".json");
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
|
||||
zipOutputStream.write(JSON.toJSONString(v).getBytes(StandardCharsets.UTF_8));
|
||||
zipOutputStream.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
zipOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadI18nByExcel() {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,25 +17,6 @@
|
|||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- 单元测试 -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-java</artifactId>
|
||||
<version>4.30.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.services.minio;
|
||||
package cn.bunny.services.config.minio;
|
||||
|
||||
import io.minio.BucketExistsArgs;
|
||||
import io.minio.MakeBucketArgs;
|
||||
|
@ -12,8 +12,7 @@ import org.springframework.context.annotation.Configuration;
|
|||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "bunny.minio")
|
||||
// 当属性有值时这个配置才生效
|
||||
@ConditionalOnProperty(name = "bunny.minio.bucket-name")
|
||||
@ConditionalOnProperty(name = "bunny.minio.bucket-name")// 当属性有值时这个配置才生效
|
||||
@Data
|
||||
public class MinioProperties {
|
||||
|
||||
|
@ -31,7 +30,6 @@ public class MinioProperties {
|
|||
MinioClient minioClient = MinioClient.builder().endpoint(endpointUrl).credentials(accessKey, secretKey).build();
|
||||
|
||||
try {
|
||||
// 判断桶是否存在,不存在则创建,并且可以有公开访问权限
|
||||
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
if (!found) {
|
||||
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
|
@ -0,0 +1,214 @@
|
|||
package cn.bunny.services.config.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.exception.AuthCustomerException;
|
||||
import io.minio.*;
|
||||
import io.minio.messages.DeleteError;
|
||||
import io.minio.messages.DeleteObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Minio操作工具类 简化操作步骤
|
||||
* 自定义封装
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MinioUtil {
|
||||
private final MinioProperties properties;
|
||||
|
||||
private final MinioClient minioClient;
|
||||
|
||||
public MinioUtil(MinioProperties properties, MinioClient minioClient) {
|
||||
this.properties = properties;
|
||||
this.minioClient = minioClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Minio文件路径
|
||||
*/
|
||||
public static MinioFilePath initUploadFileReturnMinioFilePath(String buckName, String minioPreType, MultipartFile file) {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
// 定义日期时间格式
|
||||
LocalDateTime currentDateTime = LocalDateTime.now();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM-dd");
|
||||
String extension = "";
|
||||
|
||||
// 原始文件名
|
||||
String filename = file.getOriginalFilename();
|
||||
if (StringUtils.hasText(filename) && filename.contains(".")) {
|
||||
extension = "." + filename.substring(filename.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
// UUID防止重名
|
||||
String uuidFilename = uuid + extension;
|
||||
|
||||
// 拼接时间+UUID文件名
|
||||
String timeUuidFilename = currentDateTime.format(formatter) + "/" + uuidFilename;// 加上时间路径
|
||||
|
||||
// 上传根文件夹+拼接时间+UUID文件名
|
||||
String filepath = MinioConstant.getType(minioPreType) + timeUuidFilename;
|
||||
|
||||
// 桶名称+上传根文件夹+拼接时间+UUID文件名
|
||||
String buckNameFilepath = "/" + buckName + MinioConstant.getType(minioPreType) + timeUuidFilename;
|
||||
|
||||
// 设置及Minio基础信息
|
||||
MinioFilePath minioFIlePath = new MinioFilePath();
|
||||
minioFIlePath.setFilename(filename);
|
||||
minioFIlePath.setUuidFilename(uuidFilename);
|
||||
minioFIlePath.setTimeUuidFilename(timeUuidFilename);
|
||||
minioFIlePath.setFilepath(filepath);
|
||||
minioFIlePath.setBucketNameFilepath(buckNameFilepath);
|
||||
|
||||
return minioFIlePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* * 上传文件并返回处理信息
|
||||
*/
|
||||
public MinioFilePath uploadObjectReturnFilePath(MultipartFile file, String minioPreType) throws IOException {
|
||||
if (file == null) return null;
|
||||
String bucketName = properties.getBucketName();
|
||||
|
||||
// 上传对象
|
||||
try {
|
||||
MinioFilePath minioFile = initUploadFileReturnMinioFilePath(bucketName, minioPreType, file);
|
||||
|
||||
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(minioFile.getFilepath()).stream(file.getInputStream(), file.getSize(), -1).build());
|
||||
|
||||
return minioFile;
|
||||
} catch (Exception exception) {
|
||||
throw new AuthCustomerException(ResultCodeEnum.UPLOAD_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认bucket文件,并返回字节数组
|
||||
*
|
||||
* @param objectName 对象名称
|
||||
* @return 文件流对象
|
||||
*/
|
||||
public byte[] getBucketObjectByte(String objectName) {
|
||||
String bucketName = properties.getBucketName();
|
||||
|
||||
try {
|
||||
GetObjectResponse getObjectResponse = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
|
||||
|
||||
return getObjectResponse.readAllBytes();
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
throw new AuthCustomerException(ResultCodeEnum.GET_BUCKET_EXCEPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Minio全路径名,Object带有桶名称
|
||||
*
|
||||
* @param objectName 对象名称
|
||||
* @return 全路径
|
||||
*/
|
||||
public String getObjectNameFullPath(String objectName) {
|
||||
String url = properties.getEndpointUrl();
|
||||
|
||||
return url + objectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param bucketName 桶名称
|
||||
* @param filename 文件名
|
||||
* @param inputStream 输入流
|
||||
* @param size 大小
|
||||
*/
|
||||
public void putObject(String bucketName, String filename, InputStream inputStream, Long size) {
|
||||
try {
|
||||
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(filename).stream(inputStream, size, -1).build());
|
||||
} catch (Exception exception) {
|
||||
log.error("上传文件失败:{}", (Object) exception.getStackTrace());
|
||||
throw new AuthCustomerException(ResultCodeEnum.UPLOAD_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* * 删除目标文件
|
||||
*
|
||||
* @param list 对象文件列表
|
||||
*/
|
||||
public void removeObjects(List<String> list) {
|
||||
try {
|
||||
String bucketName = properties.getBucketName();
|
||||
List<DeleteObject> objectList = list.stream().map(DeleteObject::new).toList();
|
||||
|
||||
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objectList).build());
|
||||
for (Result<DeleteError> result : results) {
|
||||
DeleteError error = result.get();
|
||||
throw new AuthCustomerException("Error in deleting object " + error.objectName() + "; " + error.message());
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* * 更新文件
|
||||
*
|
||||
* @param bucketName 桶名称
|
||||
* @param filepath 文件路径
|
||||
* @param file 文件
|
||||
*/
|
||||
public void updateFile(String bucketName, String filepath, MultipartFile file) {
|
||||
try {
|
||||
putObject(bucketName, filepath, file.getInputStream(), file.getSize());
|
||||
} catch (IOException e) {
|
||||
throw new AuthCustomerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断桶是否存在
|
||||
*
|
||||
* @param bucketName 桶名称
|
||||
* @return 布尔值,是否存在
|
||||
*/
|
||||
public boolean createBucketIfNotExists(String bucketName) {
|
||||
boolean found = false;
|
||||
try {
|
||||
found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
|
||||
// 如果 bucket 不存在就创建
|
||||
if (!found) makeBucket(bucketName);
|
||||
|
||||
return found;
|
||||
} catch (Exception exception) {
|
||||
log.error("判断桶是否存在 ------ 失败消息:{}", exception.getLocalizedMessage());
|
||||
exception.getStackTrace();
|
||||
}
|
||||
throw new AuthCustomerException("未初始化 bucket");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
*
|
||||
* @param bucketName 桶的名称
|
||||
*/
|
||||
public void makeBucket(String bucketName) {
|
||||
try {
|
||||
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
||||
} catch (Exception exception) {
|
||||
exception.getStackTrace();
|
||||
throw new AuthCustomerException("创建失败");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,9 +4,14 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
public class SecurityConfigConstant {
|
||||
public static String[] REQUEST_MATCHERS_PERMIT_ALL = {
|
||||
"/", "/**.html", "/error", "/media.ico", "/favicon.ico",
|
||||
"/webjars/**", "/v3/api-docs/**", "/swagger-ui/**",
|
||||
"/*/*/noAuth/**", "/*/noAuth/**", "/noAuth/**",
|
||||
"/*/i18n/getI18n"
|
||||
};
|
||||
|
||||
/* 可以放行的权限 */
|
||||
public static List<String> PERMIT_ACCESS_LIST = new ArrayList<>() {{
|
||||
public static List<String> PERMIT_ALL_LIST = new ArrayList<>() {{
|
||||
add("admin");
|
||||
}};
|
||||
}
|
||||
|
|
|
@ -6,7 +6,4 @@ 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";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package cn.bunny.services.domain.common.model.dto.file;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class MinioFilePath {
|
||||
private String filename;
|
||||
private String uuidFilename;
|
||||
private String timeUuidFilename;
|
||||
private String filepath;
|
||||
private String bucketNameFilepath;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package cn.bunny.services.domain.common.model.dto.ip;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -10,13 +11,13 @@ import lombok.NoArgsConstructor;
|
|||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "IpEntity对象", title = "用户IP相关信息")
|
||||
@ApiModel(value = "IpEntity对象", description = "用户IP相关信息")
|
||||
public class IpEntity {
|
||||
|
||||
@Schema(name = "ipAddr", title = "原始地址")
|
||||
@ApiModelProperty("原始地址")
|
||||
private String ipAddr;
|
||||
|
||||
@Schema(name = "ipRegion", title = "IP归属地")
|
||||
@ApiModelProperty("IP归属地")
|
||||
private String ipRegion;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package cn.bunny.services.domain.common.model.dto.minio;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "MinioFilePath", title = "Minion上传返回西悉尼")
|
||||
public class MinioUploadFileInfo {
|
||||
|
||||
@Schema(name = "filename", title = "文件名")
|
||||
private String filename;
|
||||
|
||||
@Schema(name = "uuidFilename", title = "uuid文件名,防止重复")
|
||||
private String uuidFilename;
|
||||
|
||||
@Schema(name = "timeUuidFilename", title = "时间+uuid文件名")
|
||||
private String timeUuidFilename;
|
||||
|
||||
@Schema(name = "filepath", title = "文件路径")
|
||||
private String filepath;
|
||||
|
||||
@Schema(name = "bucketNameFilepath", title = "上传桶名称文件路径")
|
||||
private String bucketNameFilepath;
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.services.domain.common.model.dto.scanner;
|
||||
package cn.bunny.services.domain.common.scanner;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.services.domain.common.model.dto.scanner;
|
||||
package cn.bunny.services.domain.common.scanner;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.services.domain.common.model.dto.scanner;
|
||||
package cn.bunny.services.domain.common.scanner;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
|
@ -1,111 +0,0 @@
|
|||
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,227 +0,0 @@
|
|||
package cn.bunny.services.minio;
|
||||
|
||||
import cn.bunny.services.domain.common.constant.MinioConstant;
|
||||
import cn.bunny.services.domain.common.model.dto.minio.MinioUploadFileInfo;
|
||||
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.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Minio操作工具类 简化操作步骤
|
||||
* 自定义封装
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MinioService {
|
||||
|
||||
@Resource
|
||||
private MinioProperties properties;
|
||||
|
||||
@Resource
|
||||
private MinioClient minioClient;
|
||||
|
||||
/**
|
||||
* 上传文件到MinIO并返回文件处理信息
|
||||
*
|
||||
* <p><b>文件存储规则:</b></p>
|
||||
* <ul>
|
||||
* <li>按日期分目录存储(格式:yyyy/MM-dd)</li>
|
||||
* <li>使用UUID重命名文件防止冲突</li>
|
||||
* <li>保留原始文件名信息</li>
|
||||
* <li>支持按类型分类存储(通过minioPreType参数)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>返回信息包含:</b></p>
|
||||
* <ol>
|
||||
* <li>原始文件名</li>
|
||||
* <li>UUID重命名后的文件名</li>
|
||||
* <li>带日期路径的文件名</li>
|
||||
* <li>完整存储路径(不含桶名)</li>
|
||||
* <li>完整访问路径(含桶名)</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param file 上传的文件对象(不可为空)
|
||||
* @param minioPreType 文件类型前缀(用于分类存储,参考MinioConstant定义)
|
||||
* @return 文件处理信息对象(包含各种路径信息),当file为null时返回null
|
||||
* @throws IOException 当文件流读取异常时抛出
|
||||
* @throws AuthCustomerException 当上传失败时抛出(错误码:UPLOAD_ERROR)
|
||||
* @see MinioConstant 文件类型前缀常量
|
||||
* @see MinioUploadFileInfo 返回信息数据结构
|
||||
*/
|
||||
public MinioUploadFileInfo uploadWithFileInfo(MultipartFile file, String minioPreType) throws IOException {
|
||||
if (file == null) return null;
|
||||
|
||||
// 上传文件时的桶名称
|
||||
String bucketName = properties.getBucketName();
|
||||
|
||||
// 上传对象
|
||||
try {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
// 定义日期时间格式
|
||||
String dateFormatter = new SimpleDateFormat("yyyy/MM-dd").format(new Date());
|
||||
// 文件后缀
|
||||
String extension = "";
|
||||
|
||||
// 原始文件名
|
||||
String filename = file.getOriginalFilename();
|
||||
if (StringUtils.hasText(filename) && filename.contains(".")) {
|
||||
extension = "." + filename.substring(filename.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
// UUID防止重名
|
||||
String uuidFilename = uuid + extension;
|
||||
|
||||
// 拼接时间+UUID文件名 加上时间路径
|
||||
String timeUuidFilename = dateFormatter + "/" + uuidFilename;
|
||||
|
||||
// 上传根文件夹+拼接时间+UUID文件名
|
||||
String filepath = MinioConstant.getType(minioPreType) + timeUuidFilename;
|
||||
|
||||
// 桶名称+上传根文件夹+拼接时间+UUID文件名
|
||||
String buckNameFilepath = "/" + bucketName + MinioConstant.getType(minioPreType) + timeUuidFilename;
|
||||
|
||||
// 设置及Minio基础信息
|
||||
MinioUploadFileInfo minioUploadFIleInfo = new MinioUploadFileInfo();
|
||||
minioUploadFIleInfo.setFilename(filename);
|
||||
minioUploadFIleInfo.setUuidFilename(uuidFilename);
|
||||
minioUploadFIleInfo.setTimeUuidFilename(timeUuidFilename);
|
||||
minioUploadFIleInfo.setFilepath(filepath);
|
||||
minioUploadFIleInfo.setBucketNameFilepath(buckNameFilepath);
|
||||
|
||||
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(minioUploadFIleInfo.getFilepath()).stream(file.getInputStream(), file.getSize(), -1).build());
|
||||
|
||||
return minioUploadFIleInfo;
|
||||
} catch (Exception exception) {
|
||||
throw new AuthCustomerException(ResultCodeEnum.UPLOAD_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认存储桶中的文件字节数组
|
||||
*
|
||||
* <p><b>注意:</b>会一次性读取全部文件内容到内存,大文件慎用</p>
|
||||
*
|
||||
* @param objectName 对象全路径名称(包含目录路径)
|
||||
* @return 文件内容的字节数组
|
||||
* @throws AuthCustomerException 当获取失败时抛出(错误码:{@link ResultCodeEnum#GET_BUCKET_EXCEPTION})
|
||||
*/
|
||||
public byte[] getBucketObjectByte(String objectName) {
|
||||
String bucketName = properties.getBucketName();
|
||||
|
||||
try {
|
||||
GetObjectResponse getObjectResponse = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
|
||||
|
||||
return getObjectResponse.readAllBytes();
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
throw new AuthCustomerException(ResultCodeEnum.GET_BUCKET_EXCEPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到指定存储桶
|
||||
*
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filename 对象存储路径(包含文件名)
|
||||
* @param inputStream 文件输入流(调用方负责关闭)
|
||||
* @param size 文件大小(字节)
|
||||
* @throws AuthCustomerException 当上传失败时抛出(错误码:{@link ResultCodeEnum#UPLOAD_ERROR})
|
||||
*/
|
||||
public void putObject(String bucketName, String filename, InputStream inputStream, Long size) {
|
||||
try {
|
||||
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(filename).stream(inputStream, size, -1).build());
|
||||
} catch (Exception exception) {
|
||||
log.error("上传文件失败:{}", (Object) exception.getStackTrace());
|
||||
throw new AuthCustomerException(ResultCodeEnum.UPLOAD_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除存储桶中的对象文件
|
||||
*
|
||||
* @param list 要删除的对象路径列表
|
||||
* @throws AuthCustomerException 当删除失败时抛出(包含错误对象信息)
|
||||
*/
|
||||
public void removeObjects(List<String> list) {
|
||||
try {
|
||||
String bucketName = properties.getBucketName();
|
||||
List<DeleteObject> objectList = list.stream().map(DeleteObject::new).toList();
|
||||
|
||||
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objectList).build());
|
||||
for (Result<DeleteError> result : results) {
|
||||
DeleteError error = result.get();
|
||||
throw new AuthCustomerException("Error in deleting object " + error.objectName() + "; " + error.message());
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新存储桶中的文件(覆盖写入)
|
||||
*
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filepath 对象存储路径
|
||||
* @param file 新的文件对象
|
||||
* @throws AuthCustomerException 当更新失败时抛出
|
||||
*/
|
||||
public void updateFile(String bucketName, String filepath, MultipartFile file) {
|
||||
try {
|
||||
putObject(bucketName, filepath, file.getInputStream(), file.getSize());
|
||||
} catch (IOException e) {
|
||||
throw new AuthCustomerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并创建存储桶(如果不存在)
|
||||
*
|
||||
* @param bucketName 存储桶名称
|
||||
* @return true-已存在,false-新创建
|
||||
* @throws AuthCustomerException 当操作失败时抛出
|
||||
*/
|
||||
public boolean createBucketIfNotExists(String bucketName) {
|
||||
boolean found = false;
|
||||
try {
|
||||
found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
|
||||
// 如果 bucket 不存在就创建
|
||||
if (!found) makeBucket(bucketName);
|
||||
|
||||
return found;
|
||||
} catch (Exception exception) {
|
||||
log.error("判断桶是否存在 ------ 失败消息:{}", exception.getLocalizedMessage());
|
||||
exception.getStackTrace();
|
||||
}
|
||||
throw new AuthCustomerException("未初始化 bucket");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的存储桶
|
||||
*
|
||||
* @param bucketName 存储桶名称(需符合命名规范)
|
||||
* @throws AuthCustomerException 当创建失败时抛出
|
||||
*/
|
||||
public void makeBucket(String bucketName) {
|
||||
try {
|
||||
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
||||
} catch (Exception exception) {
|
||||
exception.getStackTrace();
|
||||
throw new AuthCustomerException("创建失败");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class MinioTest {
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM-dd");
|
||||
String date = formatter.format(new Date());
|
||||
System.out.println(date);
|
||||
}
|
||||
}
|
|
@ -29,6 +29,24 @@
|
|||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 单元测试 -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-java</artifactId>
|
||||
<version>4.30.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- asp 切面 -->
|
||||
<dependency>
|
||||
|
@ -39,12 +57,12 @@
|
|||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>org.springframework.boot</groupId> -->
|
||||
<!-- <artifactId>spring-boot-devtools</artifactId> -->
|
||||
<!-- <scope>runtime</scope> -->
|
||||
<!-- <optional>true</optional> -->
|
||||
<!-- </dependency> -->
|
||||
|
||||
<!-- quartz -->
|
||||
<dependency>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cn.bunny.services.aop.scanner;
|
||||
|
||||
import cn.bunny.services.domain.common.model.dto.scanner.ControllerInfo;
|
||||
import cn.bunny.services.domain.common.model.dto.scanner.MethodInfo;
|
||||
import cn.bunny.services.domain.common.model.dto.scanner.ScannerControllerInfoVo;
|
||||
import cn.bunny.services.domain.common.scanner.ControllerInfo;
|
||||
import cn.bunny.services.domain.common.scanner.MethodInfo;
|
||||
import cn.bunny.services.domain.common.scanner.ScannerControllerInfoVo;
|
||||
import cn.bunny.services.security.config.WebSecurityConfig;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
|
|
@ -6,7 +6,7 @@ import cn.bunny.services.domain.system.system.entity.Role;
|
|||
import cn.bunny.services.mapper.system.PermissionMapper;
|
||||
import cn.bunny.services.mapper.system.RoleMapper;
|
||||
import cn.bunny.services.security.config.WebSecurityConfig;
|
||||
import cn.bunny.services.service.system.helper.role.RoleHelper;
|
||||
import cn.bunny.services.utils.system.RoleUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
@ -44,7 +44,7 @@ public class PermissionCheckService {
|
|||
List<String> roleCodeList = roleList.stream().map(Role::getRoleCode).toList();
|
||||
|
||||
// 判断是否是管理员用户
|
||||
boolean checkedAdmin = RoleHelper.checkAdmin(roleCodeList);
|
||||
boolean checkedAdmin = RoleUtil.checkAdmin(roleCodeList);
|
||||
if (checkedAdmin) return true;
|
||||
|
||||
// 判断请求地址是否是登录之后才需要访问的,已经登录了不需要验证的
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cn.bunny.services.service.configuration.impl;
|
||||
|
||||
import cn.bunny.services.domain.common.constant.FileType;
|
||||
import cn.bunny.services.domain.common.model.dto.excel.I18nExcel;
|
||||
import cn.bunny.services.domain.common.model.entity.BaseEntity;
|
||||
import cn.bunny.services.domain.common.model.dto.excel.I18nExcel;
|
||||
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.i18n.dto.I18nAddDto;
|
||||
|
@ -17,8 +17,8 @@ import cn.bunny.services.exception.AuthCustomerException;
|
|||
import cn.bunny.services.mapper.configuration.I18nMapper;
|
||||
import cn.bunny.services.mapper.configuration.I18nTypeMapper;
|
||||
import cn.bunny.services.service.configuration.I18nService;
|
||||
import cn.bunny.services.service.configuration.helper.i18n.I18nHelper;
|
||||
import cn.bunny.services.utils.FileUtil;
|
||||
import cn.bunny.services.utils.i8n.I18nUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
|
@ -76,7 +76,7 @@ public class I18nServiceImpl extends ServiceImpl<I18nMapper, I18n> implements I1
|
|||
I18nType i18nType = i18nTypeMapper.selectOne(Wrappers.<I18nType>lambdaQuery().eq(I18nType::getIsDefault, true));
|
||||
List<I18n> i18nList = list();
|
||||
|
||||
HashMap<String, Object> hashMap = I18nHelper.getMapByI18nList(i18nList);
|
||||
HashMap<String, Object> hashMap = I18nUtil.getMap(i18nList);
|
||||
hashMap.put("local", Objects.requireNonNull(i18nType.getTypeName(), "zh"));
|
||||
|
||||
return hashMap;
|
||||
|
@ -170,21 +170,22 @@ public class I18nServiceImpl extends ServiceImpl<I18nMapper, I18n> implements I1
|
|||
|
||||
// 类型是Excel写入Excel
|
||||
if (type.equals(FileType.EXCEL)) {
|
||||
I18nHelper.writeExcel(i18nList, zipOutputStream);
|
||||
I18nUtil.writeExcel(i18nList, zipOutputStream);
|
||||
}
|
||||
// 其他格式写入JSON
|
||||
else {
|
||||
I18nHelper.writeJson(i18nList, zipOutputStream);
|
||||
I18nUtil.writeJson(i18nList, zipOutputStream);
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
HttpHeaders headers = FileUtil.buildHttpHeadersByBinary("i18n-configuration.zip");
|
||||
|
||||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||
return new ResponseEntity<>(byteArrayInputStream.readAllBytes(), headers, HttpStatus.OK);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
HttpHeaders headers = FileUtil.buildHttpHeadersByBinary("i18n-configuration.zip");
|
||||
|
||||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||
return new ResponseEntity<>(byteArrayInputStream.readAllBytes(), headers, HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
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;
|
||||
|
@ -10,10 +7,13 @@ 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 MinioHelper minioHelper;
|
||||
private UserUtil userUtil;
|
||||
|
||||
/**
|
||||
* 管理员管理用户消息接收分页查询
|
||||
|
@ -58,7 +58,7 @@ public class MessageReceivedServiceImpl extends ServiceImpl<MessageReceivedMappe
|
|||
|
||||
// 设置封面返回内容
|
||||
String cover = vo.getCover();
|
||||
cover = minioHelper.getUserAvatar(cover);
|
||||
cover = userUtil.checkGetUserAvatar(cover);
|
||||
vo.setCover(cover);
|
||||
return vo;
|
||||
}).toList();
|
||||
|
@ -113,7 +113,7 @@ public class MessageReceivedServiceImpl extends ServiceImpl<MessageReceivedMappe
|
|||
|
||||
// 设置封面返回内容
|
||||
String cover = vo.getCover();
|
||||
cover = minioHelper.getUserAvatar(cover);
|
||||
cover = userUtil.checkGetUserAvatar(cover);
|
||||
vo.setCover(cover);
|
||||
return vo;
|
||||
}).toList();
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
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;
|
||||
|
@ -13,13 +10,16 @@ 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,14 +47,17 @@ 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;
|
||||
|
||||
/**
|
||||
* 分页查询发送消息
|
||||
|
@ -149,7 +152,7 @@ public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> impl
|
|||
|
||||
// 设置封面返回内容
|
||||
String cover = dto.getCover();
|
||||
dto.setCover(minioHelper.getUserAvatar(cover));
|
||||
dto.setCover(userUtil.checkGetUserAvatar(cover));
|
||||
|
||||
// 先保存消息数据,之后拿到保存消息的id
|
||||
Message message = new Message();
|
||||
|
@ -195,7 +198,7 @@ public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> impl
|
|||
|
||||
// 设置封面返回内容
|
||||
String cover = dto.getCover();
|
||||
dto.setCover(minioHelper.getUserAvatar(cover));
|
||||
dto.setCover(userUtil.checkGetUserAvatar(cover));
|
||||
|
||||
// 更新内容
|
||||
Message message = new Message();
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
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.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.dto.user.*;
|
||||
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,6 +24,14 @@ import java.util.List;
|
|||
*/
|
||||
public interface UserService extends IService<AdminUser> {
|
||||
|
||||
/**
|
||||
* 前台用户登录接口
|
||||
*
|
||||
* @param loginDto 登录参数
|
||||
* @return 登录后结果返回
|
||||
*/
|
||||
LoginVo login(LoginDto loginDto);
|
||||
|
||||
/**
|
||||
* * 获取用户信息列表
|
||||
*
|
||||
|
@ -52,6 +60,27 @@ 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();
|
||||
|
||||
/**
|
||||
* * 获取用户信息
|
||||
*
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
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.service.system.helper.role.RoleHelper;
|
||||
import cn.bunny.services.utils.IpUtil;
|
||||
import cn.bunny.services.utils.JwtTokenUtil;
|
||||
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 = RoleHelper.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,124 +0,0 @@
|
|||
package cn.bunny.services.service.system.helper.role;
|
||||
|
||||
import cn.bunny.services.context.BaseContext;
|
||||
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 jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class RoleHelper {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
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 用户角色编码列表(可能包含"admin"角色)
|
||||
* @param permissions 用户权限列表(用于前端按钮控制)
|
||||
* @param adminUser 用户实体对象(需包含userId字段)
|
||||
* @return true-是管理员,false-非管理员
|
||||
*/
|
||||
public static boolean checkAdmin(List<String> roleList, List<String> permissions, AdminUser adminUser) {
|
||||
// 判断是否是超级管理员
|
||||
boolean isIdAdmin = adminUser.getId().equals(1L);
|
||||
boolean isAdmin = roleList.stream().anyMatch(role -> role.equals("admin"));
|
||||
|
||||
// 判断是否是 admin
|
||||
if (isIdAdmin || isAdmin) {
|
||||
roleList.add("admin");
|
||||
permissions.add("*");
|
||||
permissions.add("*::*");
|
||||
permissions.add("*::*::*");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是管理员
|
||||
*
|
||||
* @param roleList 角色代码列表
|
||||
* @return 是否是管理员
|
||||
*/
|
||||
public static boolean checkAdmin(List<String> roleList) {
|
||||
// 可以放行的权限
|
||||
List<String> permissionList = SecurityConfigConstant.PERMIT_ACCESS_LIST;
|
||||
|
||||
// 判断是否是超级管理员
|
||||
if (BaseContext.getUserId().equals(1L)) return true;
|
||||
|
||||
// 判断是否是 admin
|
||||
return roleList.stream().anyMatch(permissionList::contains);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新Redis中用户权限信息
|
||||
*
|
||||
* <p><b>使用场景</b>:当用户角色或权限变更时,同步更新Redis中的用户权限数据</p>
|
||||
*
|
||||
* <p><b>实现策略</b>:</p>
|
||||
* <ol>
|
||||
* <li><b>主动更新(当前实现)</b>:重新构建用户权限信息并更新Redis缓存</li>
|
||||
* <li><b>强制下线</b>:删除用户登录态,强制重新认证获取最新权限</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>技术实现</b>:</p>
|
||||
* <ul>
|
||||
* <li>采用Spring事件驱动机制触发更新</li>
|
||||
* <li>使用并行流(parallelStream)提高批量处理效率</li>
|
||||
* <li>仅更新Redis中存在登录态的用户</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param userIds 需要更新的用户ID集合
|
||||
* (仅处理集合中存在的有效用户)
|
||||
* @see RedisUserConstant Redis键前缀常量
|
||||
* @see UserLoginHelper#buildLoginUserVo 用户登录信息构建方法
|
||||
*/
|
||||
public void updateUserRedisInfo(List<Long> userIds) {
|
||||
if (userIds.isEmpty()) return;
|
||||
|
||||
// 批量查询用户
|
||||
List<AdminUser> adminUsers = userMapper.selectBatchIds(userIds);
|
||||
|
||||
// 并行处理用户更新
|
||||
adminUsers.parallelStream()
|
||||
.filter(user -> redisTemplate.hasKey(RedisUserConstant.getAdminLoginInfoPrefix(user.getUsername())))
|
||||
.forEach(user -> {
|
||||
// 策略1: 更新用户权限信息
|
||||
userloginHelper.buildLoginUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME);
|
||||
|
||||
// 或者策略2: 强制用户下线
|
||||
// redisTemplate.delete(RedisUserConstant.getAdminLoginInfoPrefix(user.getUsername()));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package cn.bunny.services.service.system.helper.role;
|
||||
|
||||
import cn.bunny.services.domain.system.system.entity.UserRole;
|
||||
import cn.bunny.services.mapper.system.UserRoleMapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色更新事件处理器
|
||||
*
|
||||
* <p><b>职责说明</b>:监听并处理角色变更事件,同步更新关联用户的权限信息</p>
|
||||
*
|
||||
* <p><b>处理流程</b>:</p>
|
||||
* <ol>
|
||||
* <li>监听{@code RoleUpdatedEvent}事件</li>
|
||||
* <li>查询关联该角色的所有用户ID</li>
|
||||
* <li>通过{@code RoleHelper}批量更新用户Redis缓存</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>注意事项</b>:</p>
|
||||
* <ul>
|
||||
* <li>使用异步处理(@{@link Async})避免阻塞主线程</li>
|
||||
* <li>仅处理直接关联的用户,不包含通过用户组等间接关联的情况</li>
|
||||
* <li>更新操作采用批量处理提高效率</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Component
|
||||
public class RoleUpdateHandler {
|
||||
@Resource
|
||||
private UserRoleMapper userRoleMapper;
|
||||
|
||||
@Resource
|
||||
private RoleHelper roleHelper;
|
||||
|
||||
/**
|
||||
* 处理角色更新事件
|
||||
*
|
||||
* @param event 角色更新事件,包含变更的角色ID
|
||||
* @see RoleUpdatedEvent 角色更新事件定义
|
||||
* @see RoleHelper#updateUserRedisInfo 用户缓存更新方法
|
||||
*/
|
||||
@Async
|
||||
@EventListener
|
||||
public void handleRoleUpdatedEvent(RoleUpdatedEvent event) {
|
||||
// 查询当前用户角色中角色id
|
||||
LambdaQueryWrapper<UserRole> lambdaQueryWrapper = Wrappers.<UserRole>lambdaQuery().eq(UserRole::getRoleId, event.getRoleId());
|
||||
|
||||
// 批量查询关联用户ID
|
||||
List<UserRole> userRoles = userRoleMapper.selectList(lambdaQueryWrapper);
|
||||
List<Long> userIds = userRoles.stream().map(UserRole::getUserId).toList();
|
||||
roleHelper.updateUserRedisInfo(userIds);
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package cn.bunny.services.service.system.helper.role;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
// 角色更新事件
|
||||
@Getter
|
||||
@Setter
|
||||
public class RoleUpdatedEvent extends ApplicationEvent {
|
||||
private final Long roleId;
|
||||
|
||||
public RoleUpdatedEvent(Object source, Long roleId) {
|
||||
super(source);
|
||||
this.roleId = roleId;
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
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.minio.MinioUploadFileInfo;
|
||||
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;
|
||||
|
@ -13,9 +15,6 @@ 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;
|
||||
|
@ -60,15 +59,11 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
|
|||
private MinioProperties properties;
|
||||
|
||||
@Resource
|
||||
private MinioService minioService;
|
||||
|
||||
@Resource
|
||||
private MinioHelper minioHelper;
|
||||
private MinioUtil minioUtil;
|
||||
|
||||
@Resource
|
||||
private FilesMapper filesMapper;
|
||||
|
||||
|
||||
/**
|
||||
* * 系统文件表 服务实现类
|
||||
*
|
||||
|
@ -81,8 +76,10 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
|
|||
IPage<FilesVo> page = baseMapper.selectListByPage(pageParams, dto);
|
||||
|
||||
return PageResult.<FilesVo>builder()
|
||||
.list(page.getRecords()).pageNo(page.getCurrent())
|
||||
.pageSize(page.getSize()).total(page.getTotal())
|
||||
.list(page.getRecords())
|
||||
.pageNo(page.getCurrent())
|
||||
.pageSize(page.getSize())
|
||||
.total(page.getTotal())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -95,13 +92,13 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
|
|||
public void addFiles(FilesAddDto dto) {
|
||||
List<Files> list = dto.getFiles().stream().map(file -> {
|
||||
try {
|
||||
MinioUploadFileInfo minioUploadFileInfo = minioService.uploadWithFileInfo(file, dto.getFilepath());
|
||||
MinioFilePath minioFilePath = minioUtil.uploadObjectReturnFilePath(file, dto.getFilepath());
|
||||
|
||||
Files files = new Files();
|
||||
files.setFileType(file.getContentType());
|
||||
files.setFileSize(file.getSize());
|
||||
files.setFilepath("/" + properties.getBucketName() + minioUploadFileInfo.getFilepath());
|
||||
files.setFilename(minioUploadFileInfo.getFilename());
|
||||
files.setFilepath("/" + properties.getBucketName() + minioFilePath.getFilepath());
|
||||
files.setFilename(minioFilePath.getFilename());
|
||||
files.setDownloadCount(dto.getDownloadCount());
|
||||
return files;
|
||||
} catch (IOException e) {
|
||||
|
@ -109,6 +106,7 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
|
|||
}
|
||||
}).toList();
|
||||
|
||||
// 保存数据
|
||||
saveBatch(list);
|
||||
}
|
||||
|
||||
|
@ -126,7 +124,7 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
|
|||
if (file != null) {
|
||||
// 文件路径
|
||||
String filePath = files.getFilepath().replace("/" + properties.getBucketName() + "/", "");
|
||||
minioService.updateFile(properties.getBucketName(), filePath, file);
|
||||
minioUtil.updateFile(properties.getBucketName(), filePath, file);
|
||||
|
||||
// 设置文件信息
|
||||
files.setFileSize(file.getSize());
|
||||
|
@ -162,8 +160,8 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
|
|||
String filename = file.getOriginalFilename();
|
||||
|
||||
// 上传文件
|
||||
MinioUploadFileInfo minioUploadFIleInfo = minioService.uploadWithFileInfo(file, type);
|
||||
String bucketNameFilepath = minioUploadFIleInfo.getBucketNameFilepath();
|
||||
MinioFilePath minioFIlePath = minioUtil.uploadObjectReturnFilePath(file, type);
|
||||
String bucketNameFilepath = minioFIlePath.getBucketNameFilepath();
|
||||
|
||||
// 盘读研数据是否过大
|
||||
String mb = maxFileSize.replace("MB", "");
|
||||
|
@ -180,9 +178,12 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
|
|||
|
||||
// 返回信息内容化
|
||||
return FileInfoVo.builder()
|
||||
.size(FileUtil.getSize(fileSize)).filepath(bucketNameFilepath)
|
||||
.fileSize(fileSize).fileType(contentType)
|
||||
.filename(filename).url(minioHelper.getObjectNameFullPath(bucketNameFilepath))
|
||||
.size(FileUtil.getSize(fileSize))
|
||||
.filepath(bucketNameFilepath)
|
||||
.fileSize(fileSize)
|
||||
.fileType(contentType)
|
||||
.filename(filename)
|
||||
.url(minioUtil.getObjectNameFullPath(bucketNameFilepath))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -204,7 +205,7 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
|
|||
}).toList();
|
||||
|
||||
// 删除目标文件
|
||||
minioService.removeObjects(list);
|
||||
minioUtil.removeObjects(list);
|
||||
|
||||
// 删除数据库内容
|
||||
removeByIds(ids);
|
||||
|
@ -228,7 +229,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 = minioService.getBucketObjectByte(filepath);
|
||||
byte[] bytes = minioUtil.getBucketObjectByte(filepath);
|
||||
|
||||
// 设置响应头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
|
|
@ -15,8 +15,8 @@ import cn.bunny.services.exception.AuthCustomerException;
|
|||
import cn.bunny.services.mapper.system.PermissionMapper;
|
||||
import cn.bunny.services.mapper.system.RolePermissionMapper;
|
||||
import cn.bunny.services.service.system.PermissionService;
|
||||
import cn.bunny.services.service.system.helper.PermissionHelper;
|
||||
import cn.bunny.services.utils.FileUtil;
|
||||
import cn.bunny.services.utils.system.PermissionUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
|
@ -187,7 +187,7 @@ public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permiss
|
|||
}).toList();
|
||||
|
||||
// 构建树型结构
|
||||
List<PermissionExcel> buildTree = PermissionHelper.buildTree(permissionExcelList);
|
||||
List<PermissionExcel> buildTree = PermissionUtil.buildTree(permissionExcelList);
|
||||
|
||||
// 创建btye输出流
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
|
@ -197,9 +197,9 @@ public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permiss
|
|||
|
||||
// 判断导出类型是什么
|
||||
if (type.equals(FileType.EXCEL)) {
|
||||
PermissionHelper.writExcel(permissionExcelList, zipOutputStream, filename + ".xlsx");
|
||||
PermissionUtil.writExcel(permissionExcelList, zipOutputStream, filename + ".xlsx");
|
||||
} else {
|
||||
PermissionHelper.writeJson(buildTree, zipOutputStream, filename + ".json");
|
||||
PermissionUtil.writeJson(buildTree, zipOutputStream, filename + ".json");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -235,7 +235,7 @@ public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permiss
|
|||
List<PermissionExcel> list = JSON.parseObject(json, new TypeReference<>() {
|
||||
});
|
||||
// 格式化数据,保存到数据库
|
||||
List<PermissionExcel> flattenedTree = PermissionHelper.flattenTree(list);
|
||||
List<PermissionExcel> flattenedTree = PermissionUtil.flattenTree(list);
|
||||
List<Permission> permissionList = flattenedTree.stream().map(permissionExcel -> {
|
||||
Permission permission = new Permission();
|
||||
BeanUtils.copyProperties(permissionExcel, permission);
|
||||
|
|
|
@ -8,7 +8,7 @@ import cn.bunny.services.mapper.system.RolePermissionMapper;
|
|||
import cn.bunny.services.mapper.system.UserMapper;
|
||||
import cn.bunny.services.mapper.system.UserRoleMapper;
|
||||
import cn.bunny.services.service.system.RolePermissionService;
|
||||
import cn.bunny.services.service.system.helper.role.RoleHelper;
|
||||
import cn.bunny.services.utils.system.RoleUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
|
@ -33,7 +33,7 @@ public class RolePermissionServiceImpl extends ServiceImpl<RolePermissionMapper,
|
|||
private UserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
private RoleHelper roleHelper;
|
||||
private RoleUtil roleUtil;
|
||||
|
||||
@Resource
|
||||
private UserRoleMapper userRoleMapper;
|
||||
|
@ -86,6 +86,6 @@ public class RolePermissionServiceImpl extends ServiceImpl<RolePermissionMapper,
|
|||
|
||||
// 更新Redis中用户信息
|
||||
List<Long> userIds = adminUsers.stream().map(AdminUser::getId).toList();
|
||||
roleHelper.updateUserRedisInfo(userIds);
|
||||
roleUtil.updateUserRedisInfo(userIds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ import cn.bunny.services.mapper.system.RolePermissionMapper;
|
|||
import cn.bunny.services.mapper.system.RouterRoleMapper;
|
||||
import cn.bunny.services.mapper.system.UserRoleMapper;
|
||||
import cn.bunny.services.service.system.RoleService;
|
||||
import cn.bunny.services.service.system.helper.role.RoleUpdatedEvent;
|
||||
import cn.bunny.services.utils.FileUtil;
|
||||
import cn.bunny.services.utils.system.RoleUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
|
@ -28,7 +28,6 @@ import jakarta.validation.Valid;
|
|||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
@ -68,7 +67,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
|||
private RouterRoleMapper routerRoleMapper;
|
||||
|
||||
@Resource
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
private RoleUtil roleUtil;
|
||||
|
||||
/**
|
||||
* * 角色 服务实现类
|
||||
|
@ -95,7 +94,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
|||
* @return 所有角色列表
|
||||
*/
|
||||
@Override
|
||||
@Cacheable(cacheNames = "role", key = "'roleList'", cacheManager = "cacheManagerWithMouth")
|
||||
@Cacheable(cacheNames = "role", key = "'allRole'", cacheManager = "cacheManagerWithMouth")
|
||||
public List<RoleVo> roleList() {
|
||||
return list().stream().map(role -> {
|
||||
RoleVo roleVo = new RoleVo();
|
||||
|
@ -156,7 +155,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
|||
* @param file Excel文件
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = "role", key = "'roleList'", beforeInvocation = true)
|
||||
@CacheEvict(cacheNames = "role", key = "'allRole'", beforeInvocation = true)
|
||||
public void updateRoleByFile(MultipartFile file) {
|
||||
if (file == null) {
|
||||
throw new AuthCustomerException(ResultCodeEnum.REQUEST_IS_EMPTY);
|
||||
|
@ -177,7 +176,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
|||
* @param dto 角色添加
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = "role", key = "'roleList'", beforeInvocation = true)
|
||||
@CacheEvict(cacheNames = "role", key = "'allRole'", beforeInvocation = true)
|
||||
public void addRole(@Valid RoleAddDto dto) {
|
||||
Role role = new Role();
|
||||
BeanUtils.copyProperties(dto, role);
|
||||
|
@ -190,7 +189,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
|||
* @param dto 角色更新
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = "role", key = "'roleList'", beforeInvocation = true)
|
||||
@CacheEvict(cacheNames = "role", key = "'allRole'", beforeInvocation = true)
|
||||
public void updateRole(@Valid RoleUpdateDto dto) {
|
||||
// 查询更新的角色是否存在
|
||||
List<Role> roleList = list(Wrappers.<Role>lambdaQuery().eq(Role::getId, dto.getId()));
|
||||
|
@ -204,10 +203,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
|||
// 找到所有和当前更新角色相同的用户,并更新Redis中用户信息
|
||||
List<Long> userIds = userRoleMapper.selectList(Wrappers.<UserRole>lambdaQuery().eq(UserRole::getRoleId, dto.getId()))
|
||||
.stream().map(UserRole::getUserId).toList();
|
||||
// TODO 1
|
||||
// roleUtil.updateUserRedisInfo(userIds);
|
||||
// 发布角色更新事件
|
||||
eventPublisher.publishEvent(new RoleUpdatedEvent(this, dto.getId()));
|
||||
roleUtil.updateUserRedisInfo(userIds);
|
||||
}
|
||||
|
||||
|
||||
|
@ -217,7 +213,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
|||
* @param ids 删除id列表
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = "role", key = "'roleList'", beforeInvocation = true)
|
||||
@CacheEvict(cacheNames = "role", key = "'allRole'", beforeInvocation = true)
|
||||
public void deleteRole(List<Long> ids) {
|
||||
// 判断数据请求是否为空
|
||||
if (ids.isEmpty()) throw new AuthCustomerException(ResultCodeEnum.REQUEST_IS_EMPTY);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package cn.bunny.services.service.system.impl;
|
||||
|
||||
import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum;
|
||||
import cn.bunny.services.domain.system.system.dto.router.RouterAddDto;
|
||||
import cn.bunny.services.domain.system.system.dto.router.RouterUpdateDto;
|
||||
import cn.bunny.services.domain.system.system.entity.router.Router;
|
||||
|
@ -11,12 +10,13 @@ import cn.bunny.services.domain.system.system.views.ViewRouterRole;
|
|||
import cn.bunny.services.domain.system.system.vo.router.RouterManageVo;
|
||||
import cn.bunny.services.domain.system.system.vo.router.RouterVo;
|
||||
import cn.bunny.services.domain.system.system.vo.router.WebUserRouterVo;
|
||||
import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum;
|
||||
import cn.bunny.services.exception.AuthCustomerException;
|
||||
import cn.bunny.services.mapper.system.RolePermissionMapper;
|
||||
import cn.bunny.services.mapper.system.RouterMapper;
|
||||
import cn.bunny.services.mapper.system.RouterRoleMapper;
|
||||
import cn.bunny.services.service.system.RouterService;
|
||||
import cn.bunny.services.service.system.helper.RouterHelper;
|
||||
import cn.bunny.services.utils.system.RouterUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
@ -48,7 +48,7 @@ public class RouterServiceImpl extends ServiceImpl<RouterMapper, Router> impleme
|
|||
private RouterRoleMapper routerRoleMapper;
|
||||
|
||||
@Resource
|
||||
private RouterHelper routerHelper;
|
||||
private RouterUtil routerUtil;
|
||||
|
||||
@Resource
|
||||
private RolePermissionMapper rolePermissionMapper;
|
||||
|
@ -75,13 +75,13 @@ public class RouterServiceImpl extends ServiceImpl<RouterMapper, Router> impleme
|
|||
.collect(Collectors.groupingBy(ViewRolePermission::getRoleId, Collectors.toList()));
|
||||
|
||||
// 整理web用户所能看到的路由列表,并检查当前用户是否是admin
|
||||
List<WebUserRouterVo> webUserRouterVoList = routerHelper.getWebUserRouterVos(routerList, routerRoleList, rolePermissionList);
|
||||
List<WebUserRouterVo> webUserRouterVoList = routerUtil.getWebUserRouterVos(routerList, routerRoleList, rolePermissionList);
|
||||
|
||||
// 添加 admin 管理路由权限
|
||||
webUserRouterVoList.forEach(routerVo -> {
|
||||
// 递归添加路由节点
|
||||
if (routerVo.getParentId() == 0) {
|
||||
routerVo.setChildren(routerHelper.buildTreeSetChildren(routerVo.getId(), webUserRouterVoList));
|
||||
routerVo.setChildren(routerUtil.buildTreeSetChildren(routerVo.getId(), webUserRouterVoList));
|
||||
voList.add(routerVo);
|
||||
}
|
||||
});
|
||||
|
@ -141,7 +141,7 @@ public class RouterServiceImpl extends ServiceImpl<RouterMapper, Router> impleme
|
|||
|
||||
// 将数据提出role 和 power 存储到数据库
|
||||
Long id = router.getId();
|
||||
routerHelper.insertRouterRoleAndPermission(meta, id);
|
||||
routerUtil.insertRouterRoleAndPermission(meta, id);
|
||||
|
||||
// 添加路由
|
||||
save(router);
|
||||
|
@ -169,7 +169,7 @@ public class RouterServiceImpl extends ServiceImpl<RouterMapper, Router> impleme
|
|||
routerRoleMapper.deleteBatchIdsByRouterIds(List.of(id));
|
||||
|
||||
// 将数据提出role 和 power 存储到数据库
|
||||
routerHelper.insertRouterRoleAndPermission(meta, id);
|
||||
routerUtil.insertRouterRoleAndPermission(meta, id);
|
||||
|
||||
// 更新路由信息
|
||||
updateById(router);
|
||||
|
|
|
@ -1,201 +0,0 @@
|
|||
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.configuration.helper.email.ConcreteSenderEmailTemplate;
|
||||
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.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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送登录邮件验证码
|
||||
*
|
||||
* <p>完整处理流程:</p>
|
||||
* <ol>
|
||||
* <li><b>查询模板</b>:从数据库获取默认的验证码邮件模板</li>
|
||||
* <li><b>生成验证码</b>:创建4位数字验证码</li>
|
||||
* <li><b>模板处理</b>:替换模板中的动态变量(系统名称、验证码等)</li>
|
||||
* <li><b>发送邮件</b>:通过邮件服务发送处理后的模板</li>
|
||||
* <li><b>缓存验证码</b>:将验证码存入Redis 有效期 xxx</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param email 接收邮箱地址(不可为空)
|
||||
* <ul>
|
||||
* <li>未找到默认邮件模板</li>
|
||||
* <li>邮件发送失败</li>
|
||||
* <li>Redis操作异常</li>
|
||||
* </ul>
|
||||
* @see EmailTemplateEnums#VERIFICATION_CODE 验证码模板类型枚举
|
||||
* @see RedisUserConstant Redis键和过期时间常量
|
||||
*/
|
||||
@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中存储验证码
|
||||
String emailCodePrefix = RedisUserConstant.getAdminUserEmailCodePrefix(email);
|
||||
redisTemplate.opsForValue().set(emailCodePrefix, 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.service.system.helper.UserLoginHelper;
|
||||
import cn.bunny.services.utils.system.UserUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
|
@ -34,12 +34,15 @@ import java.util.concurrent.TimeUnit;
|
|||
@Transactional
|
||||
public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> implements UserRoleService {
|
||||
|
||||
@Resource
|
||||
private UserLoginHelper userloginHelper;
|
||||
@Resource
|
||||
private UserRoleMapper userRoleMapper;
|
||||
|
||||
@Resource
|
||||
private UserUtil userUtil;
|
||||
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
|
@ -91,7 +94,7 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> i
|
|||
|
||||
// 重新设置Redis中的用户存储信息vo对象
|
||||
String username = adminUser.getUsername();
|
||||
loginVo = userloginHelper.buildLoginUserVo(adminUser, readMeDay);
|
||||
loginVo = userUtil.buildLoginUserVo(adminUser, readMeDay);
|
||||
redisTemplate.opsForValue().set(RedisUserConstant.getAdminLoginInfoPrefix(username), loginVo, readMeDay, TimeUnit.DAYS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,40 @@
|
|||
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.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.PageResult;
|
||||
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.email.entity.EmailTemplate;
|
||||
import cn.bunny.services.domain.system.log.entity.UserLoginLog;
|
||||
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.dto.user.*;
|
||||
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.service.system.helper.UserLoginHelper;
|
||||
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 com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
|
@ -36,15 +42,18 @@ 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;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -58,23 +67,150 @@ import java.util.List;
|
|||
public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implements UserService {
|
||||
|
||||
@Resource
|
||||
private UserLoginHelper userloginHelper;
|
||||
private UserUtil userUtil;
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
private ConcreteSenderEmailTemplate concreteSenderEmailTemplate;
|
||||
@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 MinioHelper minioHelper;
|
||||
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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* * 获取用户信息
|
||||
|
@ -90,40 +226,20 @@ 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);
|
||||
|
||||
String userAvatar = minioHelper.getUserAvatar(avatar);
|
||||
userVo.setAvatar(userAvatar);
|
||||
userVo.setAvatar(userUtil.checkGetUserAvatar(avatar));
|
||||
return userVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员强制用户下线
|
||||
* * 强制退出
|
||||
*
|
||||
* <p><b>功能说明</b>:管理员强制指定用户退出登录状态,并记录操作日志</p>
|
||||
*
|
||||
* <p><b>处理流程</b>:</p>
|
||||
* <ol>
|
||||
* <li>参数校验:检查用户ID是否为空</li>
|
||||
* <li>查询用户信息:根据ID获取用户实体</li>
|
||||
* <li>记录操作日志:保存强制下线记录到用户登录日志表</li>
|
||||
* <li>清除登录状态:删除Redis中的用户登录信息</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>注意事项</b>:</p>
|
||||
* <ul>
|
||||
* <li>会中断用户当前会话,无需用户确认</li>
|
||||
* <li>操作会记录到用户登录日志,用于审计追踪</li>
|
||||
* <li>Redis键使用用户名作为唯一标识</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param id 用户ID(不可为空)
|
||||
* @see RedisUserConstant#getAdminLoginInfoPrefix Redis键生成规则
|
||||
* @see UserConstant#FORCE_LOGOUT 强制下线类型常量
|
||||
* @param id 用户id
|
||||
*/
|
||||
@Override
|
||||
public void forcedOfflineByAdmin(Long id) {
|
||||
|
@ -135,10 +251,11 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implemen
|
|||
|
||||
// 将用户登录保存在用户登录日志表中
|
||||
UserLoginLog userLoginLog = new UserLoginLog();
|
||||
BeanUtils.copyProperties(adminUser, userLoginLog);
|
||||
userLoginLog.setId(null);
|
||||
userLoginLog.setUserId(adminUser.getId());
|
||||
userLoginLog.setType(UserConstant.FORCE_LOGOUT);
|
||||
userLoginLog.setIpAddress(adminUser.getIpAddress());
|
||||
userLoginLog.setIpRegion(adminUser.getIpRegion());
|
||||
userLoginLog.setToken(null);
|
||||
userLoginLog.setType("forcedOffline");
|
||||
userLoginLogMapper.insert(userLoginLog);
|
||||
|
||||
// 删除Redis中用户信息
|
||||
|
@ -187,9 +304,8 @@ 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);
|
||||
|
||||
// 检查用户头像,因为更新用户信息会带着用户之前的信息,如果没有更新头像,前端显示的http:xxx
|
||||
String userAvatar = minioHelper.formatUserAvatar(dto.getAvatar());
|
||||
dto.setAvatar(userAvatar);
|
||||
// 检查用户头像
|
||||
dto.setAvatar(userUtil.checkPostUserAvatar(dto.getAvatar()));
|
||||
|
||||
// 更新用户
|
||||
AdminUser adminUser = new AdminUser();
|
||||
|
@ -199,7 +315,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implemen
|
|||
|
||||
// 重新生成用户信息到Redis中
|
||||
BeanUtils.copyProperties(dto, user);
|
||||
userloginHelper.buildLoginUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME);
|
||||
userUtil.buildUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -247,7 +363,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implemen
|
|||
List<AdminUserVo> voList = page.getRecords().stream()
|
||||
.map(adminUser -> {
|
||||
// 如果存在用户头像,则设置用户头像
|
||||
String avatar = minioHelper.getUserAvatar(adminUser.getAvatar());
|
||||
String avatar = userUtil.checkGetUserAvatar(adminUser.getAvatar());
|
||||
|
||||
AdminUserVo adminUserVo = new AdminUserVo();
|
||||
BeanUtils.copyProperties(adminUser, adminUserVo);
|
||||
|
@ -326,18 +442,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implemen
|
|||
}
|
||||
|
||||
// 更新头像
|
||||
uploadAvatarByAdmin(dto, adminUser);
|
||||
userUtil.uploadAvatarByAdmin(dto, adminUser);
|
||||
|
||||
// 构建用户返回信息,同步到redis
|
||||
userloginHelper.buildLoginUserVo(adminUser, RedisUserConstant.REDIS_EXPIRATION_TIME);
|
||||
userUtil.buildUserVo(adminUser, RedisUserConstant.REDIS_EXPIRATION_TIME);
|
||||
|
||||
// 更新密码,放在最后,如果更新密码就将密码删除
|
||||
updateUserPasswordByAdmin(adminUser);
|
||||
userUtil.updateUserPasswordByAdmin(adminUser);
|
||||
|
||||
updateById(adminUser);
|
||||
// 删除Redis中用户信息
|
||||
String loginInfoPrefix = RedisUserConstant.getAdminLoginInfoPrefix(adminUser.getUsername());
|
||||
redisTemplate.delete(loginInfoPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -358,56 +471,11 @@ 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,9 +1,9 @@
|
|||
package cn.bunny.services.service.configuration.helper.email;
|
||||
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;
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.services.service.configuration.helper.email;
|
||||
package cn.bunny.services.utils.email;
|
||||
|
||||
import cn.bunny.services.domain.system.email.entity.EmailTemplate;
|
||||
import cn.bunny.services.mapper.configuration.EmailTemplateMapper;
|
|
@ -1,8 +1,7 @@
|
|||
package cn.bunny.services.service.configuration.helper.i18n;
|
||||
package cn.bunny.services.utils.i8n;
|
||||
|
||||
import cn.bunny.services.domain.common.model.dto.excel.I18nExcel;
|
||||
import cn.bunny.services.domain.system.i18n.entity.I18n;
|
||||
import cn.bunny.services.service.configuration.impl.I18nServiceImpl;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@ -17,20 +16,46 @@ import java.util.stream.Collectors;
|
|||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class I18nHelper {
|
||||
public class I18nUtil {
|
||||
@NotNull
|
||||
public static HashMap<String, Object> getMap(@NotNull List<I18n> i18nList) {
|
||||
// 整理集合
|
||||
Map<String, Map<String, String>> map = i18nList.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
I18n::getTypeName,
|
||||
Collectors.toMap(I18n::getKeyName, I18n::getTranslation)));
|
||||
|
||||
// 返回集合
|
||||
return new HashMap<>(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将国际化资源列表写入Excel并打包到ZIP输出流
|
||||
* 使用zip写入json
|
||||
*
|
||||
* <p>处理流程:</p>
|
||||
* <ol>
|
||||
* <li>按资源类型(typeName)分组 --- 多语言的类型,英文?中文?</li>
|
||||
* <li>每组数据生成单独多语言 key的Excel工作表</li>
|
||||
* <li>将Excel文件写入ZIP输出流</li>
|
||||
* </ol>
|
||||
* @param i18nList i18nList
|
||||
* @param zipOutputStream zipOutputStream
|
||||
*/
|
||||
public static void writeJson(List<I18n> i18nList, ZipOutputStream zipOutputStream) {
|
||||
HashMap<String, Object> hashMap = getMap(i18nList);
|
||||
|
||||
hashMap.forEach((k, v) -> {
|
||||
try {
|
||||
ZipEntry zipEntry = new ZipEntry(k + ".json");
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
|
||||
zipOutputStream.write(JSON.toJSONString(v).getBytes(StandardCharsets.UTF_8));
|
||||
zipOutputStream.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用zip写入excel
|
||||
*
|
||||
* @param i18nList 国际化资源列表,包含key-value对和类型信息
|
||||
* @param zipOutputStream ZIP输出流,用于写入打包后的Excel文件
|
||||
* @throws RuntimeException 当IO操作失败时抛出
|
||||
* @param i18nList i18nList
|
||||
* @param zipOutputStream zipOutputStream
|
||||
*/
|
||||
public static void writeExcel(List<I18n> i18nList, ZipOutputStream zipOutputStream) {
|
||||
Map<String, List<I18nExcel>> hashMap = i18nList.stream()
|
||||
|
@ -61,62 +86,4 @@ public class I18nHelper {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将国际化资源列表写入JSON并打包到ZIP输出流
|
||||
*
|
||||
* <p>处理流程:</p>
|
||||
* <ol>
|
||||
* <li>按资源类型(typeName)分组 --- 多语言的类型,英文?中文?</li>
|
||||
* <li>每组数据生成单独多语言 key的Excel工作表</li>
|
||||
* <li>将JSON文件写入ZIP输出流</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param i18nList 国际化资源列表
|
||||
* @param zipOutputStream ZIP输出流
|
||||
* @throws RuntimeException 当IO操作失败时抛出
|
||||
*/
|
||||
public static void writeJson(List<I18n> i18nList, ZipOutputStream zipOutputStream) {
|
||||
HashMap<String, Object> hashMap = getMapByI18nList(i18nList);
|
||||
|
||||
hashMap.forEach((k, v) -> {
|
||||
try {
|
||||
ZipEntry zipEntry = new ZipEntry(k + ".json");
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
|
||||
zipOutputStream.write(JSON.toJSONString(v).getBytes(StandardCharsets.UTF_8));
|
||||
zipOutputStream.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将国际化资源列表转换为结构化Map
|
||||
*
|
||||
* <p>转换规则:</p>
|
||||
* <ul>
|
||||
* <li>外层Key: 资源类型(typeName)</li>
|
||||
* <li>内层Key: 资源键名(keyName)</li>
|
||||
* <li>值: 翻译文本(translation)</li>
|
||||
* </ul>
|
||||
* <p>详细结构和结果示例看前端传递的 {@link I18nServiceImpl#getI18nMap} 控制器</p>
|
||||
* <p>/api/i18n/public</p>
|
||||
*
|
||||
* @param i18nList 国际化资源列表
|
||||
* @return 结构化Map {typeName: {keyName: translation}}
|
||||
* @throws IllegalArgumentException 当参数为null时抛出
|
||||
*/
|
||||
@NotNull
|
||||
public static HashMap<String, Object> getMapByI18nList(@NotNull List<I18n> i18nList) {
|
||||
// 整理集合
|
||||
Map<String, Map<String, String>> map = i18nList.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
I18n::getTypeName,
|
||||
Collectors.toMap(I18n::getKeyName, I18n::getTranslation)));
|
||||
|
||||
// 返回集合
|
||||
return new HashMap<>(map);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.services.service.system.helper.login;
|
||||
package cn.bunny.services.utils.login;
|
||||
|
||||
import cn.bunny.services.domain.system.system.dto.user.LoginDto;
|
||||
import cn.bunny.services.domain.system.system.entity.AdminUser;
|
||||
|
@ -31,16 +31,4 @@ 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.service.system.helper.login;
|
||||
package cn.bunny.services.utils.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,18 +53,4 @@ 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,8 +1,9 @@
|
|||
package cn.bunny.services.service.system.helper.login;
|
||||
package cn.bunny.services.utils.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;
|
||||
|
||||
|
@ -12,9 +13,11 @@ import java.util.Map;
|
|||
public class LoginContext {
|
||||
|
||||
private final Map<String, LoginStrategy> strategies;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public LoginContext(Map<String, LoginStrategy> strategies) {
|
||||
public LoginContext(Map<String, LoginStrategy> strategies, PasswordEncoder passwordEncoder) {
|
||||
this.strategies = strategies;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,16 +37,4 @@ 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.service.system.helper.login;
|
||||
package cn.bunny.services.utils.login;
|
||||
|
||||
|
||||
import cn.bunny.services.domain.system.system.dto.user.LoginDto;
|
||||
|
@ -16,12 +16,4 @@ public interface LoginStrategy {
|
|||
* @return 鉴定身份验证
|
||||
*/
|
||||
AdminUser authenticate(LoginDto loginDto);
|
||||
|
||||
/**
|
||||
* 登录完成后的内容
|
||||
*
|
||||
* @param loginDto 登录参数
|
||||
* @param adminUser
|
||||
*/
|
||||
void authenticateAfter(LoginDto loginDto, AdminUser adminUser);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.services.service.system.helper;
|
||||
package cn.bunny.services.utils.system;
|
||||
|
||||
import cn.bunny.services.domain.common.model.dto.excel.PermissionExcel;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
|
@ -11,20 +11,12 @@ import java.util.List;
|
|||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* 权限数据处理工具类
|
||||
*
|
||||
* <p>提供权限数据的树形结构处理、扁平化处理以及导出功能</p>
|
||||
*/
|
||||
public class PermissionHelper {
|
||||
|
||||
public class PermissionUtil {
|
||||
/**
|
||||
* 将树形结构权限数据扁平化为列表
|
||||
* 将属性结构扁平化
|
||||
*
|
||||
* <p>使用递归处理树形结构</p>
|
||||
*
|
||||
* @param list 树形结构的权限列表,每个节点可能包含children子节点
|
||||
* @return 扁平化后的权限列表(
|
||||
* @param list 属性结构
|
||||
* @return 扁平化数组
|
||||
*/
|
||||
public static List<PermissionExcel> flattenTree(List<PermissionExcel> list) {
|
||||
List<PermissionExcel> result = new ArrayList<>();
|
||||
|
@ -40,12 +32,10 @@ public class PermissionHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* 递归设置子节点(内部方法)
|
||||
* 设置子集
|
||||
*
|
||||
* <p>为父节点查找并设置所有子节点,递归处理子节点的子节点</p>
|
||||
*
|
||||
* @param parent 当前父节点
|
||||
* @param list 完整的权限数据列表
|
||||
* @param parent 父级节点
|
||||
* @param list 要构建的列表
|
||||
*/
|
||||
private static void setChildren(PermissionExcel parent, List<PermissionExcel> list) {
|
||||
List<PermissionExcel> children = list.stream()
|
||||
|
@ -61,13 +51,12 @@ public class PermissionHelper {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建权限树形结构
|
||||
* 构建属性结构
|
||||
*
|
||||
* <p>从扁平列表中构建树形结构,根节点的判断条件为parentId为null或0</p>
|
||||
*
|
||||
* @param list 扁平化的权限数据列表
|
||||
* @return 构建完成的树形结构列表(只包含根节点)
|
||||
* @param list 要构建的列表
|
||||
* @return 构建完成的列表
|
||||
*/
|
||||
public static List<PermissionExcel> buildTree(List<PermissionExcel> list) {
|
||||
List<PermissionExcel> permissionExcels = list.stream()
|
||||
|
@ -81,11 +70,11 @@ public class PermissionHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* 将权限数据写入JSON格式到ZIP压缩包
|
||||
* 写入JSON
|
||||
*
|
||||
* @param list 要导出的权限数据列表
|
||||
* @param zipOutputStream ZIP输出流
|
||||
* @param zipName 在ZIP包中的文件名
|
||||
* @param list 写入的列表
|
||||
* @param zipOutputStream zip输出流
|
||||
* @param zipName zip文件名
|
||||
*/
|
||||
public static void writeJson(List<PermissionExcel> list, ZipOutputStream zipOutputStream, String zipName) {
|
||||
try {
|
||||
|
@ -99,11 +88,11 @@ public class PermissionHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* 将权限数据写入Excel格式到ZIP压缩包
|
||||
* 写入JSON
|
||||
*
|
||||
* @param list 要导出的权限数据列表
|
||||
* @param zipOutputStream ZIP输出流
|
||||
* @param zipName 在ZIP包中的文件名(需包含.xlsx后缀)
|
||||
* @param list 写入的列表
|
||||
* @param zipOutputStream zip输出流
|
||||
* @param zipName zip文件名
|
||||
*/
|
||||
public static void writExcel(List<PermissionExcel> list, ZipOutputStream zipOutputStream, String zipName) {
|
||||
try {
|
|
@ -0,0 +1,92 @@
|
|||
package cn.bunny.services.utils.system;
|
||||
|
||||
import cn.bunny.services.context.BaseContext;
|
||||
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 com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class RoleUtil {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
private UserUtil userUtil;
|
||||
|
||||
/**
|
||||
* 判断是否是管理员
|
||||
*
|
||||
* @param roleList 角色代码列表
|
||||
* @param permissions 权限列表
|
||||
* @param adminUser 用户信息
|
||||
* @return 是否是管理员
|
||||
*/
|
||||
public static boolean checkAdmin(List<String> roleList, List<String> permissions, AdminUser adminUser) {
|
||||
// 判断是否是超级管理员
|
||||
boolean isIdAdmin = adminUser.getId().equals(1L);
|
||||
boolean isAdmin = roleList.stream().anyMatch(role -> role.equals("admin"));
|
||||
|
||||
// 判断是否是 admin
|
||||
if (isIdAdmin || isAdmin) {
|
||||
roleList.add("admin");
|
||||
permissions.add("*");
|
||||
permissions.add("*::*");
|
||||
permissions.add("*::*::*");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是管理员
|
||||
*
|
||||
* @param roleList 角色代码列表
|
||||
* @return 是否是管理员
|
||||
*/
|
||||
public static boolean checkAdmin(List<String> roleList) {
|
||||
// 可以放行的权限
|
||||
List<String> permitAllList = SecurityConfigConstant.PERMIT_ALL_LIST;
|
||||
|
||||
// 判断是否是超级管理员
|
||||
if (BaseContext.getUserId().equals(1L)) return true;
|
||||
|
||||
// 判断是否是 admin
|
||||
return roleList.stream().anyMatch(permitAllList::contains);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新Redis中用户信息
|
||||
*
|
||||
* @param userIds 用户Id列表
|
||||
*/
|
||||
public void updateUserRedisInfo(List<Long> userIds) {
|
||||
if (userIds.isEmpty()) return;
|
||||
|
||||
// 根据Id查找所有用户
|
||||
List<AdminUser> adminUsers = userMapper.selectList(Wrappers.<AdminUser>lambdaQuery().in(AdminUser::getId, userIds));
|
||||
|
||||
// 用户为空时不更新Redis的key
|
||||
if (adminUsers.isEmpty()) return;
|
||||
|
||||
// 更新Redis中用户信息
|
||||
adminUsers.stream()
|
||||
.filter(user -> {
|
||||
String adminLoginInfoPrefix = RedisUserConstant.getAdminLoginInfoPrefix(user.getUsername());
|
||||
Object object = redisTemplate.opsForValue().get(adminLoginInfoPrefix);
|
||||
return object != null;
|
||||
})
|
||||
.forEach(user -> userUtil.buildUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME));
|
||||
}
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
package cn.bunny.services.service.system.helper;
|
||||
package cn.bunny.services.utils.system;
|
||||
|
||||
import cn.bunny.services.context.BaseContext;
|
||||
import cn.bunny.services.domain.system.system.entity.RouterRole;
|
||||
import cn.bunny.services.domain.system.system.entity.router.Router;
|
||||
import cn.bunny.services.domain.system.system.entity.router.RouterMeta;
|
||||
import cn.bunny.services.domain.system.system.views.ViewRolePermission;
|
||||
import cn.bunny.services.domain.system.system.views.ViewRouterRole;
|
||||
import cn.bunny.services.domain.system.system.vo.router.WebUserRouterVo;
|
||||
import cn.bunny.services.context.BaseContext;
|
||||
import cn.bunny.services.service.system.RouterRoleService;
|
||||
import cn.bunny.services.service.system.helper.role.RoleHelper;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -21,7 +20,7 @@ import java.util.*;
|
|||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RouterHelper {
|
||||
public class RouterUtil {
|
||||
@Resource
|
||||
private RouterRoleService routerRoleService;
|
||||
|
||||
|
@ -45,12 +44,10 @@ public class RouterHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* 保存路由角色关联关系
|
||||
* 查询新的路由权限和角色
|
||||
*
|
||||
* <p>将路由的权限角色配置保存到数据库</p>
|
||||
*
|
||||
* @param meta 路由元数据,包含角色配置信息
|
||||
* @param id 路由ID
|
||||
* @param meta RouterMeta
|
||||
* @param id 路由id
|
||||
*/
|
||||
public void insertRouterRoleAndPermission(RouterMeta meta, Long id) {
|
||||
List<String> roles = meta.getRoles();
|
||||
|
@ -66,31 +63,18 @@ public class RouterHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* 构建前端可访问的路由列表
|
||||
* 整理web用户所能看到的路由列表
|
||||
*
|
||||
* <p>处理流程:</p>
|
||||
* <ol>
|
||||
* <li>检查当前用户是否为管理员(拥有全部权限)</li>
|
||||
* <li>转换路由基础信息</li>
|
||||
* <li>处理路由元数据(meta)</li>
|
||||
* <li>设置路由关联的角色信息</li>
|
||||
* <li>设置路由关联的权限信息</li>
|
||||
* <li>按rank排序返回</li>
|
||||
* </ol>
|
||||
* <a href="https://pure-admin.cn/pages/routerMenu/#%E8%B7%AF%E7%94%B1%E5%92%8C%E8%8F%9C%E5%8D%95%E9%85%8D%E7%BD%AE">
|
||||
* 路由结构参考这个文档
|
||||
* </a>
|
||||
*
|
||||
* @param routerList 所有路由列表
|
||||
* @param routerRoleList 路由-角色关联Map(key: 路由ID, value: 角色列表)
|
||||
* @param rolePermissionList 角色-权限关联Map(key: 角色ID, value: 权限列表)
|
||||
* @return 前端可访问的路由列表(包含完整的权限信息)
|
||||
* @param routerList 所有的路由列表
|
||||
* @param routerRoleList 路由和角色列表
|
||||
* @param rolePermissionList 角色和权限列表
|
||||
* @return web用户所能看到的路由列表
|
||||
*/
|
||||
@NotNull
|
||||
public List<WebUserRouterVo> getWebUserRouterVos(List<Router> routerList, Map<Long, List<ViewRouterRole>> routerRoleList, Map<Long, List<ViewRolePermission>> rolePermissionList) {
|
||||
// 检查当前是否是 admin 用户
|
||||
List<String> roles = BaseContext.getLoginVo().getRoles();
|
||||
List<String> allAuths = !RoleHelper.checkAdmin(roles) ? new ArrayList<>() : List.of("*:*:*", "*:*", "*", "admin");
|
||||
List<String> allAuths = !RoleUtil.checkAdmin(roles) ? new ArrayList<>() : List.of("*:*:*", "*:*", "*", "admin");
|
||||
|
||||
// 查询路由所有数据,整理前端需要的路和、角色、权限
|
||||
return routerList.stream().map(view -> {
|
|
@ -0,0 +1,316 @@
|
|||
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