SpringBoot+Cloud+oauth2.0+JWT集成swagger及swagger-bootstrap-ui详细步骤

swagger增强knife4j在gateway中的使用入门 knife4j在gateway中的使用入门 文章目录knife4j在gateway中的使用入门1. 简介1.1 pom2. gateway集成2.1 gateway 网关模块2.2 微服务模块3.界面 1. 简介 knife4j 是对swagger的增强。 1.1 pom <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-bo 阅读详情

实现效果图

先看是否效果图是否符合你的预期符合预期你在接着往下看
在这里插入图片描述

引入Maven依赖

 <!-- swagger文档API 关于swagger-ui.html的依赖我去掉了-->
 		//这个是优化界面的类似插件会让doc.html变得更好看
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>
        //swagger2的依赖 
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        //swagger-ui的界面 这个访问doc.html
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>
        

swagger的配置文件属性



import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import springfox.documentation.builders.ApiInfoBuilder;
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.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
/**
 * @author hanfeng
 * @date 2021/8/24
 */
@Configuration
@EnableSwagger2
@EnableAutoConfiguration
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
@EnableSwaggerBootstrapUI
public class SwaggerAutoConfiguration
{
    /**
     * 默认的排除路径,排除Spring Boot默认的错误处理路径和端点
     */
    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");

    private static final String BASE_PATH = "/**";

    @Bean
    @ConditionalOnMissingBean
    public SwaggerProperties swaggerProperties()
    {
        return new SwaggerProperties();
    }

    @Bean
    public Docket api(SwaggerProperties swaggerProperties)
    {
        // base-path处理
        if (swaggerProperties.getBasePath().isEmpty())
        {
            swaggerProperties.getBasePath().add(BASE_PATH);
        }
        // noinspection unchecked
        List<Predicate<String>> basePath = new ArrayList<Predicate<String>>();
        swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));

        // exclude-path处理
        if (swaggerProperties.getExcludePath().isEmpty())
        {
            swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
        }
        List<Predicate<String>> excludePath = new ArrayList<>();
        swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));

        //noinspection Guava
        return new Docket(DocumentationType.SWAGGER_2)
                .host(swaggerProperties.getHost())
                .apiInfo(apiInfo(swaggerProperties)).select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath)))
                .build()
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts())
                .pathMapping("/");
    }

    /**
     * 安全模式,这里指定token通过Authorization头请求头传递
     */
    private List<ApiKey> securitySchemes()
    {
        List<ApiKey> apiKeyList = new ArrayList<ApiKey>();
        apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
        return apiKeyList;
    }

    /**
     * 安全上下文
     */
    private List<SecurityContext> securityContexts()
    {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .forPaths(PathSelectors.regex("^(?!auth).*$"))
                        .build());
        return securityContexts;
    }

    /**
     * 默认的全局鉴权策略
     *
     * @return
     */
    private List<SecurityReference> defaultAuth()
    {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }

    private ApiInfo apiInfo(SwaggerProperties swaggerProperties)
    {
        return new ApiInfoBuilder()
                .title("swagger构建项目api文档")
                .description("https://www.baidu.com/")
                .license(swaggerProperties.getLicense())
                .licenseUrl(swaggerProperties.getLicenseUrl())
                .termsOfServiceUrl("https://www.baidu.com/")
                .contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
                .version("1.0")
                .build();
    }
}

配置文件所需要的类


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * @author hanfeng
 * @date 2021/8/24
 */
@Data
@Component
@ConfigurationProperties("swagger")
public class SwaggerProperties
{
    /**
     * 是否开启swagger
     */
    private Boolean enabled;

    /**
     * swagger会解析的包路径
     **/
    private String basePackage = "";

    /**
     * swagger会解析的url规则
     **/
    private List<String> basePath = new ArrayList<>();

    /**
     * 在basePath基础上需要排除的url规则
     **/
    private List<String> excludePath = new ArrayList<>();

