Skip to content

Commit

Permalink
feat: auto insert authority
Browse files Browse the repository at this point in the history
  • Loading branch information
lichong-a committed Aug 7, 2024
1 parent 1f93d73 commit 1d5a876
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package org.funcode.portal.server.common.core.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

@Slf4j
public class ClassUtil {

/**
* 从包package中获取所有的Class
*
* @param pack 包名
* @return 所有类
*/
public static Set<Class<?>> getClasses(String pack) {

// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection()).getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx).replace('/', '.');
}
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class") && !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
// 添加到classes
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException e) {
log.error("在扫描用户定义视图时从jar包获取文件出错", e);
}
}
}
}
} catch (IOException e) {
log.error("在扫描用户定义视图时从jar包获取文件出错", e);
}
}
}
} catch (IOException e) {
log.error("在扫描用户定义视图时从jar包获取文件出错", e);
}

return classes;
}

/**
* 以文件的形式来获取包下的所有Class
*
* @param packageName 包的名字
* @param packagePath 包的路径
* @param recursive 是否循环
* @param classes 集合
*/
public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
Set<Class<?>> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
File[] dirFiles = dir.listFiles(file -> (recursive && file.isDirectory()) || (file.getName().endsWith(".class")));
if (dirFiles == null) {
return;
}
// 循环所有文件
for (File file : dirFiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0, file.getName().length() - 6);
try {
// 添加到集合中去
// classes.add(Class.forName(packageName + '.' + className));
// 经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(
Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
log.error("添加用户自定义视图类错误 找不到此类的.class文件", e);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public ResponseResult<Boolean> addOrEditRole(@Valid @RequestBody RoleAddOrEditVo

@Operation(summary = "根据不同条件模糊分页查询角色列表")
@PostMapping("pageList")
@PreAuthorize(value = "hasRole('system:role:pageList')")
@PreAuthorize(value = "hasAuthority('system:role:pageList')")
public ResponseResult<Page<Role>> getRolePageList(@Valid @RequestBody RoleQueryVo roleQueryVo) {
return ResponseResult.success(roleService.findPage(roleQueryVo));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@

package org.funcode.portal.server.starter.init;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.funcode.portal.server.common.core.config.ApplicationConfig;
import org.funcode.portal.server.common.core.security.repository.IBasicAuthorityRepository;
import org.funcode.portal.server.common.core.security.repository.IRoleRepository;
import org.funcode.portal.server.common.core.security.repository.IUserRepository;
import org.funcode.portal.server.common.core.util.ClassUtil;
import org.funcode.portal.server.common.domain.security.BasicAuthority;
import org.funcode.portal.server.common.domain.security.Role;
import org.funcode.portal.server.common.domain.security.User;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

Expand Down Expand Up @@ -49,14 +57,54 @@ public void init() {
log.info("=======开始初始化数据...=========================================================");
// 权限初始化
Set<BasicAuthority> authorities = new HashSet<>();
authorities.add(BasicAuthority.builder().authorityName("新增或编辑权限").authorityKey("system:authority:addOrEditAuthority").build());
authorities.add(BasicAuthority.builder().authorityName("根据不同条件模糊分页查询权限列表").authorityKey("system:authority:getAuthorityPageList").build());
authorities.add(BasicAuthority.builder().authorityName("新增或编辑角色").authorityKey("system:role:addOrEditRole").build());
authorities.add(BasicAuthority.builder().authorityName("根据不同条件模糊分页查询角色列表").authorityKey("system:role:getRolePageList").build());
authorities.add(BasicAuthority.builder().authorityName("根据邮箱获取人员").authorityKey("system:user:getUserByEmail").build());
authorities.add(BasicAuthority.builder().authorityName("根据用户名获取人员").authorityKey("system:user:getUserByUsername").build());
authorities.add(BasicAuthority.builder().authorityName("根据微信ID获取人员").authorityKey("system:user:getUserByWechatId").build());
authorities.add(BasicAuthority.builder().authorityName("根据不同条件模糊分页查询人员列表").authorityKey("system:user:addOrEditAuthority").build());
// 包下面的类
Set<Class<?>> clazzs = ClassUtil.getClasses("org.funcode.portal.server");
for (Class<?> clazz : clazzs) {
if (!clazz.isAnnotationPresent(RestController.class) && !clazz.isAnnotationPresent(Controller.class)) {
continue;
}
if (clazz.isAnnotationPresent(PreAuthorize.class)) {
// 获取类上的注解
PreAuthorize clazzPreAuthorizeAnno = clazz.getAnnotation(PreAuthorize.class);
String clazzPreAuthorizeAnnoValue = clazzPreAuthorizeAnno.value();
String authority = extractAuthority(clazzPreAuthorizeAnnoValue);
if (StringUtils.isNotBlank(authority) && !StringUtils.startsWith(authority, "ROLE_")) {
BasicAuthority clazzBasicAuthority = BasicAuthority.builder().authorityKey(authority).build();
if (clazz.isAnnotationPresent(Tag.class)) {
// 获取类上的注解
Tag clazzTagAnno = clazz.getAnnotation(Tag.class);
String clazzTagName = clazzTagAnno.name();
String clazzTagDescription = clazzTagAnno.description();
clazzBasicAuthority.setAuthorityName(clazzTagName);
clazzBasicAuthority.setDescription(clazzTagDescription);
}
authorities.add(clazzBasicAuthority);
}
}

// 获取方法上的注解
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(PreAuthorize.class)) {
// 获取方法上的注解
PreAuthorize methodPreAuthorizeAnno = method.getAnnotation(PreAuthorize.class);
String methodPreAuthorizeAnnoValue = methodPreAuthorizeAnno.value();
String authority = extractAuthority(methodPreAuthorizeAnnoValue);
if (StringUtils.isNotBlank(authority) && !StringUtils.startsWith(authority, "ROLE_")) {
BasicAuthority methodBasicAuthority = BasicAuthority.builder().authorityKey(authority).build();
if (method.isAnnotationPresent(Operation.class)) {
// 获取方法上的注解
Operation methodOperationAnno = method.getAnnotation(Operation.class);
String methodOperationSummary = methodOperationAnno.summary();
String methodOperationDescription = methodOperationAnno.description();
methodBasicAuthority.setAuthorityName(methodOperationSummary);
methodBasicAuthority.setDescription(methodOperationDescription);
}
authorities.add(methodBasicAuthority);
}
}
}
}
basicAuthorityRepository.saveAllAndFlush(authorities);
// 角色初始化
Set<Role> roles = new HashSet<>();
Expand All @@ -75,4 +123,18 @@ public void init() {
userRepository.saveAndFlush(adminUser);
log.info("=======初始化数据结束=========================================================");
}

/**
* 提取 hasAuthority 值的简单正则表达式
*/
private static String extractAuthority(String value) {
String authority = null;
String regex = "hasAuthority\\('(.*?)'\\)";
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex);
java.util.regex.Matcher matcher = pattern.matcher(value);
if (matcher.find()) {
authority = matcher.group(1);
}
return authority;
}
}

0 comments on commit 1d5a876

Please sign in to comment.