Nacos注册中心,服务注册时加入自定义元数据,把各个服务的接口信息放入服务元数据里

最近在做分布式细粒度权限控制,业务需要使用各个服务的接口及权限信息进行权限的校验。
这里把各个接口的权限级别分为三种:

  • 公开权限:所有的客户端都能直接访问,不参与Token校验(不需要登录)、不参与权限校验
  • 内部公开权限:需要登录参与Token校验,不需要进行授权校验,所有登录用户都能访问
  • 完整控制权限:需要登录并通过权限校验才能访问

下面介绍使用Nacos作为服务注册中心和配置中心,在服务注册时把服务的接口信息放入服务元数据里,其他需要监听的服务只需要监听服务注册事件并取出服务实例内的接口信息即可。
定义一个枚举进行公开权限分类PublicScopeEnum

package org.feasy.cloud.auth.config;

/**
 * API公开级别枚举类
 */
public enum PublicScopeEnum {
    /**
     * 内部公开-只要登录了系统 都能访问
     */
    INTERNAL_PUBLIC,
    /**
     * 全部公开-无论有没有登录系统,都会公开
     */
    PUBLIC;
}

自定义注解PublicApi用来标识接口是否是开放接口

package org.feasy.cloud.auth.config;

import java.lang.annotation.*;

/**
 * API开放权限注解
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PublicApi {
    /**
     * 公开域,默认内部公开 即:只要登录了系统都能访问
     */
    PublicScopeEnum scope() default PublicScopeEnum.INTERNAL_PUBLIC ;
}

自定义ApplicationApiContainer存储服务接口信息,并根据开放权限控制类型进行分类

package org.feasy.cloud.auth.config;

import lombok.*;
import lombok.experimental.Accessors;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 应用服务 API容器封装类
 */
@Data
public class ApplicationApiContainer {
    private final String serverKey;
    private final String serverName;
    /**
     * 公开接口集合
     */
    private List<ServerApiBO> publicApis = Collections.synchronizedList(new ArrayList<>());
    /**
     * 内部公开接口集合
     */
    private List<ServerApiBO> internalPublicApis = Collections.synchronizedList(new ArrayList<>());

    /**
     * 纳入权限控制的接口集合
     *
     * @param applicationContext
     */
    private List<ServerApiBO> accessApis = Collections.synchronizedList(new ArrayList<>());

    public ApplicationApiContainer(WebApplicationContext applicationContext, String serverKey, String serverName) {
        this.serverKey = serverKey;
        this.serverName = serverName;
        this.initThisApis(applicationContext);
    }

    private void initThisApis(WebApplicationContext applicationContext) {
        // 取出所有的Mapping
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        // 获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        // 遍历服务接口信息,筛选符合条件的数据
        map.forEach((mappingInfo, handlerMethod) -> {
            // 类
            Class<?> controllerClass = handlerMethod.getBeanType();
            // 包路径
            String classPackage = controllerClass.getName();
            if (verifyClassPackageHasProperties(classPackage, "org.feasy.cloud.**.controller")) {
                // 方法
                Method method = handlerMethod.getMethod();
                // 获取方法请求类型
                String[] methodTypes=this.getMethodTypes(mappingInfo);
                // 获取方法路径
                String[] methodPaths = mappingInfo.getPatternsCondition().getPatterns().toArray(new String[]{});
                // 生成数据
                List<ServerApiBO> serverApiBOS = this.builderServerApiBO(
                        controllerClass.isAnnotationPresent(RequestMapping.class) ? controllerClass.getAnnotation(RequestMapping.class).value()[0] : controllerClass.getSimpleName(),
                        methodTypes,
                        methodPaths
                );
                // 查看类上是否包含@PublicApi注解
                if (controllerClass.isAnnotationPresent(PublicApi.class) || method.isAnnotationPresent(PublicApi.class)) {
                    PublicApi publicApi = controllerClass.isAnnotationPresent(PublicApi.class) ? controllerClass.getAnnotation(PublicApi.class) : method.getAnnotation(PublicApi.class);
                    if (publicApi.scope() == PublicScopeEnum.PUBLIC) {
                        this.publicApis.addAll(serverApiBOS);
                    } else {
                        this.internalPublicApis.addAll(serverApiBOS);
                    }
                } else {
                    this.accessApis.addAll(serverApiBOS);
                }
            }
        });
    }