    /**
     * 标题
     **/
    private String title = "";

    /**
     * 描述
     **/
    private String description = "";

    /**
     * 版本
     **/
    private String version = "";

    /**
     * 许可证
     **/
    private String license = "";

    /**
     * 许可证URL
     **/
    private String licenseUrl = "";

    /**
     * 服务条款URL
     **/
    private String termsOfServiceUrl = "";

    /**
     * host信息
     **/
    private String host = "";

    /**
     * 联系人信息
     */
    private Contact contact = new Contact();

    /**
     * 全局统一鉴权配置
     **/
    private Authorization authorization = new Authorization();
 public static class Contact
    {
        /**
         * 联系人
         **/
        private String name = "";
        /**
         * 联系人url
         **/
        private String url = "";
        /**
         * 联系人email
         **/
        private String email = "";

        public String getName()
        {
            return name;
        }

        public void setName(String name)
        {
            this.name = name;
        }

        public String getUrl()
        {
            return url;
        }

        public void setUrl(String url)
        {
            this.url = url;
        }

        public String getEmail()
        {
            return email;
        }

        public void setEmail(String email)
        {
            this.email = email;
        }
    }

    public static class Authorization
    {
        /**
         * 鉴权策略ID,需要和SecurityReferences ID保持一致
         */
        private String name = "";

        /**
         * 需要开启鉴权URL的正则
         */
        private String authRegex = "^.*$";

        /**
         * 鉴权作用域列表
         */
        private List<AuthorizationScope> authorizationScopeList = new ArrayList<>();

        private List<String> tokenUrlList = new ArrayList<>();

        public String getName()
        {
            return name;
        }

        public void setName(String name)
        {
            this.name = name;
        }

        public String getAuthRegex()
        {
            return authRegex;
        }

        public void setAuthRegex(String authRegex)
        {
            this.authRegex = authRegex;
        }

        public List<AuthorizationScope> getAuthorizationScopeList()
        {
            return authorizationScopeList;
        }

        public void setAuthorizationScopeList(List<AuthorizationScope> authorizationScopeList)
        {
            this.authorizationScopeList = authorizationScopeList;
        }

        public List<String> getTokenUrlList()
        {
            return tokenUrlList;
        }

        public void setTokenUrlList(List<String> tokenUrlList)
        {
            this.tokenUrlList = tokenUrlList;
        }
    }

    public static class AuthorizationScope
    {
        /**
         * 作用域名称
         */
        private String scope = "";

        /**
         * 作用域描述
         */
        private String description = "";

        public String getScope()
        {
            return scope;
        }

        public void setScope(String scope)
        {
            this.scope = scope;
        }

        public String getDescription()
        {
            return description;
        }

        public void setDescription(String description)
        {
            this.description = description;
        }
    }
   
}

oauth的拦截机制配置

这个如果不配置有时候可能会出现401的现象

//安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/actuator/**","/v2/api-docs", "/swagger-resources/configuration/ui",
                        "/swagger-resources","/swagger-resources/configuration/security",
                        "/swagger-ui.html","/css/**", "/js/**","/images/**", "/webjars/**",
                        "**/favicon.ico", "/index","/doc.html"
                ).permitAll()  //健康检查端点 执行放行
                .anyRequest().authenticated()
                .and()
                .formLogin();
    }

应用启动类



import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @author hanfeng
 * @date 2021/5/18
 */
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.sys.mapper")
@EnableAutoConfiguration
@EnableSwagger2
public class SysApplication   {
    public static void main(String[] args) {
        SpringApplication.run(SysApplication.class,args);
    }
}

注意事项

如果你的代码里继承了WebMvcConfigurationSupport 这个类的话则需要在配置里边添加如下代码:

/**
     * 发现如果继承了WebMvcConfigurationSupport,则在yml中配置的相关内容会失效。 需要重新指定静态资源
     *
     * @param registry
     * */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations(
                "classpath:/static/");
        registry.addResourceHandler("doc.html").addResourceLocations(
                "classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations(
                "classpath:/META-INF/resources/webjars/");
    }

