资源权限管理之自定义注解并扫描资源至数据库
此文的目的在于将所有在资源上加了自定义注解的接口自动扫描并添加进数据库
一:设计权限表的domain实体类
package cn.ybl.system.domain;
import lombok.Data;
/**
* @Author Mr.Yang
* @createTime 2022/8/2 15:37
* @Describe 权限表t_permission实体类
*/
@Data
public class Permission {
//id
private Long id;
//权限名
private String name;
//权限资源路径【即控制层的接口路径】
private String url;
//权限描述
private String descs;
//sn编号
private String sn;
}
二:Mapper接口
package cn.ybl.system.mapper;
import cn.ybl.system.domain.Permission;
/**
* @Author Mr.Yang
* @createTime 2022/8/2 15:37
* @Describe 权限表t_permission——Mapper层
*/
public interface PermissionMapper {
//根据sn查询Permission
Permission loadBySn(String permissionSn);
//添加
void save(Permission permission);
//更新
void update(Permission permissionTmp);
}
三:权限表业务接口
package cn.ybl.system.service;
/**
* @Author Mr.Yang
* @createTime 2022/8/2 15:28
* @Describe 权限表t_permission业务接口
*/
public interface IPermissionScanService {
//扫描权限
void scanPermission();
}
四:权限表业务实现【cv的,一般改改可以通用】
package cn.ybl.system.service.impl;
import cn.ybl.basic.util.ClassUtils;
import cn.ybl.system.annotation.PreAuthorize;
import cn.ybl.system.domain.Permission;
import cn.ybl.system.mapper.PermissionMapper;
import cn.ybl.system.service.IPermissionScanService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Author Mr.Yang
* @createTime 2022/8/2 15:29
* @Describe
*/
@Service
public class PermissionScanServiceImpl implements IPermissionScanService {
private static final String PKG_PREFIX = "cn.ybl.";
private static final String PKG_SUFFIX = ".controller";
@Autowired
private PermissionMapper permissionMapper;
@Override
public void scanPermission() {
//获取cn.ybl下面所有的模块目录
String path = this.getClass().getResource("/").getPath() + "/cn/ybl/";
File file = new File(path);
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory();
}
});
//获取cn.ybl.*.controller里面所有的类
Set<Class> clazzes = new HashSet<>();
for (File fileTmp : files) {
System.out.println("===============权限注解解析:获取所有的包==============");
System.out.println(fileTmp.getName());
clazzes.addAll(ClassUtils.getClasses(PKG_PREFIX+fileTmp.getName()+PKG_SUFFIX));
}
for (Class clazz : clazzes) {
Method[] methods = clazz.getMethods();
if (methods==null || methods.length<1)
return;
for (Method method : methods) {
String uri = getUri(clazz, method);
try {
PreAuthorize preAuthorizeAnno = method.getAnnotation(PreAuthorize.class);
if (preAuthorizeAnno==null)
continue;
String name = preAuthorizeAnno.name();
String permissionSn = preAuthorizeAnno.sn();
Permission permissionTmp = permissionMapper.loadBySn(permissionSn);
//如果不存在就添加
if (permissionTmp == null) {
Permission permission = new Permission();
permission.setName(name); //t_permission表中的权限名
permission.setSn(permissionSn); //t_permission表中的权限编号
permission.setUrl(uri); //t_permission表中的权限路径
permissionMapper.save(permission);
}else{
//如果存在就修改
permissionTmp.setName(name);
permissionTmp.setSn(permissionSn);
permissionTmp.setUrl(uri);
permissionMapper.update(permissionTmp);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//获取t_permission表中的url //@RequestMapping("/department") //@GetMapping("/{id}")
private String getUri(Class clazz, Method method) {
//获取类上的请求路径:/department
String classPath = "";
Annotation annotation = clazz.getAnnotation(RequestMapping.class);
if (annotation!=null){
RequestMapping requestMapping = (RequestMapping) annotation;
String[] values = requestMapping.value();
if(values!=null&&values.length>0){
classPath = values[0];
if (!"".equals(classPath)&&!classPath.startsWith("/"))
classPath="/"+classPath;
}
}
//以下是获取方法上的请求路径:/{id}
GetMapping getMapping = method.getAnnotation(GetMapping.class);
String methodPath = "";
if (getMapping!=null){
String[] values = getMapping.value();
if(values!=null&&values.length>0){
methodPath = values[0];
if (!"".equals(methodPath)&&!methodPath.startsWith("/"))
methodPath="/"+methodPath;
}
}
PostMapping postMapping = method.getAnnotation(PostMapping.class);
if (postMapping!=null){
String[] values = postMapping.value();
if(values!=null&&values.length>0){
methodPath = values[0];
if (!"".equals(methodPath)&&!methodPath.startsWith("/"))
methodPath="/"+methodPath;
}
}
DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
if (deleteMapping!=null){
String[] values = deleteMapping.value();
if(values!=null&&values.length>0){
methodPath = values[0];
if (!"".equals(methodPath)&&!methodPath.startsWith("/"))
methodPath="/"+methodPath;
}
}
PutMapping putMapping = method.getAnnotation(PutMapping.class);
if (putMapping!=null){
String[] values = putMapping.value();
if(values!=null&&values.length>0){
methodPath = values[0];
if (!"".equals(methodPath)&&!methodPath.startsWith("/"))
methodPath="/"+methodPath;
}
}
PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);
if (patchMapping!=null){
String[] values = patchMapping.value();
if(values!=null&&values.length>0){
methodPath = values[0];
if (!"".equals(methodPath)&&!methodPath.startsWith("/"))
methodPath="/"+methodPath;
}
}
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
if (requestMapping!=null){
String[] values = requestMapping.value();
if(values!=null&&values.length>0){
methodPath = values[0];
if (!"".equals(methodPath)&&!methodPath.startsWith("/"))
methodPath="/"+methodPath;
}
}
return classPath+methodPath; // /department/{id}
}
private String getPermissionSn(String value) {
String regex = "\\[(.*?)]";
Pattern p = Pattern.compile("(?<=\\()[^\\)]+");
Matcher m = p.matcher(value);
String permissionSn =null;
if (m.find()){
permissionSn= m.group(0).substring(1, m.group().length()-1);
}
return permissionSn;
}
public static void main(String[] args) {
String regex = "\\[(.*?)]";
String text = "hasAuthority('permission:add')";
Pattern p = Pattern.compile("(?<=\\()[^\\)]+");
Matcher m = p.matcher(text);
if (m.find()) {
System.out.println(m.group(0).substring(1, m.group().length()-1));
}
}
}
五:反射工具类ClassUtils
package cn.ybl.basic.util;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 权限表需要的反射工具类
*/
public class ClassUtils {
/**
* 从传入的包中获取所有的类的字节码对象:
*
* @author LEIYU
* @param pack
* @return
*/
public static Set<Class<?>> getClasses(String pack) {
// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 是否循环迭代
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(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
// 2.以文件的形式来获取包下的所有Class
/**
* 以文件的形式来获取包下的所有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;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
}
});
// 循环所有文件
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.printStackTrace();
}
}
}
}
}
六:自定义注解类
package cn.ybl.system.annotation;
import java.lang.annotation.*;
/**
* @Author Mr.Yang
* @createTime 2022/8/2 15:20
* @Describe
*/
@Target({ElementType.TYPE,ElementType.METHOD}) //作用范围
@Retention(RetentionPolicy.RUNTIME) //生命周期
@Inherited //可继承
public @interface PreAuthorize {
//表中的sn字段
String sn();
//表中的name字段
String name();
}
七:Web监听器
package cn.ybl.system.listener;
import cn.ybl.system.service.IPermissionScanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener//申明自定义的web监听器,被容器注册和使用
@Slf4j
public class PermissionScanInitListener implements ServletContextListener {
@Autowired
private IPermissionScanService permissionScanService;
//spring容器初始化结束之后被调用
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println(1);
//这里面的业务随着接口的变多,可能执行时间会非常久,影响性能。影响主线程的启动
new Thread(new Runnable() {//不用主线程去执行,用一个新的线程去执行
@Override
public void run() {
//可以在这里扫描我们自定义的注解@PreAuthorize,然后将信息存储到t_permission表
//这样就无需手动录入信息到权限t_permission表了
log.info("权限初始化开始******************************************");
System.out.println("权限初始化开始******************************************");
permissionScanService.scanPermission();
System.out.println("权限初始化结束******************************************");
}
}).start();
}
//容器销毁的时候执行
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
八:启动类@ServletComponentScan扫描监听器
package cn.ybl;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
/**
* @Author Mr.Yang
* @createTime 2022/7/14 14:20
* @Describe
*/
@SpringBootApplication
@MapperScan("cn.ybl.*.mapper")
//扫描自定义监听器
@ServletComponentScan("cn.ybl.system.listener")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
九:使用方法
在接口上打上自定义注解即可
/**
* 分页查询
* @return PageList
*/
@PreAuthorize(name = "数据字典明细分页查询",sn = "SystemDetail:queryPage") //自定义注解
@PostMapping
public PageList<SystemDictionaryDetail> queryPage(@RequestBody SystemDictionaryDetailQuery systemDictionaryDetailQuery){
return detailService.queryPage(systemDictionaryDetailQuery);
}
到此添加资源权限至数据库完成,其中业务层的类和ClassUtils以及Web监听器可通用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!