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注册中心查看服务元数据:
在这里插入图片描述

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清晨先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值