No6-3.从零搭建spring-cloud-alibaba微服务框架,实现资源端用户认证与授权等(三,no6-3)

news2024/11/16 17:33:31

  代码地址与接口看总目录:【学习笔记】记录冷冷-pig项目的学习过程,大概包括Authorization Server、springcloud、Mybatis Plus~~~_清晨敲代码的博客-CSDN博客

之前只零碎的学习过spring-cloud-alibaba,并没有全面了解过,这次学习pig框架时,想着可以根据这个项目学习一下,练练手,于是断断续续的用了几天时间搭建了一下基础框架。目前就先重点记录一下遇到的问题吧,毕竟流程也不是特别复杂,就是有的东西没遇到过了解的也不深~

由于微服务包括认证这里内容太多,所以分了好几篇~

第一篇文章:No6.从零搭建spring-cloud-alibaba微服务框架,实现fegin、gateway、springevent等(一)_清晨敲代码的博客-CSDN博客

文章包括:

1.将服务系统注册到nacos注册中心;

2.通过nacos实现配置动态更新;

3.添加fegin服务,实现服务之间调用;

4.添加网关(学会使用webflux,学会添加过滤器);

5.添加log服务,通过springevent实现,并使用注解使用(使用AOP);

第二篇文章:

No6.从零搭建spring-cloud-alibaba微服务框架,实现数据库调用、用户认证与授权等(二,no6-2)_清晨敲代码的博客-CSDN博客

文章包括:

6.添加 mysql 数据库调用,并使用mybatis-plus操作;

7.在认证模块添加用户认证,基于oauth2的自定义密码模式(已认证用户是基于自定义token加redis持久化,不是session);

本篇文章包括:

8.在资源端模块添加用户认证,基于授权端的认证token添加逻辑(但是没有处理微服务间的不鉴权调用,微服务间的调用接口都是白名单呢!);

剩余包括(会有变动):

9.解决微服务间的不鉴权调用(优化外部访问鉴权逻辑~)

10.添加用户权限校验等逻辑;

目录

A8.在资源端模块添加用户认证,基于授权端的认证token添加逻辑;

oauth2资源端自定义密码模式调用图:

遇到的问题:


A8.在资源端模块添加用户认证,基于授权端的认证token添加逻辑;

再给 pig-upms模块添加 security 鉴权前,突然意识到 pig-auth 模块认证的时候是会远程调用它的接口的,如果这个接口需要鉴权后才能调用,这样就需要 pig-auth 提供认证信息了。可pig-auth 调用接口本身就是要进行认证的,这不就成死循环了吗?

而且抛开这个问题,后续开发中会有多个微服务之间进行远程调用,难道都需要携带认证信息吗?

于是在理解了 pig 项目里面的微服务之间的调用逻辑后,可以参考他的文档:feign调用及服务间鉴权 · 语雀和Inner注解使用说明 · 语雀,反正我有些没懂,然后看了源码后才理解,于是写了篇笔记防止自己忘掉:【pig-cloud项目】关于@Inner和@PreAuthorize的理解,以及微服务内外部间的调用认证鉴权理解

在开始前,首先明确目标,先实现资源端的用户认证,微服务间的调用在下一个A9里面实现,那么这里就需要注意要先将A7里面用户认证所需要的接口添加到白名单里面,不然是没有认证权限,就永无法调用的!

开始步骤:

1.不用导包,因为authorization-server包里面带有resource-server包;

2.在pig-common-security模块添加security白名单PermitAllUrlProperties,对外暴露URL,可以添加到nacos配置中心;

3.在pig-common-security模块添加请求认证的决策器BearerTokenResolver,对于白名单不进行认证,其余路径必须经过token认证,不认证则不能访问;

4.在pig-common-security模块添加自定义令牌自省类OpaqueTokenIntrospector,通过token拿到授权端用户认证信息,然后进行用户认证(也可以直接认证成功,添加是为了防止用户信息更改);

