springcloud-gateway集成knife4j(swagger2)

  • springcloud-gateway集成knife4j(swagger2)

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

环境信息

  • spring-boot:2.6.3
  • spring-cloud-alibaba:2021.0.1.0
  • knife4j-openapi2-spring-boot-starter:4.0.0

准备工作

各微服务&网关引入依赖

<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter</artifactId><version>4.0.0</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.ideaaedi.springcloud.jd.commonds.constant.BaseConstant;
import com.ideaaedi.springcloud.jd.commonds.support.EnumDescriptor;
import com.ideaaedi.springcloud.jd.commonspring.config.properties.Knife4jApiInfoProperties;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
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.ModelPropertyBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
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 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 2021.0.1.D*/
@Slf4j
@Configuration
public class Knife4jConfig implements WebMvcConfigurer {/*** 文档相关资源的链接(需保证这些资源不需要鉴权即可访问)*/public static String[] RESOURCE_URLS = new String[]{"/webjars/**", "/swagger**", "/v2/api-docs", "/doc.html"};@Value("${spring.application.name:}")private String applicationName;@Beanpublic Docket docket(Knife4jApiInfoProperties knife4jApiInfoProperties) {String apiBasePackage = knife4jApiInfoProperties.getBasePackage();Docket docket =new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo(knife4jApiInfoProperties)).select().apis(RequestHandlerSelectors.basePackage(apiBasePackage)).paths(PathSelectors.any()).build();if (knife4jApiInfoProperties.isEnableSecurity()) {docket.securitySchemes(securitySchemes()).securityContexts(securityContexts());}return docket;}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}private ApiInfo apiInfo(Knife4jApiInfoProperties knife4jApiInfoProperties) {return new ApiInfoBuilder().title(knife4jApiInfoProperties.getTitle()).description(knife4jApiInfoProperties.getDescription()).termsOfServiceUrl(StringUtils.isBlank(applicationName) ? "" : "/" + applicationName).contact(new Contact(knife4jApiInfoProperties.getContactName(), knife4jApiInfoProperties.getContactUrl(), knife4jApiInfoProperties.getContactEmail())).version(knife4jApiInfoProperties.getVersion()).build();}private List<SecurityScheme> securitySchemes() {// 设置请求头信息List<SecurityScheme> result = new ArrayList<>();// 第一个参数name,自定义即可。 在配置securityContexts时,通过此name对应到apiKey即可// 第二个参数,header name自定义即可。 如:BaseConstant.JWT_TOKEN_KEY=Auth-Token,然后在代码里request.getHeader(BaseConstant.JWT_TOKEN_KEY)取值ApiKey apiKey = new ApiKey("JustryDengApiKey", 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("/sys-login/.*").or(PathSelectors.regex("/tmp.*")).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进行认证鉴权. 这里指定使用上面名为JustryDengApiKey的apiKeyresult.add(new SecurityReference("JustryDengApiKey", authorizationScopes));return result;}/*** 显示自定义枚举类型注释* <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 {// swagger3用: parameterDescriptionField = ReflectionUtils.findField(RequestParameterBuilder.class, "description");parameterDescriptionField = ReflectionUtils.findField(ParameterBuilder.class, "description");Objects.requireNonNull(parameterDescriptionField, "parameterDescriptionField should noe be null.");ReflectionUtils.makeAccessible(parameterDescriptionField);// swagger3用: modelPropertyBuilderDescriptionField = ReflectionUtils.findField(PropertySpecificationBuilder.class, "description");modelPropertyBuilderDescriptionField = ReflectionUtils.findField(ModelPropertyBuilder.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>*/@Overridepublic 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();// swagger3用: PropertySpecificationBuilder modelPropertyBuilder = context.getSpecificationBuilder();ModelPropertyBuilder modelPropertyBuilder = context.getBuilder();Object oldValue = ReflectionUtils.getField(modelPropertyBuilderDescriptionField, modelPropertyBuilder);// 解析枚举List<String> enumDescList =Arrays.stream(enumConstants).map(this::obtainEnumDescription).collect(Collectors.toList());/** swagger3用:*   modelPropertyBuilder.description((oldValue == null ? BaseConstant.EMPTY : oldValue) + buildHtmlUnOrderList(enumDescList))*                       .type(new ModelSpecificationBuilder().scalarModel(ScalarType.UUID).build());*/modelPropertyBuilder.description((oldValue == null ? BaseConstant.EMPTY : oldValue) + buildHtmlUnOrderList(enumDescList)).type(context.getResolver().resolve(Enum.class));}/*** {@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>*/@Overridepublic void apply(ParameterContext context) {Class<?> type = context.resolvedMethodParameter().getParameterType().getErasedType();// swagger3用: RequestParameterBuilder parameterBuilder = context.requestParameterBuilder();ParameterBuilder parameterBuilder = context.parameterBuilder();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 ? BaseConstant.EMPTY : oldValue) + buildHtmlUnOrderList(enumDescList));}/*** 此插件是否支持处理该DocumentationType*/@Overridepublic boolean supports(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 BaseConstant.EMPTY;}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**", "/v2/api-docs", "/doc.html"};
    
