Введение
Недавно я работаю над небольшим проектом, который включает в себя разрешения. не использовалShiroрамка иSpring Security, заключается в том, чтобы контролировать свою власть.
- Таблица разрешений
- таблица ролей
- Таблица разрешений-ролей
- пользовательская таблица
- Таблица ролей пользователей
После того, как таблица построена, а затем используются пользовательские аннотации и перехватчики, все они являются плохими процедурами.
возникшие проблемы
Отвратительная проблема в том, что на интерфейсах, которые требуют разрешений, я должен использовать аннотации, и мне также нужно использовать аннотации в аннотациях.valueКакие разрешения требуются для идентификатора в .
Итак, вначале я использовал класс перечисления, определил несколько кодов разрешений, а затем сохранил копию в базе данных.
Код разрешения в классе перечисления в основном используется в аннотациях. Разрешения в базе данных для настройки ролей.
С этим есть проблема: если определен новый интерфейс, нужно добавить новые разрешения, тогда мне нужно изменить базу данных и классы перечисления одновременно.
Не могу этого вынести ...
Решение
Лучше всего автоматически проверять наличие нового интерфейса при запуске проекта и автоматически синхронизировать код разрешения с базой данных, если он есть.
Поэтому код разрешения должен быть отождествлен с существующей и уникальной вещью.
Это URL-адрес пути запроса интерфейса.
начать
Во-первых, Baidu, в конце концов, я могу об этом подумать, кто-то, должно быть, давно об этом подумал. 🤣
Конечно же, я нашел способ: использоватьInitializingBean
- выполнить
InitializingBeanинтерфейс, дубликатafterPropertiesSetметод. Затем этот метод выполняется при инициализации компонента.
Это не большая проблема, потому что, чтобы получить все пути запросов, вам нужно использоватьApplicationContextизgetBeansWithAnnotationметод. И у меня есть служебный класс, который я могу получить в любое времяApplicationContextобъект.
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @author lww
* @date 2019-03-27 11:20 AM
*/
@Component
public class SpringBeanFactoryUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBeanFactoryUtils.context = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return context;
}
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
}
затем используйтеApplicationContext context = SpringBeanFactoryUtils.getApplicationContext();ПолучатьApplicationContextобъект, результат нулевой...
Конечно, не то чтобы этот метод нельзя было использовать, его нельзя получить с помощью класса-инструмента, но его можно получить прямым внедрением. Но если получится, ничего не останется. Класс инструмента не может быть получен, так как реализация
ApplicationContextAwareинтерфейс. существуетSpringBootВ процессе запуска,InitializingBeanизafterPropertiesSetсуществуетApplicationContextAwareизsetApplicationContextДо. Итак, на этот разgetApplicationContextРезультат нулевой. Но в это время контейнерApplicationContext, поэтому используйтеResourceилиAutowiredможно вводить. ты можешь посмотретьSpringBootпроцесс запуска.
Второй способ
Видеть класс при вводе кодаSpringApplicationRunListener, нажмите, чтобы увидеть этот класс, это интерфейс
ВидетьListener for the {@link SpringApplication} {@code run} method., на первый взгляд, это этот класс.
package org.springframework.boot;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Listener for the {@link SpringApplication} {@code run} method.
* {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
* and should declare a public constructor that accepts a {@link SpringApplication}
* instance and a {@code String[]} of arguments. A new
* {@link SpringApplicationRunListener} instance will be created for each run.
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
* @since 1.0.0
*/
public interface SpringApplicationRunListener {
/**
* 在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
*/
default void starting() {
}
/**
* 当environment构建完成,ApplicationContext创建之前,该方法被调用
*/
default void environmentPrepared(ConfigurableEnvironment environment) {
}
/**
* 当ApplicationContext构建完成时,该方法被调用
*/
default void contextPrepared(ConfigurableApplicationContext context) {
}
/**
* 在ApplicationContext完成加载,但没有被刷新前,该方法被调用
*/
default void contextLoaded(ConfigurableApplicationContext context) {
}
/**
* 在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
*/
default void started(ConfigurableApplicationContext context) {
}
/**
* 在run()方法执行完成前该方法被调用
*/
default void running(ConfigurableApplicationContext context) {
}
/**
* 当应用运行出错时该方法被调用
*/
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
Однако, если вы хотите использовать прослушиватель, вам нужно сначала создать файл конфигурации.
- существует
resourcesСоздайте папку в каталоге с именемMETA-INF - создать файл
spring.factories - Последний настроенный прослушиватель
org.springframework.boot.SpringApplicationRunListener=你实现的监听器类
Вот моя конфигурацияorg.springframework.boot.SpringApplicationRunListener=com.ler.sparrowmanager.config.PermissionInitListener
В реализованном классе слушателя требуется конструктор.Это метод инициализации для SpringBoot для создания слушателя.Если нет, будет сообщено об ошибке.
Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.boot.SpringApplicationRunListener : com.ler.sparrowmanager.config.PermissionInitListener
at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:445)
at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:427)
at org.springframework.boot.SpringApplication.getRunListeners(SpringApplication.java:416)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:304)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
at com.ler.sparrowmanager.SparrowManagerApplication.main(SparrowManagerApplication.java:12)
Caused by: java.lang.NoSuchMethodException: com.ler.sparrowmanager.config.PermissionInitListener.<init>(org.springframework.boot.SpringApplication, [Ljava.lang.String;)
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:440)
... 6 more
public PermissionInitListener(SpringApplication application, String[] args) {
super();
}
копироватьstartedМетод в порядке, ниже приведен полный пример
package com.ler.sparrowmanager.config;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ler.sparrowmanager.domain.SmPermission;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 启动时 扫描所有url 添加到数据库
*
* @author lww
* @date 2020-04-11 09:27
*/
@Slf4j
public class PermissionInitListener implements SpringApplicationRunListener {
public PermissionInitListener(SpringApplication application, String[] args) {
super();
}
@Override
public void started(ConfigurableApplicationContext context) {
//获取application.properties配置中的属性 请求路径
ConfigurableEnvironment environment = context.getEnvironment();
String property = environment.getProperty("server.servlet.context-path");
if (StringUtils.isBlank(property)) {
property = "";
}
SmPermission permission;
//获取所有controller,因为我的Controller使用的都是RestController注解,如果使用的是Controller注解,要自己处理一下
Map<String, Object> beanMap = context.getBeansWithAnnotation(RestController.class);
for (Entry<String, Object> objectEntry : beanMap.entrySet()) {
Object value = objectEntry.getValue();
Class<?> valueClass = value.getClass();
//获取类上的 请求路径
RequestMapping mapping = valueClass.getAnnotation(RequestMapping.class);
String mid = "";
if (mapping != null) {
String[] value1 = mapping.value();
if (value1.length == 1) {
mid = value1[0];
}
}
Method[] methods = valueClass.getMethods();
for (Method method : methods) {
String rname = "";
String rcp = property;
permission = new SmPermission();
//获取方法上的url
RequestMapping rm = method.getAnnotation(RequestMapping.class);
GetMapping gm = method.getAnnotation(GetMapping.class);
PostMapping pm = method.getAnnotation(PostMapping.class);
PutMapping pum = method.getAnnotation(PutMapping.class);
DeleteMapping dm = method.getAnnotation(DeleteMapping.class);
if (rm != null && rm.value().length == 1) {
rcp = rcp + mid + rm.value()[0];
rname = rm.name();
} else if (gm != null && gm.value().length == 1) {
rcp = rcp + mid + gm.value()[0];
rname = gm.name();
} else if (pm != null && pm.value().length == 1) {
rcp = rcp + mid + pm.value()[0];
rname = pm.name();
} else if (pum != null && pum.value().length == 1) {
rcp = rcp + mid + pum.value()[0];
rname = pum.name();
} else if (dm != null && dm.value().length == 1) {
rcp = rcp + mid + dm.value()[0];
rname = dm.name();
} else {
continue;
}
permission.setPermsUrl(rcp);
//RequestMapping,GetMapping这些注解都有一个 name属性,可以用来存 权限名称
permission.setPermName(rname);
//检查是否已经存在,存在不插入
SmPermission selectOne = permission.selectOne(new QueryWrapper<SmPermission>().lambda()
.eq(SmPermission::getPermsUrl, permission.getPermsUrl()));
log.info("PermissionInitScanConfig_started_selectOne:{}", JSONObject.toJSONString(selectOne));
if (selectOne == null) {
//不存在插入,这里用了 Mybatis-Plus的 AR特性
boolean insertPermission = permission.insert();
log.info("PermissionInitScanConfig_started_insertPermission:{}", insertPermission);
}
}
}
}
}
результат:
Выполнить при запуске:
Сохраните в таблице разрешений базы данных:
наконец:
Затем используйте перехватчик, чтобы сравнить запрошенный URL-адрес с URL-адресом, соответствующим роли пользователя, чтобы определить разрешения.
Преимущество этого в том, что при добавлении интерфейса дополнительная настройка не требуется, и он будет автоматически вставлен в таблицу разрешений базы данных при запуске.