📝 feat: 修改部分代码;添加生成权限文档

This commit is contained in:
bunny 2025-05-07 19:32:29 +08:00
parent 94e0d7f264
commit 7849236e6e
25 changed files with 829 additions and 762 deletions

View File

@ -28,10 +28,11 @@
**介绍视频视频**
- [环境搭建](https://www.bilibili.com/video/BV17odHY6E3S/?spm_id_from=333.1387.homepage.video_card.click&vd_source=d42b5b664efb958be39eef8ee1196a7e)
- [运行项目](https://www.bilibili.com/video/BV1qodHYzErA/?spm_id_from=333.1387.homepage.video_card.click&vd_source=d42b5b664efb958be39eef8ee1196a7e)
- [前端部署](https://www.bilibili.com/video/BV1BddHYgEPq/?spm_id_from=333.1387.homepage.video_card.click&vd_source=d42b5b664efb958be39eef8ee1196a7e)
- [后端部署](https://www.bilibili.com/video/BV1BddHYgEFt/?spm_id_from=333.1387.homepage.video_card.click&vd_source=d42b5b664efb958be39eef8ee1196a7e)
- [RBAC中URL的权限数据库、后端设计](https://www.bilibili.com/video/BV1nGVazrEKf/)
- [bunny-admin 配置说明](https://www.bilibili.com/video/BV177VazMEiM/)
- [Bunny-Admin 用户相关操作](https://www.bilibili.com/video/BV1B7VazME72/)
- [Bunny-Admin 角色权限](https://www.bilibili.com/video/BV1ELVazzEnC/)
- [Bunny-Admin 剩下的业务逻辑](https://www.bilibili.com/video/BV1ELVazzE7S/)
- [代码生成器](https://www.bilibili.com/video/BV1d4Lxz9E3j/?vd_source=d42b5b664efb958be39eef8ee1196a7e)
**Github地址**
@ -131,7 +132,7 @@ graph TD
auth-api -->|启动项、控制器| service
service -->|mapper| dao
service -->|包含domain、配置等| auth-core
dao -->|包含domain、配置等| auth-code
dao -->|包含domain、配置等| auth-core
```

View File

@ -0,0 +1,80 @@
# 生成系统需要的权限
```java
import cn.bunny.services.AuthServiceApplication;
import cn.bunny.services.aop.scanner.ControllerApiPermissionScanner;
import cn.bunny.services.domain.common.model.dto.scanner.ScannerControllerInfoVo;
import cn.bunny.services.domain.system.system.entity.Permission;
import cn.bunny.services.service.system.PermissionService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import java.util.Objects;
@SpringBootTest(classes = AuthServiceApplication.class, properties = "spring.profiles.active=dev")
public class BuildPermissionApiTest {
@Autowired
private PermissionService permissionService;
@Test
void test() {
List<ScannerControllerInfoVo> list = ControllerApiPermissionScanner.getSystemApiInfoList();
ScannerControllerInfoVo actuatorParent = ScannerControllerInfoVo.builder().powerCodes(List.of("admin::actuator")).summary("actuator端点访问").build();
ScannerControllerInfoVo actuatorChild = ScannerControllerInfoVo.builder().path("/api/actuator/**")
.summary("Springboot端点全部可以访问")
.description("系统监控使用")
.powerCodes(List.of("actuator::all"))
.build();
actuatorParent.setChildren(List.of(actuatorChild));
list.add(actuatorParent);
list.forEach(parent -> {
String summary = parent.getSummary();
String path = parent.getPath();
String httpMethod = parent.getHttpMethod();
List<String> powerCodes = parent.getPowerCodes();
String description = parent.getDescription();
// 设置 powerCode
String powerCode = Objects.isNull(powerCodes) ? "" : powerCodes.get(0);
Permission permission = new Permission();
permission.setParentId(0L);
permission.setPowerName(summary);
permission.setPowerCode(powerCode);
permission.setRequestMethod(httpMethod);
permission.setRequestUrl(path);
permissionService.save(permission);
// 保存后 permission 的 Id 作为子级的父级Id
Long permissionId = permission.getId();
// 子级列表
List<Permission> permissionList = parent.getChildren().stream()
.map(children -> {
String childrenSummary = children.getSummary();
String childrenPath = children.getPath();
String childrenHttpMethod = children.getHttpMethod();
List<String> childrenPowerCodes = children.getPowerCodes();
// 设置 powerCode
String childrenPowerCode = Objects.isNull(childrenPowerCodes) ? "" : childrenPowerCodes.get(0);
Permission childPermission = new Permission();
childPermission.setParentId(permissionId);
childPermission.setPowerName(childrenSummary);
childPermission.setPowerCode(childrenPowerCode);
childPermission.setRequestMethod(childrenHttpMethod);
childPermission.setRequestUrl(childrenPath);
return childPermission;
}).toList();
permissionService.saveBatch(permissionList);
});
}
}
```

View File

@ -1,10 +1,10 @@
package cn.bunny.services.controller.system;
import cn.bunny.services.aop.scanner.ControllerApiPermissionScanner;
import cn.bunny.services.domain.common.enums.ResultCodeEnum;
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.enums.ResultCodeEnum;
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;

View File

@ -0,0 +1,78 @@
package impl;
import cn.bunny.services.AuthServiceApplication;
import cn.bunny.services.aop.scanner.ControllerApiPermissionScanner;
import cn.bunny.services.domain.common.model.dto.scanner.ScannerControllerInfoVo;
import cn.bunny.services.domain.system.system.entity.Permission;
import cn.bunny.services.service.system.PermissionService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import java.util.Objects;
@SpringBootTest(classes = AuthServiceApplication.class, properties = "spring.profiles.active=dev")
public class BuildPermissionApiTest {
@Autowired
private PermissionService permissionService;
@Test
void test() {
List<ScannerControllerInfoVo> list = ControllerApiPermissionScanner.getSystemApiInfoList();
ScannerControllerInfoVo actuatorParent = ScannerControllerInfoVo.builder().powerCodes(List.of("admin::actuator")).summary("actuator端点访问").build();
ScannerControllerInfoVo actuatorChild = ScannerControllerInfoVo.builder().path("/api/actuator/**")
.summary("Springboot端点全部可以访问")
.description("系统监控使用")
.powerCodes(List.of("actuator::all"))
.build();
actuatorParent.setChildren(List.of(actuatorChild));
list.add(actuatorParent);
list.forEach(parent -> {
String summary = parent.getSummary();
String path = parent.getPath();
String httpMethod = parent.getHttpMethod();
List<String> powerCodes = parent.getPowerCodes();
String description = parent.getDescription();
// 设置 powerCode
String powerCode = Objects.isNull(powerCodes) ? "" : powerCodes.get(0);
Permission permission = new Permission();
permission.setParentId(0L);
permission.setPowerName(summary);
permission.setPowerCode(powerCode);
permission.setRequestMethod(httpMethod);
permission.setRequestUrl(path);
permissionService.save(permission);
// 保存后 permission Id 作为子级的父级Id
Long permissionId = permission.getId();
// 子级列表
List<Permission> permissionList = parent.getChildren().stream()
.map(children -> {
String childrenSummary = children.getSummary();
String childrenPath = children.getPath();
String childrenHttpMethod = children.getHttpMethod();
List<String> childrenPowerCodes = children.getPowerCodes();
// 设置 powerCode
String childrenPowerCode = Objects.isNull(childrenPowerCodes) ? "" : childrenPowerCodes.get(0);
Permission childPermission = new Permission();
childPermission.setParentId(permissionId);
childPermission.setPowerName(childrenSummary);
childPermission.setPowerCode(childrenPowerCode);
childPermission.setRequestMethod(childrenHttpMethod);
childPermission.setRequestUrl(childrenPath);
return childPermission;
}).toList();
permissionService.saveBatch(permissionList);
});
}
}

View File

@ -90,6 +90,11 @@
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- 多数据库源插件 -->
<!-- <dependency> -->
<!-- <groupId>com.baomidou</groupId> -->
<!-- <artifactId>dynamic-datasource-spring-boot3-starter</artifactId> -->
<!-- </dependency> -->
<!-- fastjson2 -->
<dependency>

View File

@ -32,7 +32,6 @@ public class RedisUserConstant {
*/
public static String getUserLoginInfoPrefix(String user) {
return USER_LOGIN_INFO_PREFIX + user;
}
/**

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,7 @@
bunny:
master:
# host: 192.168.3.137
host: localhost
host: 192.168.3.137 # localhost
port: 3306
database: bunny_admin
database: auth_admin
username: root
password: "123456"

View File

@ -72,12 +72,6 @@
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- mysql -->
<!-- <dependency> -->
<!-- <groupId>com.mysql</groupId> -->
<!-- <artifactId>mysql-connector-java</artifactId> -->
<!-- <version>${mysql.version}</version> -->
<!-- </dependency> -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>

View File

@ -29,7 +29,6 @@
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- asp 切面 -->
<dependency>
<groupId>org.aspectj</groupId>

View File

@ -64,7 +64,7 @@ public class UserAuthorizationCacheService {
// 防止 list 为空报错
roleList = list != null ? list : new ArrayList<>();
}
return roleList;
}

View File

@ -12,6 +12,7 @@ import cn.bunny.services.mapper.system.RolePermissionMapper;
import cn.bunny.services.mapper.system.UserRoleMapper;
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;
@ -30,6 +31,7 @@ public class UserinfoUpdateListener extends AbstractUserInfoUpdateHandler {
/* 根据用户id更新用户信息重新生成LoginVo对象 */
@EventListener
@Async
public void handlerUpdateUserinfoByUserIds(UpdateUserinfoByUserIdsEvent event) {
List<Long> userIds = event.getUserIds();
processUserUpdate(userIds, user -> {
@ -40,6 +42,7 @@ public class UserinfoUpdateListener extends AbstractUserInfoUpdateHandler {
/* 根据角色id更新用户信息重新生成LoginVo对象 */
@EventListener
@Async
public void handlerUserinfoUpdateByRoleId(UpdateUserinfoByRoleIdsEvent event) {
List<Long> roleIds = event.getRoleIds();
List<UserRole> userRoles = userRoleMapper.selectListByRoleIds(roleIds);
@ -51,6 +54,7 @@ public class UserinfoUpdateListener extends AbstractUserInfoUpdateHandler {
/* 根据角色id更新用户信息重新生成LoginVo对象 */
@EventListener
@Async
public void handlerUserinfoUpdateByPermissionId(UpdateUserinfoByPermissionIdsEvent event) {
List<Long> permissionIds = event.getPermissionIds();
List<RolePermission> rolePermissions = rolePermissionMapper.selectRolePermissionListByPermissionIds(permissionIds);
@ -62,6 +66,7 @@ public class UserinfoUpdateListener extends AbstractUserInfoUpdateHandler {
/* 清除用户登录、角色、权限所有缓存 */
@EventListener
@Async
public void handlerDeleteAllUserCache(ClearAllUserCacheEvent event) {
userCacheCleaner.cleanAllUserCache(event.getKey());
}

View File

@ -7,12 +7,12 @@ import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ExcelExportStrategy implements ExportStrategy<List<?>> {
public class ExcelZipExportStrategy implements ExportStrategy<List<?>> {
private final Class<?> clazz;
private final String sheetName;
public ExcelExportStrategy(Class<?> clazz, String sheetName) {
public ExcelZipExportStrategy(Class<?> clazz, String sheetName) {
this.clazz = clazz;
this.sheetName = sheetName;
}

View File

@ -6,7 +6,7 @@ import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class JsonExportStrategy implements ExportStrategy<Object> {
public class JsonZipExportStrategy implements ExportStrategy<Object> {
@Override
public void export(Object data, ZipOutputStream zipOutputStream, String filename) {

View File

@ -21,6 +21,7 @@ import java.util.*;
@Slf4j
@Component
public class RouterServiceHelper {
@Resource
private RouterRoleService routerRoleService;

View File

@ -9,6 +9,7 @@ import org.quartz.JobExecutionException;
@Slf4j
@QuartzSchedulers(type = "test", description = "JobHello任务内容")
public class JobHello implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 测试使用

View File

@ -13,7 +13,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class MethodSecurityConfig {
/**
* 设置密码校验器
*

View File

@ -34,6 +34,7 @@ public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 前端段分离不需要---禁用明文验证
.httpBasic(AbstractHttpConfigurer::disable)

View File

@ -1,9 +1,9 @@
package cn.bunny.services.security.service;
import cn.bunny.services.context.BaseContext;
import cn.bunny.services.domain.common.enums.ResultCodeEnum;
import cn.bunny.services.domain.common.model.dto.security.TokenInfo;
import cn.bunny.services.domain.common.model.vo.LoginVo;
import cn.bunny.services.domain.common.enums.ResultCodeEnum;
import cn.bunny.services.security.exception.CustomAuthenticationException;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
@ -36,19 +36,6 @@ public class CustomAuthorizationManagerServiceImpl implements AuthorizationManag
@Resource
private PermissionCheckService permissionCheckService;
/**
* 授权决策主方法
* <ul>
* <li>Token验证失败</li>
* <li>用户状态异常</li>
* <li>权限检查失败</li>
* </ul>
*
* @param authentication 认证信息提供者
* @param context 请求授权上下文
* @return 授权决策结果允许/拒绝
* @throws CustomAuthenticationException 当出现以下情况时抛出
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
// 用户的token和用户id请求Url
@ -70,12 +57,6 @@ public class CustomAuthorizationManagerServiceImpl implements AuthorizationManag
return new AuthorizationDecision(hasPermission);
}
/**
* 验证用户状态
*
* @param loginVo 用户登录信息
* @throws CustomAuthenticationException 当用户状态异常时抛出
*/
private void validateUserStatus(LoginVo loginVo) {
// 登录信息为空
if (loginVo == null) {

View File

@ -1,8 +1,8 @@
package cn.bunny.services.service.configuration.impl;
import cn.bunny.services.core.event.listener.excel.I18nExcelListener;
import cn.bunny.services.core.strategy.export.ExcelExportStrategy;
import cn.bunny.services.core.strategy.export.JsonExportStrategy;
import cn.bunny.services.core.strategy.export.ExcelZipExportStrategy;
import cn.bunny.services.core.strategy.export.JsonZipExportStrategy;
import cn.bunny.services.domain.common.constant.FileType;
import cn.bunny.services.domain.common.enums.ResultCodeEnum;
import cn.bunny.services.domain.common.model.dto.excel.I18nExcel;
@ -184,8 +184,8 @@ public class I18nServiceImpl extends ServiceImpl<I18nMapper, I18n> implements I1
}, Collectors.toList())
))
.forEach((key, value) -> {
ExcelExportStrategy excelExportStrategy = new ExcelExportStrategy(I18nExcel.class, key);
excelExportStrategy.export(value, zipOutputStream, key + ".xlsx");
ExcelZipExportStrategy excelZipExportStrategy = new ExcelZipExportStrategy(I18nExcel.class, key);
excelZipExportStrategy.export(value, zipOutputStream, key + ".xlsx");
});
}
// 其他格式写入JSON
@ -193,8 +193,8 @@ public class I18nServiceImpl extends ServiceImpl<I18nMapper, I18n> implements I1
HashMap<String, Object> hashMap = getMapByI18nList(i18nList);
hashMap.forEach((k, v) -> {
JsonExportStrategy jsonExportStrategy = new JsonExportStrategy();
jsonExportStrategy.export(v, zipOutputStream, k + ".json");
JsonZipExportStrategy jsonZipExportStrategy = new JsonZipExportStrategy();
jsonZipExportStrategy.export(v, zipOutputStream, k + ".json");
});
}

View File

@ -1,7 +1,7 @@
package cn.bunny.services.service.schedule.impl;
import cn.bunny.services.domain.common.model.vo.result.PageResult;
import cn.bunny.services.domain.common.enums.ResultCodeEnum;
import cn.bunny.services.domain.common.model.vo.result.PageResult;
import cn.bunny.services.domain.system.quartz.dto.SchedulersAddDto;
import cn.bunny.services.domain.system.quartz.dto.SchedulersDto;
import cn.bunny.services.domain.system.quartz.dto.SchedulersUpdateDto;
@ -106,13 +106,13 @@ public class SchedulersServiceImpl extends ServiceImpl<SchedulersMapper, Schedul
try {
// 动态创建Class对象
Class<?> className = Class.forName(dto.getJobClassName());
Class<?> clazz = Class.forName(dto.getJobClassName());
// 获取无参构造函数
className.getConstructor().newInstance();
clazz.getConstructor().newInstance();
// 创建任务
JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) className)
JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) clazz)
.withIdentity(jobName, jobGroup)
.withDescription(dto.getDescription())
.build();

View File

@ -2,8 +2,8 @@ package cn.bunny.services.service.system.impl;
import cn.bunny.services.core.event.event.UpdateUserinfoByPermissionIdsEvent;
import cn.bunny.services.core.event.listener.excel.PermissionExcelListener;
import cn.bunny.services.core.strategy.export.ExcelExportStrategy;
import cn.bunny.services.core.strategy.export.JsonExportStrategy;
import cn.bunny.services.core.strategy.export.ExcelZipExportStrategy;
import cn.bunny.services.core.strategy.export.JsonZipExportStrategy;
import cn.bunny.services.core.template.PermissionTreeProcessor;
import cn.bunny.services.domain.common.constant.FileType;
import cn.bunny.services.domain.common.enums.ResultCodeEnum;
@ -239,11 +239,11 @@ public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permiss
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
// 判断导出类型是什么
if (type.equals(FileType.EXCEL)) {
ExcelExportStrategy excelExportStrategy = new ExcelExportStrategy(PermissionExcel.class, "permission");
excelExportStrategy.export(permissionExcelList, zipOutputStream, filename + ".xlsx");
ExcelZipExportStrategy excelZipExportStrategy = new ExcelZipExportStrategy(PermissionExcel.class, "permission");
excelZipExportStrategy.export(permissionExcelList, zipOutputStream, filename + ".xlsx");
} else {
JsonExportStrategy jsonExportStrategy = new JsonExportStrategy();
jsonExportStrategy.export(buildTree, zipOutputStream, filename + ".json");
JsonZipExportStrategy jsonZipExportStrategy = new JsonZipExportStrategy();
jsonZipExportStrategy.export(buildTree, zipOutputStream, filename + ".json");
}
} catch (Exception e) {
throw new RuntimeException(e);

View File

@ -152,7 +152,6 @@ public class RouterServiceImpl extends ServiceImpl<RouterMapper, Router> impleme
save(router);
}
/**
* * 更新路由菜单
*

View File

@ -188,7 +188,6 @@ public class UserLoginServiceImpl extends ServiceImpl<UserMapper, AdminUser> imp
emailCacheService.buildEmailCodeCache(email, emailCode);
}
/**
* 刷新用户token
*
@ -238,7 +237,6 @@ public class UserLoginServiceImpl extends ServiceImpl<UserMapper, AdminUser> imp
applicationEventPublisher.publishEvent(new ClearAllUserCacheEvent(this, username));
}
/**
* 更新本地用户信息
*

View File

@ -4,7 +4,6 @@ import cn.bunny.services.core.cache.RedisService;
import cn.bunny.services.core.event.event.ClearAllUserCacheEvent;
import cn.bunny.services.core.event.event.UpdateUserinfoByUserIdsEvent;
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.ResultCodeEnum;
import cn.bunny.services.domain.common.model.vo.LoginVo;
@ -193,11 +192,12 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implemen
List<String> keys = redisService.scannerRedisKeyByPage(pageNum, pageSize);
List<UserVo> list = keys.stream().map(key -> {
Object loginVoObject = redisTemplate.opsForValue().get(RedisUserConstant.getUserLoginInfoPrefix(key));
Object loginVoObject = redisTemplate.opsForValue().get(key);
LoginVo loginVo = JSON.parseObject(JSON.toJSONString(loginVoObject), LoginVo.class);
UserVo userVo = new UserVo();
BeanUtils.copyProperties(loginVo, userVo);
userVo.setSummary(loginVo.getPersonDescription());
return userVo;
}).toList();
@ -207,7 +207,6 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implemen
.build();
}
/**
* 用户信息 服务实现类
*
@ -284,7 +283,6 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implemen
if (adminUser == null) throw new AuthCustomerException(ResultCodeEnum.DATA_NOT_EXIST);
// 更新用户
// AdminUser user = new AdminUser();
BeanUtils.copyProperties(dto, adminUser);
// 部门Id