如果@EnableSwaggerBootstrapUI找不到的话,那就把pom文件中的swagger-ui的依赖去掉

//这个是初始的依赖,应该是有冲突,如果注解找不到的就去掉这个保留swagger-bootstrap-ui即可
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>

bug问题解决

问题1: java.lang.NumberFormatException: For input string: “”

每次启动时报如下错误

 WARN 19699 --- [nio-1111-exec-4] i.s.m.p.AbstractSerializableParameter    : Illegal DefaultValue null for parameter type integer

java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_171]
	at java.lang.Long.parseLong(Long.java:601) ~[na:1.8.0_171]
	at java.lang.Long.valueOf(Long.java:803) ~[na:1.8.0_171]
	at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412) ~[swagger-models-1.5.20.jar:1.5.20]
	at sun.reflect.GeneratedMethodAccessor109.invoke(Unknown Source) ~[na:na]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_171]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_171]
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:687) [jackson-databind-2.9.6.jar:2.9.6]

解决办法: 排除依赖更新版本即可

<!--swagger依赖-->
 
<dependency> 
    <groupId>io.springfox</groupId> 
    <artifactId>springfox-swagger2</artifactId> 
    <version>2.9.2</version> 
  <exclusions> 
      <exclusion> 
        <groupId>io.swagger</groupId> 
        <artifactId>swagger-annotations</artifactId> 
      </exclusion> 
      <exclusion> 
        <groupId>io.swagger</groupId> 
        <artifactId>swagger-models</artifactId>
      </exclusion>
  </exclusions>
</dependency>
 
<!--解决进入swagger页面报类型转换错误,排除2.9.2中的引用,手动增加1.5.21版本-->

<dependency>
  <groupId>io.swagger</groupId>
  <artifactId>swagger-annotations</artifactId>
  <version>1.5.21</version>
</dependency>
<dependency>
  <groupId>io.swagger</groupId>
  <artifactId>swagger-models</artifactId>
  <version>1.5.21</version>
</dependency>

Knife4j系列--使用-教程-实例-配置 详细讲解 Knife4j是基于springboot构建的一个文档生成工具,它可以让开发者为我们的应用生成API文档,目的是可以更加方便的基于API文档进行测试,生成的文档还可以导出,然后给到前端开发团队,前端开发团队可以基于API接口写具体的调用。 阅读详情

相关推荐

Knife4j使用教程(一) -- 在不同版本SpringBoot,选用不同的Knife4j相关的jar包

【代码】Knife4j使用教程(一) -- 在不同版本SpringBoot,选用不同的Knife4j相关的jar包。

PUYALEI的博客 1058

springboot3使用kni4j

springboot3使用kni4j的4版本

m0_71110711的博客 321

Java 企业级 SaaS 架构 B2C 微信小程序电商项目实战训练营01-开发环境搭建与架构设计

本文完整覆盖从 JDK、Maven、Node.js、MySQL、Redis 开发环境搭建到整体架构设计两大核心板块。深入剖析三端分离架构(管理端/店铺端/移动端)的设计理念与 URL 前缀隔离策略,详解 Maven 九模块依赖关系和 wind-common 公共层复用机制,对比 Spring Boot 2.3 + MyBatis-Plus + Shiro/JWT 技术选型依据,解析前端 Vue 3 + Vite + Element Plus 与 uni-app 多端发布方案。

program哲学 112

SpringBoot整合knife4j(快速入门超详细版)

在日常开发中,写接口文档是我们必不可少的,而Knife4j就是一个接口文档工具,可以看作是Swagger的升级版,但是界面比Swagger更好看,功能更丰富早期,swagger-boostrap-ui是1.x版本,如今swagger-bootsrap-ui到2.x,同时也更改名字Knife4j,适用于单体和微服务项目。怎么样,是不是特别的方便和简单~