5.在pig-common-security模块添加认证异常处理类AuthenticationEntryPoint,处理token失效、无token、token异常等情况;

6.在pig-common-security模块将需要bean的类通过PigResourceServerAutoConfiguration注入容器;

7.在pig-common-security模块在PigResourceServerConfiguration添加资源端的安全过滤链,要将oauth2ResourceServer配置成我们自定义的;

8.最后在pig-common-security模块将资源端认证配置类封装成了注解,然后加到需要的资源端启动类上!

注意:由于之前upms模块不需要security,所以在启动类上exclude = SecurityAutoConfiguration.class,现在记得要去掉这个~

步骤代码:

//2. 源服务器对外直接暴露URL
package com.pig4cloud.pig.common.security.component;

@Slf4j
@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class PermitAllUrlProperties {

    @Getter
    @Setter
    private List<String> urls = new ArrayList<>();

}
//----------------------------
//在nacos的application-dev.yml里面添加配置

# 自定义的 spring security 配置,目前只对资源端认证有效(也就是只有资源端认证配置有用到);注意,如果有{##}的可能会匹配多个路径,需要对路径添加规则,并检查哦~

security:
  oauth2:
    # 通用放行URL,服务个性化,请在对应配置文件覆盖
    ignore:
      urls:
        - /user/info/*
        - /client/getClientDetailsById/*
//3.请求认证的决策器,白名单不进行认证

public class PigBearerTokenExtractorResolver implements BearerTokenResolver {

    /**
     * 可以设置为灵活的配置项
     */
    private boolean allowFormEncodedBodyParameter = false;

    private boolean allowUriQueryParameter = false;

    /**
     * 路径白名单
     */
    private final PermitAllUrlProperties permitAllUrlProperties;

    /**
     * Pattern可以看作是一个正则表达式的匹配模式,Matcher可以看作是管理匹配结果
     */
    private static final Pattern authenticationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$", Pattern.CASE_INSENSITIVE);

    private final PathMatcher pathMatcher = new AntPathMatcher();

    public PigBearerTokenExtractorResolver(PermitAllUrlProperties urlProperties) {
        this.permitAllUrlProperties = urlProperties;
    }


    @Override
    public String resolve(HttpServletRequest request) {
        //校验请求路径,校验是否是白名单,是直接返回null
        boolean match = permitAllUrlProperties.getUrls().stream().anyMatch(url -> pathMatcher.match(url, request.getRequestURI()));
        if(match){
            return null;
        }

        //校验hearder里的accesstoken格式是否匹配,并返回token
        final String headerToken =resolveFromHeader(request);
        //校验请求方式是否是GET/POST,是就从参数中获取accesstoken,并返回token
        final String parameterToken = isParameterSupportedForRequest(request) == true ? resolveFromParameters(request) : null ;
        //判断如果headertoekn不是空
        if(StringUtils.hasText(headerToken)){
            //如果parametertoekn不是空则抛出多个accesstoken异常
            if(StringUtils.hasText(parameterToken)){
                final BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
                throw new OAuth2AuthenticationException(error);
            }
            //如果parametertoekn是空则返回headertoekn
            return headerToken;
        }

        //如果parametertoekn不是空并且支持参数的token,则返回parameteken
        if(StringUtils.hasText(parameterToken) && isParameterEnableForRequest(request)){
            return parameterToken;
        }

        //都是空则返回null
        return null;
    }


    private boolean isParameterEnableForRequest(HttpServletRequest request) {
        return (this.allowFormEncodedBodyParameter && HttpMethod.POST.equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())
                || this.allowUriQueryParameter && HttpMethod.GET.equals(request.getMethod()));
    }

    private boolean isParameterSupportedForRequest(HttpServletRequest request) {
        return (HttpMethod.POST.equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())
                || HttpMethod.GET.equals(request.getMethod()));
    }

    private String resolveFromHeader(HttpServletRequest request) {
        String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
        //判断是否是 bearer 开头
        if(!StringUtils.startsWithIgnoreCase(authorization, "bearer")){
            return null;
        }
        Matcher matcher = authenticationPattern.matcher(authorization);
        //判断是否能匹配上
        if(!matcher.matches()){
            BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
            throw new OAuth2AuthenticationException(error);
        }
        return matcher.group("token");
    }

    private String resolveFromParameters(HttpServletRequest request) {
        String[] values = request.getParameterValues("access_token");
        if (values == null || values.length == 0) {
            return null;
        }
        if (values.length == 1) {
            return values[0];
        }
        BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
        throw new OAuth2AuthenticationException(error);
    }

}
//4.令牌自省
//由于用户认证的信息都存到了 redis 里面,所以所有服务都可以通过 token 从 redis 里面拿到用户认证信息