  • 保证启动了knife4j

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

网关集成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.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@Componentpublic 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;@Resourceprivate RouteLocator routeLocator;@Resourceprivate GatewayProperties gatewayProperties;/*** 根据当前所有的微服务路由信息,创建对应的SwaggerResource*/@Overridepublic List<SwaggerResource> get() {List<SwaggerResource> finalResources;Set<String> routes = new LinkedHashSet<>(16);// 获取所有路由的idrouteLocator.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}/v2/api-docs.replace("**", "v2/api-docs"))));});} else {// 如果对应的微服务设置了server.servlet.context-path,那么这里应该是/{context-path}/v2/api-docsfinalResources = routes.stream().map(routeId -> swaggerResource(routeId, routeId + "/v2/api-docs")).collect(Collectors.toList());}List<SwaggerResource> resources = new ArrayList<>(finalResources);// resources过滤掉网关的SwaggerResource, 我们一般也不会在网关中编写业务controllerif (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}/v2/api-docs)* @return  swager资源*/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);swaggerResource.setSwaggerVersion("2.0");return swaggerResource;}}
}

测试验证

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

相关资料

  • springboot2集成knife4j(swagger2)
  • springboot2集成knife4j(swagger3)
  • springcloud-gateway集成knife4j(swagger3)
  • 在微服务项目中引入 knife4j
  • 本文已被收录进《程序员成长笔记》 ,笔者JustryDeng

springcloud-gateway集成knife4j(swagger2)相关推荐

  1. springcloud gateway结合knife4j生成Api文档

    springcloud gateway结合knife4j生成Api文档 学习中笔记 一. 接口模块(mxm-system-api)引入依赖 <dependency><groupId& ...

  2. SpringCloud Gateway 集成 oauth2 实现统一认证授权_03

    文章目录 一.网关搭建 1. 引入依赖 2. 配置文件 3. 增加权限管理器 4. 自定义认证接口管理类 5. 增加网关层的安全配置 6. 搭建授权认证中心 二.搭建产品服务 2.1. 创建boot项 ...

  3. SpringCloud系列教程(五)之SpringCloud Gateway 网关聚合开发文档 swagger knife4j 和登录权限统一验证【Hoxton版】

    阅读提醒: 本文面向的是有一定springboot基础者 本次教程使用的Spring Cloud Hoxton RELEASE版本 由于knife4j比swagger更加友好,所以本文集成knife4 ...

  4. springcloud gateway 使用nacos 动态过滤器 记一次线上网关升级cpu升高的问题

