springcloud-gateway集成knife4j(swagger3)

news2024/11/16 1:30:07

springcloud-gateway集成knife4j(swagger3)

  • springcloud-gateway集成knife4j(swagger3)
    • 环境信息
    • 准备工作
    • 微服务集成knife4j
      • 第一步:编写Knife4jApiInfoProperties
      • 第二步:编写配置类Knife4jConfig
      • 第三步:放行相关资源 & 保证启动了knife4j
    • 网关集成knife4j
      • 编写配置类Knife4jGatewayConfig
    • 测试验证
    • 相关资料

环境信息

  • spring-boot:2.6.13
  • spring-cloud-alibaba:2021.0.5.0
  • knife4j-spring-boot-starter:3.0.3

准备工作

各微服务&网关引入依赖

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

微服务集成knife4j

第一步:编写Knife4jApiInfoProperties

import com.ideaaedi.springcloud.jd.commonspring.config.Knife4jConfig;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * api 基础信息配置。更多配置信息项见{@link Knife4jConfig}
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2021.0.1.D
 */
@Data
@Component
public class Knife4jApiInfoProperties {
    
    /**
     * 要扫描api的base包
     */
    @Value("${api-info.base-package:com}")
    private String basePackage;
    
    /**
     * 是否启用登录认证
     */
    @Value("${api-info.enable-security:true}")
    private boolean enableSecurity;
    
    /**
     * 文档标题
     */
    @Value("${api-info.title:}")
    private String title;
    
    /**
     * 文档描述
     */
    @Value("${api-info.description:api info}")
    private String description;
    
    /**
     * 文档版本
     */
    @Value("${api-info.version:1.0.0}")
    private String version;
    
    /**
     * 联系人姓名
     */
    @Value("${api-info.contact-name:JustryDeng}")
    private String contactName;
    
    /**
     * 联系人网址
     */
    @Value("${api-info.contact-url:https://gitee.com/JustryDeng/projects}")
    private String contactUrl;
    
    /**
     * 联系人邮箱
     */
    @Value("${api-info.contact-email:13548417409@163.com}")
    private String contactEmail;
}

第二步:编写配置类Knife4jConfig

import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.github.xiaoymin.knife4j.core.model.MarkdownProperty;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import com.ideaaedi.springcloud.jd.commonds.constant.BaseConstant;
import com.ideaaedi.springcloud.jd.commonds.support.EnumDescriptor;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ModelSpecificationBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.PropertySpecificationBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Knife4j配置
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 1.0.0
 */
@Slf4j
@Configuration
@EnableSwagger2
public class Knife4jConfig implements WebMvcConfigurer {
    
    /** 对于管控了权限的应用,应放行以下资源 */
    public static String[] RESOURCE_URLS = new String[]{"/webjars/**", "/swagger**", "/v3/api-docs", "/doc.html"};
    
    @Value("${spring.application.name:default}")
    private String applicationName;
    
    @Resource
    private OpenApiExtensionResolver openApiExtensionResolver;
    