weixin_47316183的博客 4万+

基于粒子群算法的PID控制器优化设计

粒子群算法、PID、寻优

qq_46107700的博客 2万+

uniapp:K线图,支持H5,APP

K线,1分钟搞定。备注详细,无需理解,直接复制即可。

sunluyi的博客 4573

第3章 从0开始用若依

若依(RuoYi)框架是一款基于Spring Boot、Spring Cloud等开源框架搭建的企业级开发平台。它的主要目标是提供全面的解决方案,以简化企业级应用开发提高开发效率。模块化设计:若依框架采用模块化的设计,使得应用可以根据需求进行灵活的扩展和定制。每个模块都可以独立部署和升级,这大大提高了系统的可维护性和可扩展性。前后端分离:框架采用前后端分离的开发模式,后端专注于数据处理和API接口的提供,前端则负责页面的展示和用户交互。这种模式使得前后端职责明确,进一步提高了开发效率和代码的可维护性。

m0_73919023的博客 284

STM32G070xx将Flash页分块方式存储,固定数据块存储,实现一次擦除多次写入

本例程使用STM32G070xx芯片,Flash大小128KB,Flash每页(2048字节)写满后再擦除。操作存储区分为64块,每块32字节,即写满64次擦除一次;标定存储区分为16块,每块128字节,即写满16次擦除一次,每个存储区的块都有各自的头部标记

RaphaelCeo的博客 1543

springboot集成knife4j详细教程

springboot集成knife4j详细教程

可乐要加冰!!! 7644

保姆级教程:在SIwave 2024 R2中搞定TDR仿真,手把手教你分析USB 3.0差分线阻抗

本文提供了一份详细的SIwave 2024 R2 TDR仿真教程,重点讲解如何分析USB 3.0差分线阻抗。从项目准备、参数配置到结果分析,逐步指导工程师完成完整的仿真流程,并分享实战经验和高级技巧,帮助提升信号完整性分析能力。

weixin_33670786的博客 493

在Jetson Orin (Arm64)上从Ubuntu22.04回退至20.04的完整指南及ROS-noetic适配方案

本文提供了在Jetson Orin (Arm64)设备上将Ubuntu 22.04系统回退至20.04 LTS版本的完整操作指南。针对机器人开发中ROS-noetic对Ubuntu20.04的官方最佳支持,详细阐述了从关键备份、软件源与APT优先级配置、系统降级执行到后续问题排查的全过程,并附带了在Arm64架构上成功安装和验证ROS-noetic的适配方案,旨在为开发者构建稳定可靠的机器人开发环境。

flower的专栏 578

Swagger使用-----------kenf4j

Swagger与Knife4j自动生成接口文档的使用

m0_74196005的博客 934

利用Solidworks2020将挖掘机装配体转换为URDF模型

利用Solidworks2020将挖掘机装配体转换为URDF模型一、下载sw_urdf_exporter插件 URDF(Universal Robot Description Format)——通用机器人描述格式,它是ROS里边使用的一种机器人的描述文件,包含的内容有:连杆、关节,运动学和动力学参数、可视化模型、碰撞检测模型等。 一、下载sw_urdf_exporter插件 ROS提供了SolidWorks转URDF的插件(下载链接)—sw2urdfSetup.exe,下载后点击安装,安装完成后就能在sol

weixin_48192326的博客 6098

knife4j搭建

knife4j搭建

qq_61025812的博客 1932

Grafana图像渲染插件安装全攻略:解决依赖缺失问题(附完整命令清单)

本文详细介绍了在CentOS/RHEL环境下安装Grafana图像渲染插件`grafana-image-renderer`的全过程,包括解决常见的依赖缺失问题。通过提供完整的命令清单和疑难问题排查指南,帮助用户高效部署和优化插件性能,确保图像渲染功能稳定运行。

weixin_30399821的博客 85