@Slf4j
@RequiredArgsConstructor
public class PigCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {

    private final OAuth2AuthorizationService authorizationService;

    @Override
    public OAuth2AuthenticatedPrincipal introspect(String token) {
        //根据 token 从 redis 里面拿到用户认证信息
        OAuth2Authorization authAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
        if (Objects.isNull(authAuthorization)) {
            throw new InvalidBearerTokenException(token);
        }

        //从容器中获取到 UserDetailsService bean
        Map<String, PigUserDetailsServiceImpl> userDetailsServiceMap = SpringUtil
                .getBeansOfType(PigUserDetailsServiceImpl.class);

        Optional<PigUserDetailsServiceImpl> optional = userDetailsServiceMap.values().stream()
                .max(Comparator.comparingInt(Ordered::getOrder));

        UserDetails userDetails = null;
        try{
            //由于在授权端认证过程中会给 OAuth2Authorization 的 attributes 添加 <Principal,upAuthenticationToken>,所以直接拿
            Object principal = Objects.requireNonNull(authAuthorization.getAttributes().get(Principal.class.getName()));
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal;
            Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal();
            //拿到授权端认证的用户信息后,可以在这里再认证一遍
            userDetails = optional.get().loadUserByUser((PigUser) tokenPrincipal);
        }catch (UsernameNotFoundException notFoundException) {
            log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage());
            throw notFoundException;
        }catch (Exception ex) {
            log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage());
        }
        return (PigUser) userDetails;
    }
}
//5.客户端异常处理 AuthenticationException 不同细化异常处理,匿名用户访问无权限资源时的异常

@RequiredArgsConstructor
public class ResourceAuthExceptionEntryPoint  implements AuthenticationEntryPoint {
    private final ObjectMapper objectMapper;

    @Override
    @SneakyThrows
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
        response.setCharacterEncoding(CommonConstants.UTF8);
        response.setContentType(CommonConstants.CONTENT_TYPE);
        R<String> result = new R<>();
        result.setCode(CommonConstants.FAIL);
        response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
        if (authException != null) {
            result.setMsg("error");
            result.setData(authException.getMessage());
        }

        // 针对令牌过期返回特殊的 424
        if (authException instanceof InvalidBearerTokenException) {
            response.setStatus(org.springframework.http.HttpStatus.FAILED_DEPENDENCY.value());
            result.setMsg("token expire");
        }
        PrintWriter printWriter = response.getWriter();
        printWriter.append(objectMapper.writeValueAsString(result));
    }

}
//6.资源端的自动配置项

@RequiredArgsConstructor
@EnableConfigurationProperties(PermitAllUrlProperties.class)
public class PigResourceServerAutoConfiguration {

    /**
     * @Description: 请求认证的决策器,白名单不进行认证
     * @param urlProperties 对外暴露的接口列表
     * @Return: com.pig4cloud.pig.common.security.component.PigBearerTokenExtractorResolver
     */
    @Bean
    public PigBearerTokenExtractorResolver pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
        return new PigBearerTokenExtractorResolver(urlProperties);
    }

    /**
     * 资源端认证异常
     * @param objectMapper jackson 输出对象
     * @return ResourceAuthExceptionEntryPoint
     */
    @Bean
    public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper) {
        return new ResourceAuthExceptionEntryPoint(objectMapper);
    }

    /**
     * 资源服务器toke内省处理器
     * @param authorizationService token 存储实现
     * @return TokenIntrospector
     */
    @Bean
    public OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {
        return new PigCustomOpaqueTokenIntrospector(authorizationService);
    }

}
//7.资源服务器认证授权配置