    @Bean
    public Docket docket() {
        // 指定使用Swagger2规范
        Docket docket = new Docket(DocumentationType.OAS_30)
                .apiInfo(new ApiInfoBuilder()
                        // 简介(支持Markdown语法)
                        .description("# 我是API简介")
                        // 服务地址
                        .termsOfServiceUrl("http://local.idea-aedi.com/")
                        // 作者及联系信息
                        .contact(new Contact("JustryDeng", "https://gitee.com/JustryDeng", "13548417409@163.com"))
                        // api版本
                        .version("1.0.0")
                        .build())
                .extensions(openApiExtensionResolver.buildExtensions(applicationName))
                //分组名称(微服务项目可以用微服务名分组)
                .groupName(applicationName)
                .select()
                // 定位api
                .apis(
                        RequestHandlerSelectors.basePackage(getProjectBasePackage())
                                .and(RequestHandlerSelectors.withClassAnnotation(RestController.class)
                                        .or(RequestHandlerSelectors.withClassAnnotation(Controller.class))
                                )
                )
                .paths(PathSelectors.any())
                .build();
        
        docket.securitySchemes(securitySchemes()).securityContexts(securityContexts());
        
        // 自定义文档解析
        try {
            Field markdownPropertiesField = FieldUtils.getDeclaredField(OpenApiExtensionResolver.class,
                    "markdownProperties", true);
            List<MarkdownProperty> markdownProperties = (List<MarkdownProperty>)markdownPropertiesField.get(openApiExtensionResolver);
            if (!CollectionUtils.isEmpty(markdownProperties)) {
                for (MarkdownProperty markdownProperty : markdownProperties) {
                    docket.extensions(openApiExtensionResolver.buildExtensions(markdownProperty.getGroup()));
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        
        return docket;
    }
    
    private List<SecurityScheme> securitySchemes() {
        // 设置请求头信息
        List<SecurityScheme> result = new ArrayList<>();
        // 第一个参数name,自定义即可。 在配置securityContexts时,通过此name对应到apiKey即可
        // 第二个参数,header name自定义即可。 如:JWT_TOKEN_KEY=Auth-Token,然后在代码里request.getHeader(JWT_TOKEN_KEY)取值
        ApiKey apiKey = new ApiKey("auth-info", BaseConstant.JWT_TOKEN_KEY, "header");
        result.add(apiKey);
        return result;
    }
    
    private List<SecurityContext> securityContexts() {
        // 设置需要登录认证的路径
        List<SecurityContext> result = new ArrayList<>();
        List<SecurityReference> securityReferences = defaultAuth();
        result.add(
                SecurityContext.builder().securityReferences(securityReferences).forPaths(
                        // 当直接使用swagger文档发送请求时,这些api需要满足securityReferences认证要求
                        PathSelectors.regex("/.*")
                                .and(
                                        // 当直接使用swagger文档发送请求时,这些api不需要满足securityReferences认证要求. '.*'表示匹配所有
                                        PathSelectors.regex("/hello.*").or(PathSelectors.regex("/hi.*"))
                                                .negate()
                                )
                ).build()
        );
        return result;
    }
    
    private List<SecurityReference> defaultAuth() {
        List<SecurityReference> result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        // 这里指定使用哪个apiKey进行认证鉴权. 这里指定使用上面名为auth-info的apiKey
        result.add(new SecurityReference("auth-info", authorizationScopes));
        return result;
    }
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
    
    /**
     * 获取项目包前缀
     */
    private String getProjectBasePackage() {
        String projectBasePackage;
        String currPackageName = this.getClass().getPackage().getName();
        String[] packageItemArr = currPackageName.split("\\.");
        if (packageItemArr.length > 3) {
            projectBasePackage = String.join(".", packageItemArr[0], packageItemArr[1], packageItemArr[2]);
        } else {
            projectBasePackage = currPackageName;
        }
        log.info("Base package to scan api is -> {}", projectBasePackage);
        return projectBasePackage;
    }
    
    /**
     * 显示自定义枚举类型注释
     * <p>
     * <br/> 参考<a
     * href="https://blog.gelu.me/2021/Knife4j-Swagger%E8%87%AA%E5%AE%9A%E4%B9%89%E6%9E%9A%E4%B8%BE%E7%B1%BB%E5%9E%8B
     * /">here</a>
     */
    @Component
    @SuppressWarnings("unchecked")
    public static class Knife4jSwaggerEnumPlugin implements ModelPropertyBuilderPlugin, ParameterBuilderPlugin {
        
        private static final Field parameterDescriptionField;
        
        private static final Field modelPropertyBuilderDescriptionField;
        
        
        static {
            parameterDescriptionField = ReflectionUtils.findField(RequestParameterBuilder.class, "description");
            Objects.requireNonNull(parameterDescriptionField, "parameterDescriptionField should noe be null.");
            ReflectionUtils.makeAccessible(parameterDescriptionField);
            
            modelPropertyBuilderDescriptionField = ReflectionUtils.findField(PropertySpecificationBuilder.class, "description");
            Objects.requireNonNull(modelPropertyBuilderDescriptionField, "ModelPropertyBuilder_descriptionField should noe be null.");
            ReflectionUtils.makeAccessible(modelPropertyBuilderDescriptionField);
        }
        
        /**
         * {@link ApiModelProperty}相关
         * <p>
         * 主要处理枚举对象直接作为方法参数的内部字段的情况. 如:
         * <pre>
         *  &nbsp;  @Data
         *  &nbsp;  public class LoginTokenRespVO {
         *  &nbsp;
         *  &nbsp;      @ApiModelProperty("用户类型")
         *  &nbsp;      private UserTypeEnum userType;
         *  &nbsp;  }
         * </pre>
         */
        @Override
        public void apply(ModelPropertyContext context) {
            Optional<BeanPropertyDefinition> optional = context.getBeanPropertyDefinition();
            if (!optional.isPresent()) {
                return;
            }
            // 对应被@ApiModelProperty标注的字段
            BeanPropertyDefinition beanPropertyDefinition = optional.get();
            Class<?> fieldType = beanPropertyDefinition.getField().getRawType();
            if (!Enum.class.isAssignableFrom(fieldType)) {
                return;
            }
            Class<Enum<?>> enumType = (Class<Enum<?>>) fieldType;
            Enum<?>[] enumConstants = enumType.getEnumConstants();
            PropertySpecificationBuilder modelPropertyBuilder = context.getSpecificationBuilder();
            Object oldValue = ReflectionUtils.getField(modelPropertyBuilderDescriptionField, modelPropertyBuilder);
            // 解析枚举
            List<String> enumDescList =
                    Arrays.stream(enumConstants).map(this::obtainEnumDescription).collect(Collectors.toList());
            modelPropertyBuilder.description((oldValue == null ? BaseConstant.EMPTY : oldValue) + buildHtmlUnOrderList(enumDescList))
                    .type(new ModelSpecificationBuilder().scalarModel(ScalarType.UUID).build());
        }
        
        /**
         * {@link ApiParam}、{@link io.swagger.v3.oas.annotations.Parameter}相关.
         * <p> 主要处理:枚举对象直接作为方法参数的情况. 如:
         * <pre>
         *  &nbsp;  @PostMapping("/test1")
         *  &nbsp;  @ApiOperation(value = "测试1")
         *  &nbsp;  public void test1(@ApiParam(value = "用户类型", required = true) UserTypeEnum userTypeEnum)
         * </pre>
         */
        @Override
        public void apply(ParameterContext context) {
            Class<?> type = context.resolvedMethodParameter().getParameterType().getErasedType();
            RequestParameterBuilder parameterBuilder = context.requestParameterBuilder();
            if (!Enum.class.isAssignableFrom(type)) {
                return;
            }
            Class<Enum<?>> enumType = (Class<Enum<?>>) type;
            Enum<?>[] enumConstants = enumType.getEnumConstants();
            // 解析枚举
            List<String> enumDescList = Arrays.stream(enumConstants).map(this::obtainEnumDescription).collect(Collectors.toList());
            Object oldValue = ReflectionUtils.getField(parameterDescriptionField, parameterBuilder);
            parameterBuilder.description((oldValue == null ? "" : oldValue) + buildHtmlUnOrderList(enumDescList));
        }
        
        /**
         * 此插件是否支持处理该DocumentationType
         */
        @Override
        public boolean supports(@NonNull DocumentationType documentationType) {
            return true;
        }
        
        /**
         * 获取枚举描述
         *
         * @param enumObj 枚举对象
         *
         * @return 枚举描述
         */
        private String obtainEnumDescription(@NonNull Enum<?> enumObj) {
            String name = enumObj.name();
            /*
             * 枚举说明器示例:
             *
             * public interface EnumDescriptor {
             *     // 获取枚举项说明
             *     String obtainDescription();
             * }
             */
            if (enumObj instanceof EnumDescriptor) {
                return name + ":" + ((EnumDescriptor) enumObj).obtainDescription();
            }
            return name;
        }
        
        /**
         * 构建无序列表html
         *
         * @param itemList 列表元素
         *
         * @return 无序列表html
         */
        private String buildHtmlUnOrderList(@Nullable List<String> itemList) {
            if (CollectionUtils.isEmpty(itemList)) {
                return "";
            }
            StringBuilder sb = new StringBuilder();
            sb.append("<ul>");
            for (String item : itemList) {
                sb.append("<li>");
                sb.append(item);
                sb.append("</li>");
            }
            sb.append("</ul>");
            return sb.toString();
        }
    }
}

第三步:放行相关资源 & 保证启动了knife4j

  • 放行相关资源

    对于管控了权限的微服务,应放行以下资源

    // 需要放行的资源已经定义进上面编写的Knife4jConfig中
    public static String[] RESOURCE_URLS = new String[]{"/webjars/**", "/swagger**", "/v3/api-docs", "/doc.html"};
    
  • 保证启动了knife4j

    # 启动knife4j(注:有时,如果我们不进行此配置,knife4j不会开启)
    # 其实核心是:保证com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration生效即可。如果knife4j怎么也没启动,请debug此类,确保其被加载
    knife4j.enable=true
    # 兼容swagger3
    spring.mvc.pathmatch.matching-strategy=ant_path_matcher
    

网关集成knife4j

编写配置类Knife4jGatewayConfig

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 网关knife4j配置
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2021.0.1.D
 */
@RestController
public class Knife4jGatewayConfig {
    
    private final SecurityConfiguration securityConfiguration;
    
    private final UiConfiguration uiConfiguration;
    
    private final SwaggerResourceAdapter swaggerResourceAdapter;
    
    public Knife4jGatewayConfig(@Autowired(required = false) SecurityConfiguration securityConfiguration,
                                @Autowired(required = false) UiConfiguration uiConfiguration,
                                SwaggerResourceAdapter swaggerResourceAdapter) {
        this.securityConfiguration = securityConfiguration;
        this.uiConfiguration = uiConfiguration;
        this.swaggerResourceAdapter = swaggerResourceAdapter;
    }
    
    /**
     * 安全配置
     */
    @GetMapping("/swagger-resources/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
    }
    
    /**
     * ui配置
     */
    @GetMapping("/swagger-resources/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }
    
    /**
     * 资源配置,自动路由到微服务中的各个服务的api-docs信息
     */
    @GetMapping("/swagger-resources")
    public Mono<ResponseEntity<List<SwaggerResource>>> swaggerResources() {
        return Mono.just(new ResponseEntity<>(swaggerResourceAdapter.get(), HttpStatus.OK));
    }
    
    /**
     * favicon.ico
     */
    @GetMapping("/favicon.ico")
    public Mono<ResponseEntity<?>> favicon() {
        return Mono.just(new ResponseEntity<>(null, HttpStatus.OK));
    }
    
    /**
     * swagger资源适配器
     *
     * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
     * @since 2021.0.1.D
     */
    @Slf4j
    @Primary
    @Component
    public static class SwaggerResourceAdapter implements SwaggerResourcesProvider {
        
        /**
         * spring-cloud-gateway是否开启了根据服务发现自动为服务创建router
         */
        @Value("${spring.cloud.gateway.discovery.locator.enabled:false}")
        private boolean autoCreateRouter;
        
        @Value("${spring.application.name:}")
        private String applicationName;
        
        @Resource
        private RouteLocator routeLocator;
        
        @Resource
        private GatewayProperties gatewayProperties;
        
        /**
         * 根据当前所有的微服务路由信息,创建对应的SwaggerResource
         */
        @Override
        public List<SwaggerResource> get() {
            List<SwaggerResource> finalResources;
            Set<String> routes = new LinkedHashSet<>(16);
            // 获取所有路由的id
            routeLocator.getRoutes().subscribe(route -> {
                String routeId = route.getId();
                routeId = routeId.replace("ReactiveCompositeDiscoveryClient_", "");
                routes.add(routeId);
            });
            // 没有开启自动创建路由,那么走配置文件中配置的路由
            if (!autoCreateRouter) {
                finalResources = new ArrayList<>(16);
                gatewayProperties.getRoutes().stream()
                        // 过滤出配置文件中定义的路由
                        .filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
                            route.getPredicates().stream()
                                    // 过滤出设置有Path Predicate的路由
                                    .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
                                    // 根据路径拼接成api-docs路径,生成SwaggerResource
                                    .forEach(predicateDefinition -> finalResources.add(swaggerResource(route.getId(),
                                            predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
                                                    // 如果对应的微服务设置了server.servlet.context-path,那么这里应该是{context-path}/v3/api-docs
                                                    .replace("**", "v3/api-docs"))));
                        });
            } else {
                // 如果对应的微服务设置了server.servlet.context-path,那么这里应该是/{context-path}/v3/api-docs
                finalResources = routes.stream().map(routeId -> swaggerResource(routeId, routeId + "/v3/api-docs")).collect(Collectors.toList());
            }
            List<SwaggerResource> resources = new ArrayList<>(finalResources);
            // resources过滤掉网关的SwaggerResource, 我们一般也不会在网关中编写业务controller
            if (StringUtils.isNotBlank(applicationName)) {
                resources = resources.stream().filter(x -> !applicationName.equalsIgnoreCase(x.getName())).collect(Collectors.toList());
            }
            // 排序
            resources.sort(Comparator.comparing(x -> x.getName().length()));
            return resources;
        }
        
        /**
         * 创建swagger资源
         *
         * @param name
         *            swagger资源名(注:一般对应 {路由id})
         * @param location
         *            swagger资源路径(注:一般对应 {路由id}/v3/api-docs)
         * @return  swagger资源
         */
        private SwaggerResource swaggerResource(String name, String location) {
            // 确保首字符不是/
            location = location.startsWith("/") ? location.substring(1) : location;
            log.info("name:{},location:{}", name, location);
            SwaggerResource swaggerResource = new SwaggerResource();
            swaggerResource.setName(name);
            swaggerResource.setLocation(location + "?group=" + name);
            swaggerResource.setSwaggerVersion("3.0");
            return swaggerResource;
        }
    }
}

测试验证

启动微服务后,访问{网关}/doc.html完成验证

在这里插入图片描述

相关资料

  • springboot2集成knife4j(swagger2)
  • springboot2集成knife4j(swagger3)
  • springcloud-gateway集成knife4j(swagger2)
  • 在微服务项目中引入 knife4j

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

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

相关文章

5月9号软件资讯更新合集......

Linux 嵌入式系统构建工具 Yocto 发布 4.2 版本 基于 Linux 基金会的 Yocto 项目发布了 4.2 版本。Yocto 提供模板、工具和方法&#xff0c;帮助开发者创建基于 Linux 的定制版物联网 / 嵌入式操作系统&#xff0c;而无需关心硬件体系。 4.2 中的新功能 / 增强功能 Linux 内核…

106.(cesium篇)cesium椎体旋转

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <html lang="en"> <

【简单入门】ChatGPT prompt engineering (中文版)笔记 |吴恩达ChatGPT 提示工程

目录 思维导图一、资料二、 指南环境配置两个基本原则&#xff08;最重要!!!!&#xff09;原则一&#xff1a;编写清晰、具体的指令**策略一&#xff1a;使用分隔符清晰地表示输入的不同部分**&#xff0c;**分隔符可以是&#xff1a;&#xff0c;""&#xff0c;<…

camunda的Java委托如何使用

一、camunda的Java委托有什么用途 在Camunda中&#xff0c;Java委托是一种用于在流程执行期间执行自定义逻辑的Java类。使用Java委托&#xff0c;您可以在流程执行期间通过Java代码实现各种复杂的业务逻辑。 以下是一些使用Java委托的常见用途&#xff1a; 1、计算值&#x…

算法设计 || 第3题:多边形算法+分治算法解决循环赛问题(奇偶赛)

2022北京冬奥会在新冠疫情肆虐的情况下仍然成功举办&#xff0c;体现了我们国家和组织者的集体智慧。假设有项体育比赛有N14个队伍需要和其他N-113只队伍进行循环赛, 如果偶数个队伍&#xff0c;每个队伍每天捉对赛一场&#xff0c;共进行N-113天比赛; 奇数个队伍会出现每天有…

【K8s】Pod一文详解

文章目录 一、Pod介绍1、Pod结构2、Pod的定义 二、Pod配置&#xff1a;spec.containers1、基本配置 name和image2、镜像拉取策略 imagePullpolicy3、启动命令 command4、环境变量 env5、端口设置 ports6、资源配额 resources 三、Pod的生命周期1、创建和终止2、初始化容器3、钩…

Docker 部署 Zabbix6.4

一、安装docker 1.1.离线安装docker docker网址&#xff1a;https://download.docker.com/linux/static/stable/x86_64/ [rootVM-16-15-centos ~]# mkdir docker_install [rootVM-16-15-centos ~]# cd docker_install/ [rootVM-16-15-centos docker_install]# vim docker.se…

使用proc文件系统

使用proc文件系统 文章目录 使用proc文件系统1.meminfo文件2. free命令3、创建 /proc 节点4、使用 file_operations 实现 proc 文件读写 导向内核信息5、使用 seq_file 实现 proc 文件的读取 在Linux系统中&#xff0c; “/proc”文件系统十分有用&#xff0c; 它被内核用于向用…

vue3学习四 watch

在vue3中使用watch 来监听某个数据的变化&#xff0c; 因为我们定义数据的时候有 ref 和 reactive 两种方法&#xff0c; 所以watch 也会分出不同的五种情况 当使用 watch 来监听 ref 定义的数据时 <template><div> sum: {{sum}}</div><button click&qu…

java非静态代码块和静态代码块介绍

代码块 SE.10.0…02.28 非静态普通代码块&#xff1a;定义在方法内部的代码块&#xff0c;不用任何关键字修饰&#xff0c;又名构造代码块、实例代码块 静态代码块&#xff1a;用static修饰的代码块 非静态代码块 public class Test {public static void main(String[] args…

【EKF】卡尔曼滤波的一维应用实例

前言 推导了卡尔曼滤波的原理之后&#xff0c;使用一个简单的一维应用实例来训练一下&#xff0c;加深印象。使用一个温度测量的实例来说明&#xff0c;系统的状态方程为&#xff1a; X(k) A*X(k-1) B*u(k-1) w(k-1) Z(k) H*X(k) v(k) 其中 w 为过程噪声&#xff0c;方…

方言翻译APP小程序开发具备哪些功能?

我国语言文华博大精深&#xff0c;很多地方都有着民族特色方言&#xff0c;在当地很盛行但是外地人听不懂也不会说&#xff0c;这就給沟通造成了一定的困扰。方言翻译APP软件是专门针对地方性方言开发的一款系统软件&#xff0c;提供全国各地方言翻译功能&#xff0c;一键在线就…

Android中如何使用GPS

目录 GPS简介 GPS的常用API locationProvider 使用GPS获取位置信息 室内WIFI定位 近距离警报 GPS简介 Gobal Positioning System&#xff0c;全球定位系统&#xff0c;是美国在20世纪70年代研制的一种以人造地球卫星为基础的高精度无线电导航的定位系统&#xff0c;它在全…

【手机摄影】--全集

算了&#xff0c;摄像机太贵了&#xff0c;玩不起&#xff0c;还是看看手机摄影吧。 学习链接 &#xff1a;https://www.bilibili.com/video/BV14e411T7md 1. 认识手机摄影 1.1 局限性 手机的摄像头能够满足大部分场景&#xff0c;但以下场景会受到掣肘&#xff0c;最好还是用…

面试这么简单,阿里原来这么容易就能进去…...

最近和阿里的一个老朋友闲聊&#xff0c;感触颇深&#xff0c;据他说公司近期招聘的测试工程师&#xff0c;大多数候选人都有一个“通病”&#xff1a;在工作2-3年的时候遇到瓶颈&#xff0c;而且是一道很难跨越的坎。 为什么会遇到这种情况&#xff1f;因为大部分测试工程师在…

项目管理基础(第五版)读书笔记

项目管理基础&#xff08;第五版&#xff09;读书笔记 章节概要前言第一章&#xff1a;项目管理概述 日期&#xff1a;2023年3月23日 章节概要 前言 项目管理协会 Project Management Institute。简称PMI。项目管理知识体系指南 Project Management Body Of Knowledge。简称P…

4.QT应用程序主窗口

本章代码见文末链接 主窗口框架 新建Qt Wisgets项目mymainwindow&#xff0c;类名默认MainWindow&#xff0c;基类默认QMainWindow 更改文字如图&#xff0c;如果中文无法直接输入&#xff0c;可以试试复制粘贴 “动作编辑器”中&#xff08;默认在右下角&#xff09;&…

关于IRIG-B码对时的理解和分析

一、IRIG-B是什么&#xff1f; IRIG-B&#xff08;简称B码&#xff09;是一种应用于靶场的串行时间交换码。由美国靶场司令部委员会下属“靶场仪器组”提出的一种时间信息编码标准&#xff08;IRIG是英文InterRange Instrumentation Group的缩写。它是美国靶场司令委员会的下属…

一百零八、Kettle采集Kafka数据到HDFS(踩坑,亲测有效)

Kafka到HDFS&#xff0c;除了用Kafka API和flume之外&#xff0c;还可以用kettle&#xff0c;最大优点是不用写代码&#xff01; 版本&#xff1a;Kettle版本&#xff1a;8.2、Hadoop版本&#xff1a;3.1.3 前提&#xff1a; 详情请看鄙人的一百零一、Kettle8.2.0连接Hive…

pip安装配置清华镜像源scrapy框架,并进行框架的案例演示

文章目录 运行cmd终端后直接输入安装成功scrapy框架安装成功 为什么使用此命令安装scrapy框架成功后创建scrapy项目建议在pycharm终端创建项目打开项目 用scrapy框架实现案例——从新浪网爬取热点并把数据输入到excel表中编辑setting.py文件创建脚本、写入脚本在终端运行脚本文…