    大家好,我是烤鸭: ​ 网关升级,想使用 springcloud gateway nacos 动态过滤器配置(原来是硬编码的方式),升级之后出了一些问题(cpu升高,ygc频繁),记录一下. 关于 s ...

  5. Springboot 集成Springcloud gateway的入门

    最近做项目使用到了springcloude gateway作为网关,因此在此记录下springcloud gateway的入门操作,后续再将源码解读写出来,先立个flag. 回归正题,Springcl ...

  6. SpringCloud Greenwich(五)之nacos、dubbo、Zuul和 gateway集成

    本项目是搭建基于nacos注册中心的springcloud,集成dubbo框架,使用zuul网关和gateway网关 一.框架搭建 (1)项目结构 micro-service  服务提供者 zuul- ...

  7. 从0到1手把手搭建spring cloud alibaba 微服务大型应用框架(十五) swagger篇 : gateway 集成swagger 与 knife4j实现在线api文档并嵌入到自己项目内

    背景 我们日常开发中基本都是协同开发的,当然极个别的项目整体前后端都是一个人开发的,当多人协作时,尤其是前后端人员协同开发时 必然会面临着前端需要了解后端api接口的情况,两个选择,提前设计好文档,然 ...

  8. Spring Cloud Gateway 整合 knife4j 聚合接口文档

    当系统中微服务数量越来越多时,如果任由这些服务散落在各处,那么最终管理每个项目的接口文档将是一件十分麻烦的事情,单是记住所有微服务的接口文档访问地址就是一件苦差事了.当如果能够将所有微服务项目的接口文 ...

  9. SpringBoot集成knife4j实现Swagger接口文档

    前言:如果你是后台开发,提供restful接口给前端,建议你使用Swagger3提供restful的接口文档自动生成和在线接口调试.knife4j是对Swagger进一步封装,其优化了API文档的UI ...

最新文章

  1. linux locale文件,Linux 怎样修改locale语言设置
  2. Webhooks上的一个简单方法:恐吓现在停止
  3. 【异步爬虫】【aiohttp】不需要手动指定aiohttp中的encoding编码
  4. 怎么能方便的进行数据库存储过程的版本管理?
  5. [html] 可替换元素和不可替换元素有什么不同的特点?
  6. 如何提高写前端的效率?干货,快进!
  7. Linux 系统检测工具
  8. 利用python求解度中心性
  9. 光伏农业七大问题不解决 投资者恐“先驱”变“先烈”
  10. 文科生必备计算机知识点,文科生计算机知识点调查报告.docx
  11. ASP.NET2.0 Person Web Site:将图片以二进制的从数据库读取
  12. filter动态参数 maven_多环境下Maven项目的管理
  13. 华为鸿蒙系统再公测,安卓再见!华为正式发布鸿蒙系统公测版!
  14. 如何购买银行理财子公司的理财产品?
  15. [芭比公主系列][国英双语]芭比之十二芭蕾舞公主 Barbie in The 12 Dancing Princesses
  16. 你还不会用python进行数据分析吗
  17. Leetcode:剑指 Offer 58 - II. 左旋转字符串(C++)
  18. ios学习 准备列表
  19. D型环血压计袖带的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  20. 【每日一具3】推荐一个4K、蓝光、3D高清影视下载站,影视资源丰富 发烧友必备

热门文章

  1. FastJson---高性能JSON开发包
  2. woe分析_WOE(证据权重)为何这样计算?
  3. 人教版五年级下册计算机试题答案,人教版五年级下册数学练习题及答案人教版...
  4. angularjs 微信授权登录 微信支付
  5. 中国石油大学《日常礼仪系列》在线考试
  6. 分布式部署ID全局配置之雪花算法
  7. CSS3日食示意图动画
  8. 什么是photoshop中的通道(转载) 2010-7-15 10:24
  9. 简单几步让你的 Mac 系统恢复如新
  10. JAVA中parameterized,ParameterizedType的作用