@Slf4j
@EnableWebSecurity
@RequiredArgsConstructor
public class PigResourceServerConfiguration {

    private final PigBearerTokenExtractorResolver bearerTokenExtractorResolver;

    private final PigCustomOpaqueTokenIntrospector opaqueTokenIntrospector;

    protected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;

    private final PermitAllUrlProperties permitAllUrlProperties;

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    SecurityFilterChain securityFilterChain(HttpSecurity http)throws Exception {

        http.authorizeRequests(auththorized ->
                auththorized.antMatchers(ArrayUtil.toArray(permitAllUrlProperties.getUrls(), String.class)).permitAll()
                        .anyRequest().authenticated()
        ).oauth2ResourceServer(oauth ->
                oauth.bearerTokenResolver(bearerTokenExtractorResolver)
                        .opaqueToken(opaqueTokenConfigurer -> opaqueTokenConfigurer.introspector(opaqueTokenIntrospector))
                        .authenticationEntryPoint(resourceAuthExceptionEntryPoint)
        );
        http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        return http.build();

    }
}
//8.资源服务注解

@Documented
@Inherited        //@Inherited修饰的注解的@Retention是RetentionPolicy.RUNTIME,则增强了继承性,在反射中可以获取得到
@Target({ ElementType.TYPE })	//@Target注解的作用目标,接口、类、枚举、注解
@Retention(RetentionPolicy.RUNTIME)	//注解的保留位置
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Import({ PigResourceServerAutoConfiguration.class, PigResourceServerConfiguration.class})
public @interface EnablePigResourceServer {
}


//--------------------

@EnablePigResourceServer
@EnableFeignClients(basePackages = "com.pig4cloud.pig")
@EnableDiscoveryClient
//@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@SpringBootApplication
public class PigAdminApplication {

    public static void main(String[] args) {
        SpringApplication.run(PigAdminApplication.class, args);
    }

}

启动程序,先从auth里面拿到 token ,然后去用户模块测试,不添加token访问,会提示错误,添加正确的token冯文就会成功~

oauth2资源端自定义密码模式调用图:

防止忘记

遇到的问题:

拿到token访问 upms 模块接口时,已经根据token拿到用户信息了,但是在OpaqueTokenAuthenticationProvider里面报错了!!!

问题就是写 PigUser 类时,实现了 OAuth2AuthenticatedPrincipal 接口,会重写他的getAttributes()方法,而 OpaqueTokenAuthenticationProvider 里面拿的就是 OAuth2AuthenticatedPrincipal#getAttributes() !由于我写这个类的时候没有修改,直接就是 null ,于是报错了~ 需要返回个对象,否则会报错!

/**
 * @author QingChen
 * @Description 扩展用户认证时的信息
 * @date 2022-10-25 11:59
 * @Version 1.0
 */

public class PigUser extends User implements OAuth2AuthenticatedPrincipal {

...

    @Override
    public Map<String, Object> getAttributes() {
        return new HashMap<>();
    }

