Связано с разрешением — SpringBoot получает все URL-адреса пути запроса при запуске

Java

Введение

Недавно я работаю над небольшим проектом, который включает в себя разрешения. не использовал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-адресом, соответствующим роли пользователя, чтобы определить разрешения.
Преимущество этого в том, что при добавлении интерфейса дополнительная настройка не требуется, и он будет автоматически вставлен в таблицу разрешений базы данных при запуске.