Python 爬虫实战:全球咖啡期货市场数据采集与分析全攻略

通过本项目,我们实现了对全球咖啡期货市场数据的采集、处理和可视化,为市场分析和决策支持提供了数据基础。未来,可以进一步结合机器学习和深度学习技术,对咖啡期货价格进行预测和建模,提高市场分析的准确性和效率。

2201_76125261的博客 394

knife4j 配置和使用

knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍! 目前,一般都使用knife4j框架。

cyt涛的博客 7683

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

API文档生成工具-----Knife4j的详细介绍、配置及应用

Knife4j是一个基于Swagger构建的开源Java API文档工具,它为Java开发者提供了生成、展示和调试API文档的功能。它提供了一套美观且功能强大的界面,可以自动生成API文档,并支持接口分组、参数设置、请求示例、响应模型配置等高级功能。同时,Knife4j也提供了丰富的配置选项和样式定制功能,使得用户可以根据自己的需求进行个性化定制。如果需要自定义Knife4j的配置,可以自己创建一个配置类,来指定文档的访问路径、标题、版本等信息。

不知名小人物的博客 6952

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Swagger整合Oauth2

Swagger整合Oauth2

飘渺Jam的博客 5800

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Spring Boot 集成 Swagger2 与配置 OAuth2.0 授权

Spring Boot 集成 Swagger2 很简单,由于接口采用了OAuth2.0 &amp; JWT 协议做了安全验证,使用过程中也遇到了很多小的问题,多次尝试下述配置可以正常使用。 Maven &lt;!-- swagger2 --&gt; &lt;dependency&gt; &lt;groupId&gt;io.springf...

weixin_33816611的博客 3379

Spring Boot + Spring Security + JWT 集成Swagger文档问题

 现在的新项目框架都选择用Spring Boot,之前的旧项目,开发完对外的接口还需要自己写接口文档,在新项目中集成Swagger文档,这样就省去了我们写文档的时间,前端调用还可以利用它进行接口测试。  简单的一个Spring Boot框架,很简单地几步就可以将Swagger文档配置集成好,如下:  一 . 添加Swagger依赖  &amp;lt;dependency&amp;gt; &amp;lt;gr...

Dream it Possible 1万+

后端整合Swagger + Knife4j 接口文档,极致提高效率

写接口信息的文档,每条接口包括。

m0_61899713的博客 423

Springboot 整合swagger、springsecurity、jjwt、实现前后端分离架构的权限认证搭建。

Springboot 整合swagger、springsecurity、jjwt、实现前后端分离架构的权限认证搭建。 一、写本文的目的性() 1.1、网上有很多关于springsecurity、整合jjwt的相关例子,我前段时间因为个人原因,需要整合,但是看了网上的例子,要不太过于复杂,要么前后端耦合度太高,要么没有什么ruan用(但是我还是找到了某位猿猿的分享,得到了启发) 二、需要做的准备 2.1、没学过springboot、springsecurity的去学一下 这里有一份springsecurit

weixin_43277309的博客 1305

knife4j介绍详细介绍

knife4j介绍详细介绍

weixin_45428910的博客 522

springboot+swagger3+oauth2 client credentials模式授权

效果 文档页面上出现授权按钮 点击授权按钮输入客户端id,密码获取令牌 测试请求中自动携带令牌 版本 springboot 2.5.4 springdoc 1.5.10 依赖 <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-webmvc-core</artifactId> <version>1.5.10

路过君的博客 1434

Swagger接入OAuth2认证

1.pom依赖 在所要使用Swagger的项目引用Swagger依赖 <dependencies> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </depe

qq_42383970的博客 3873
上一篇: Mybatis-plus与Mybatis依赖冲突问题解决(An attempt was made to call a method that does not exist.)
下一篇: windows重装操作系统后oracle11g 数据库恢复(前提oracle安装目录还在表空间还在)利用原有oracle表空间文件还原数据库
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值