    @Override
    public String getName() {
        return this.getUsername();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python】入门,总结与拾遗

前言 根据我的实践经验来看&#xff0c;对于有编程基础&#xff08;如C语言&#xff09;的同学来说&#xff0c;python入门应该很简单&#xff0c;而且加上官方文档做得非常好&#xff0c;所以个人建议python的学习主要去参考官网。本博客主要是记录那些容易忽略的关键点以及对…

蓝桥杯单片机第七届省赛题详细讲解(温度记录器)

看之前强烈建议先自己做一遍&#xff01;&#xff01;&#xff01;演示效果题目讲解代码main.cds1302.cds1302.honewire.conewire.h工程文件演示效果 题目讲解 首先我们从系统框图看起&#xff1a; 做题之前要做的是把系统框图里面的各个模块先提前调试好&#xff01;&#x…

StarRocks 的学习笔记

StarRocks 的学习笔记 文章目录StarRocks 的学习笔记1. 介绍1.1 StarRocks 特性1.2 使用场景1.3 OLAP 多维分析1.4 实时数据仓库1.5 高并发查询1.6 统一分析2. 系统架构2.1 StarRocks架构2.2 StarRocks业务架构2.3 StarRocks 数据生态2.4 *数据管理3. 特性3.1 MPP分布式执行框架…

ES6中的set与map

文章目录1.set1.1创建set集合1.2 如何对set集合进行后续操作1.3 如何与数组进行相互转换1.4 如何遍历1.5set的应用2.map集合2.1 如何创建map2.2 如何进行后续操作2.3 如何与数组转换2.4 遍历一直以来&#xff0c;JS只能使用数组和对象来保存多个数据&#xff0c;缺乏像其他语言…

介绍document部分自带的方法及属性,场景使用例如倒计时等

适用场景页面不显示&#xff08;不看&#xff09;触发方法停止运行逻辑&#xff0c;页面显示&#xff08;看&#xff09;触发方法继续运行逻辑。 &#xff08;例如倒计时&#xff09; 操作手法触发&#xff0c;浏览器多页签切换时触发&#xff0c;或者当前页隐藏浏览器&#xf…

飞象星球落地舟山36所学校 双师课堂化解课后服务区域资源差异

傍晚的阳光正好&#xff0c;此时舟山二小北校区五年级学生周陈延正紧盯大屏幕&#xff0c;沉醉在《航天探秘》飞象星球双师素质课堂带来的知识奥义中。作为浙江极具特色的海岛城市&#xff0c;舟山正进行着有关课后服务领域的新探索&#xff0c;双师素质课堂便是“千岛之城”舟…

VSCODE联合ModelSim语法检错

Vscode联合ModelSim检错 一、Vscode配置 首先在 vs code 中安装支持 Verilog 的插件&#xff1a; 在 vs code 的 Extension 中搜索 Verilog&#xff0c;安装如下图所示的插件&#xff1b; 二、ModelSim语法检查器检查 Modelsim的安装破解本文不再赘述&#xff0c;可选的Mod…

【初阶数据结构】第一篇——时间复杂度和空间复杂度详解(C描述)

文章目录前言什么是数据结构&#xff1f;什么是算法&#xff1f;1. 算法效率1.1如何衡量一个算法的好坏1.2 算法的复杂度1.2 复杂度在校招中的考察2. 时间复杂度2.1 时间复杂度的概念2.2 大O的渐进表示法2.3 常见时间复杂度计算举例例1双重循环例2. 多未知数例3. 常数次循环例4…

【深度学习】卷积神经网络之双阶段目标检测|R-CNN、SPP-Net、Fast-RCNN、Faster-RCNN、R-FCN

文章目录基本概念一、R-CNN1. 网络结构2. 训练流程3. 测试阶段5.RNN存在的问题二、SPP-Net1. 网络结构2. 基础知识共享卷积计算金字塔池化 Spatial Pyramid Pooling3. 训练流程4. 测试流程5. 存在问题三、 Fast R-CNN1. 网络结构2. 基础知识感兴趣区域池化层 (ROI pooling)多任…

别再被数据分析“割韭菜了”,光学python、BI没有用,上项目才行

前几天有个粉丝找我&#xff0c;说花699报了一门数据分析课程&#xff0c;还有实战项目&#xff0c;让我帮她看看她做的数据分析。项目的名称叫&#xff1a;豆瓣高分电影分析。她写了一大堆内容&#xff0c;我也没细看&#xff0c;截取几张可视化图表给大家&#xff0c;大家觉得…

嵌入式串口转CAN模块详细参数分析

引脚定义和尺寸 测试评估板 将模块插到评估板上&#xff0c;注意模块引脚标注要与评估板上插座引脚标注相对应&#xff0c;然后进行参数设置。特别的&#xff0c;也可以在模块集成到电路板上后&#xff0c;直接通过模块的CAN口来配置参数。 通过CAN通道配置参数 模块集成到用…

基于javaweb+mysql的就业管理系统设计和实现(java+springboot+ssm)

基于javawebmysql的就业管理系统设计和实现(javaspringbootssm) 运行环境 Java≥8、MySQL≥5.7 开发工具 eclipse/idea/myeclipse/sts等均可配置运行 适用 课程设计&#xff0c;大作业&#xff0c;毕业设计&#xff0c;项目练习&#xff0c;学习演示等 功能说明 基于jav…

windows10下安装fbprophet及使用虚拟环境

Prophet是Facebook 在2017年2月开源的一款基于 Python 和 R 语言的时间序列预测框架&#xff0c;也是一种数据分析工具。github官网&#xff1a;https://github.com/facebookincubator/prophet prophet是基于可分解&#xff08;趋势季节节假日&#xff09;模型的开源库&#xf…

【云原生 | 从零开始学istio】五、istio灰度发布以及核心资源

istio灰度发布接着上一章部署bookinfo通过 Istio 实现灰度发布什么是灰度发布&#xff1f;使用 istio 进行灰度发布istio 核心资源解读GatewayVirtualServiceDestinationRule写在最后接着上一章部署bookinfo 1.进入 istio 安装目录。 2.istio 默认自动注入 sidecar&#xff0c…

面试题 17.04. 消失的数字

顺序表题目消失的数字1、题目详情2、题目详解&#xff08;1&#xff09;方法1&#xff08;2&#xff09;方法2&#xff08;3&#xff09;方法3&#xff08;4&#xff09;方法4&#xff1a;消失的数字 1、题目详情 题目链接&#xff1a;leetcode消失的数字 数组nums包含从0到…

CSS——基础学习

目录 一&#xff0c;什么是CSS 二&#xff0c;基本语法规范 三&#xff0c;引入方式 1.内部样式表 2.行内样式表 3.外部样式 四&#xff0c;代码风格 1.样式格式 (1).紧凑风格 (2).展开风格(推荐) 2.样式大小写 3.空格规范 五&#xff0c;选择器 1.选择器的种类 …

牛客网-《刷C语言百题》第二期

✅作者简介&#xff1a;嵌入式入坑者&#xff0c;与大家一起加油&#xff0c;希望文章能够帮助各位&#xff01;&#xff01;&#xff01;&#xff01; &#x1f4c3;个人主页&#xff1a;rivencode的个人主页 &#x1f525;系列专栏&#xff1a;《C语言入门必刷百题》 &#x…

[每周一更]-(第18期):Postman全局配置token信息,加速测试接口进度

Postman作为API调试工具&#xff0c;对于后端开发至关重要&#xff0c;开发、测试、写文档&#xff0c;都必不可少&#xff0c;但是日常使用过程中都是token校验&#xff0c;不同接口之间都会携带token头信息&#xff0c;但是接口的情况&#xff0c;除了写请求参数也要一个个配…

FCOS相关

因为用到了某家带bpu的(懂的都懂) 他们支持这个只是demo做的有点差 还没有c的~~ 因为他们用所以就搬来了 勿怪啊 基于昨天和他们相关的tops 又说说这个!! FCOS是一种基于全卷积的单阶段目标检测算法&#xff0c;并且是一种Anchor box free的算法。其实现了无Anchor&#xff…

该反省了!元数据管理平台为什么会被当成一件“摆设”?

尽管企业越来越意识到元数据管理的重要性&#xff0c;但是在实际中很多应用并没有发挥应有的价值。 前不久与一个行业客户沟通&#xff0c;他提出让他们帮着总结一下元数据管理到底有哪些应用场景&#xff0c;他感觉元数据管理平台就是一种摆设呢&#xff1f; 说者无意听者有心…