    /**
     * 生成一个ServerApiBO对象
     */
    private List<ServerApiBO> builderServerApiBO(String moduleKey, String[] methodTypes, String[] methodPaths) {
        List<ServerApiBO> serverApiBOS = new ArrayList<>();
        if (methodTypes.length <= 0) {
            serverApiBOS.add(new ServerApiBO()
                    .setModuleKey(moduleKey)
                    .setModuleName(moduleKey)
                    .setApiType("POST")
                    .setApiPath(this.serverKey + methodPaths[0])
            );
            serverApiBOS.add(new ServerApiBO()
                    .setModuleKey(moduleKey)
                    .setModuleName(moduleKey)
                    .setApiType("PUT")
                    .setApiPath(this.serverKey + methodPaths[0])
            );
            serverApiBOS.add(new ServerApiBO()
                    .setModuleKey(moduleKey)
                    .setModuleName(moduleKey)
                    .setApiType("GET")
                    .setApiPath(this.serverKey + methodPaths[0])
            );
            serverApiBOS.add(new ServerApiBO()
                    .setModuleKey(moduleKey)
                    .setModuleName(moduleKey)
                    .setApiType("DELETE")
                    .setApiPath(this.serverKey + methodPaths[0])
            );
        } else {
            serverApiBOS.add(new ServerApiBO()
                    .setModuleKey(moduleKey)
                    .setModuleName(moduleKey)
                    .setApiType(methodTypes[0])
                    .setApiPath(this.serverKey + methodPaths[0])
            );
        }
        return serverApiBOS;
    }

    private String[] getMethodTypes(RequestMappingInfo mappingInfo){
        List<RequestMethod> requestMethodList = new ArrayList<>(mappingInfo.getMethodsCondition().getMethods());
        String[] methodTypes=new String[requestMethodList.size()];
        for (int i=0;i<requestMethodList.size();i++){
            methodTypes[i]=requestMethodList.get(i).toString();
        }
        return methodTypes;
    }
    /**
     * 验证包路径
     *
     * @param classPackage 需要验证的包路径
     * @param scanPackages 验证条件的包路径,可以传入多个
     * @return 验证结果,只要有一个条件符合,条件就会成立并返回True
     */
    private static boolean verifyClassPackageHasProperties(String classPackage, String... scanPackages) {
        for (String scanPackage : scanPackages) {
            if (Pattern.matches(buildRegexPackage(scanPackage), classPackage)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 转换验证条件,使其支持正则验证
     *
     * @param scanPackage 验证条件包路径
     * @return 验证条件正则
     */
    private static String buildRegexPackage(String scanPackage) {
        return scanPackage.replace("**", "[\\w]*") + ".[\\w]*";
    }

    @Data
    @EqualsAndHashCode(callSuper = false)
    @Accessors(chain = true)
    class ServerApiBO {
        private String id;
        private String moduleKey;
        private String moduleName;
        private String apiName;
        private String apiPath;
        private String apiType;
    }
}

自定义NacosDiscoveryClientConfiguration配置类修改元数据

package org.feasy.cloud.auth.config;


import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.discovery.NacosWatch;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.feasy.cloud.redis.conf.FastJsonRedisSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.WebApplicationContext;

import java.text.SimpleDateFormat;
import java.util.*;

/**
 * nacos客户端注册至服务端时,更改服务详情中的元数据
 */
@Slf4j
@Configuration
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class})
public class NacosDiscoveryClientConfiguration {
    @Value("${spring.application.name}")
    private String applicationName;

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(value = {"spring.cloud.nacos.discovery.watch.enabled"}, matchIfMissing = true)
    public NacosWatch nacosWatch(NacosDiscoveryProperties nacosDiscoveryProperties, WebApplicationContext webApplicationContext) {
        //更改服务详情中的元数据,增加服务注册时间
        nacosDiscoveryProperties.getMetadata().put("startup.time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        nacosDiscoveryProperties.getMetadata().put("apis", JSONObject.toJSONString(new ApplicationApiContainer(webApplicationContext,applicationName,applicationName)));
        return new NacosWatch(nacosDiscoveryProperties);
    }

}

启动服务注册到Nacos注册中心查看服务元数据:
在这里插入图片描述

热门文章

暂无图片
编程学习 ·

ubuntu软件安装

ubuntu软件安装apt和apt-getdpkg源码安装 apt和apt-get 在ubuntu安装软件时常用命令apt-get install xxx命令来安装。从字面上理解install即是安装的意思,get即获取的意思,apt此处理解为工具名称,全称 Advanced Packaging Tool(APT)字义是先进的包装工具,但在linux系统中…
暂无图片
编程学习 ·

数据结构与数据类型

数据结构与数据类型数据类型是面向应用领域的具体化,同时面向计算机系统底层是为了确定分配的内存容量的大小。 在C,JAVA等静态类型的编程语言中,编译器根据数据类型,提前在内存的进程的栈中分配特定 大小的空间。C 的malloc,和Java的new是动态分配大块内存的,提前在内存…
暂无图片
编程学习 ·

一线互联网大厂300多道Java面试题【全面解析】,助你备战“金九银十”、进军BAT、斩获offer必备的核心知识点

前言今年因为疫情原因,很多人在家里宅了很长一段时间,“金三银四”黄金季也随之而然的“泡汤”,所有的跳槽涨薪的黄金季都集中在了“金九银十”季,所以程序员的竞争会对比往年更加激烈,为了备战“金九银十”需要有充足的时间复习筹备,为面试做足准备。我这里这筹备了一份…
暂无图片
编程学习 ·

电商新手做亚马逊要怎样开始?

"说到互联网创业,很多人的第一个想到的是淘宝,但是很多人并不清楚,经过十几年的发展淘宝已经很难再进入了,利润也是下降到了最低,很多的卖家都在寻找机会做转型,而你一个毫无经验的小白现在进入,基本可以说很难生存,近年来,我国的跨境电子商务进入迅猛的发展阶段,…
暂无图片
编程学习 ·

mac mysql更改了目录所遇到的坑

之前安装的目录为/usr/local/develope/mysql后来改了下目录 同时也改了MySQL文件夹名现在为/usr/local/develope/develop/mysql5.6 同时data目录还是在的配置文件已经修改 MySQL在安装或者启动的时候没有指定配置文件时候 默认找的配置文件/etc/my.cnf将basedir目录和data目录修…
暂无图片
编程学习 ·

必应每日壁纸——7月

只分享,不科普 自行必应科普July1 Wednesday2 Thursday3 Friday4 Saturday5 Sunday6 Monday7 Tuesday8 Wednesday9 Thursday10 Friday11 Saturday12 Sunday July 1 Wednesday 班夫国家公园 莫兰湖德国卡塞尔威廉高地公园中的阿波罗神庙2 Thursday 3 Friday 4 Saturday 5 Sunda…
暂无图片
编程学习 ·

Strategies For Pre-Training Graph Neural Networks

Paper : STRATEGIES FOR PRE-TRAINING GRAPH NEURAL NETWORKS Code : official摘要 作者解决的问题是如何预训练一个GNN网络,保证预训练的结果在具体数据集中finetune不会negative transfer 的现象。作者在文中并没有细致的解释为什么GNN上进行transfer learning 会更难,这个…
暂无图片
编程学习 ·

HDFS架构

五.HDFS架构大多数分布式大数据框架都是主从架构HDFS也是主从架构Master|Slave或称为管理节点|工作节点主叫NameNode,中文称“名称节点”从叫DataNode,中文称“数据节点”5.1 NameNode5.1.1 文件系统file system文件系统:操作系统中负责管理文件、存储文件信息的软件具体地说…
暂无图片
编程学习 ·

程序员:Java数据结构与算法——第十六章·算法设计技术详解

Java数据结构与算法-第十六章算法设计16.1引言在求解一个新问题时,通常的思路是寻找当前问题与已解决问题之间的相似之处,从而轻松找到新问题的求解方法。本章将对各种算法按照不同的方法进行分类,然后在随后的3章中分别介绍3个算法设计思想(即贪婪、分治和动态规划)。16.2分…
暂无图片
编程学习 ·

集合类

Collection集合: 集合是Java提供的一种容器,可以用来存储多个数据。数组也是容器,但是数组的长度是固定的,集合的长度是可变的 数组中存储的都是同一类型的元素,可以存储基本数据类型的值;集合存储的都是对象,而且对象的类型可以不一致。 在开发中,一般对象多的时候,使…
暂无图片
编程学习 ·

UE4学习-添加机关并添加代码控制

文章目录添加机关代码编写给密室添加屋顶打印日志控制系统角色创建一个新游戏模式替换DefaultPawn添加抓取组件获取起点和终点物体拾取,碰撞属性设置今日完整代码 添加机关 首先向场景里面添加一个聚光源添加聚光源以后,可以对其属性进行修改,如图:然后需要给聚光源添加一个…
暂无图片
编程学习 ·

面试被问傻了,同事说不懂volatile关键字,由浅入深讲解volatile

面试被问傻了,同事说不懂volatile关键字,由浅入深讲解volatile前言随着互联网企业的兴起,对我们技术的要求也越来越高,很多时候企业又想省钱,又想发挥出机器的最大性能,真是累坏了程序员们。当然,想要适应社会的进步,程序员也要不断的给自己充电,但人能忘本,基础知识…
暂无图片
编程学习 ·

centos自用命令备份

上传 scp -p E:\abc\requirement.txt root@132.232.10.218:/root/stock 下载 scp root@103.51.15.130:/root/project/log/2020-01-07-12-22-15.log D:\ ----------------------------------------- 启动多个脚本 python3 zmq.py & python3 open.py & python3 clos.py &…
暂无图片
编程学习 ·

SDL环境搭建

一、SDL简单来说SDL就是封装了复杂的音视频底层操作,简化了音视频处理的难度。主要用于游戏开发和多媒体开发领域。而且SDL是C语言编写,可以跨平台使用。二、环境搭建SDL的环境搭建及其简单,直接在官网下载开发库就可以了;下载地址:http://www.libsdl.org/download-2.0.ph…