0038【Gradle】听说这比maven编译更高效率呢

gradle4.6版本, gradledemo01

2023-02-26,

一 项目建立

1.1 idea配置gradle

setting→Build,Execution,Deployment→Build Tools→Gradle

gradle user home :下载文件的路径
user gradle from :选择本地的gradle路径
gradle jvm:选择本地jdk

二 文件详解

创建gradle项目后,有四个默认配置文件, 分别是: build.gradle、settings.gradle、gradlew、gradlew.bat

2.1 build.gradle

gradle项目的核心配置文件, 相当于maven项目的pom.xml文件.

2.1.1 默认配置

plugins {
    id 'java'
}

group 'com.tiannan.gradledemo'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}

test {
    useJUnitPlatform()
}

2.1.2 详解build.gradle

/*
plugins {
    id 'java'
}
 */
// 闭包,括号可以省略
plugins({
    id 'java'  //引入java插件
    //id 'war' //web项目的时候用到,打war包
})

//group 'com.tiannan.gradledemo'
group('com.tiannan.gradledemo')

// 相当于pom.xml中的 <groupId>com.tiannan.gradledemo</groupId>
//version '1.0-SNAPSHOT'
version('1.0-SNAPSHOT')

// 相当于pom.xml中的 <version>1.0-SNAPSHOT</version>

repositories {
    //gradle没有自己的库,而是使用maven的仓库
    //本地库
    mavenLocal()
    //中央仓库
    mavenCentral()
}

//依赖,相当于pom.xml中的<dependencies></dependencies>
dependencies {
    //group:artifactId:version
    //testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testImplementation('org.junit.jupiter:junit-jupiter-api:5.6.0')
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}

test {
    useJUnitPlatform()
}

2.2 settings.gradle

rootProject.name = 'gradledemo01'
// 相当于pom.xml中的 <artifactId>gradledemo01</artifactId>

2.3 测试

点击build编译jar包后, 执行测试主类

gradledemo01> java -classpath build/libs/gradledemo01-1.0-SNAPSHOT.jar com.tiannan.gradledemo.App

三 讲解

3.7 Gradle构建脚本的介绍

3.7.1 Project属性

构建脚步概要:

   Gradle构建脚步中最重要的两个概念是`project``task`, 任何一个Gradle构建都由一个或者多个project组成, 每个project包括许多的构建部分, 可以是一个jar包, 也可以是一个web应用, 也可以是多个jar的整合, 可以部署应用和搭建环境.

   每个project由一个或多个task组成, 每个task表示在构建执行过程中的一个原子操作. 如编译、打包、生成javadoc、发布到仓库等操作.

Project对象介绍:

   一个project代表一个正在构建等组件(jar/war文件), 当构建开始时, Gradle会基于build.gradle实例化一个org.gradle.api.Project对象, 并通过project变量来隐式调用其成员, 那Project的成员有哪些呢?

Project属性:

名字类型默认值
projectProjectproject实例
groupObject项目分组未指定
nameString项目目录名
versionObject项目版本未指定
pathString项目绝对路径
description
projectDir
buildDir
ant

Project中常用的属性有project(隐式使用), group、name、version

Project其他常用配置:

1. plugins, apply plugin用来引入插件使用
2. dependencies依赖配置
3. repositories仓库配置
4. task任务书写
5. ext、gradle.properties Project中属性的其他配置方式

结论: 所有的配置都会被封装到Project对象中.

3.7.2 task任务

task任务介绍:

每个任务在构建执行过程中会被封装成org.gradle.api.Task对象, 主要包括任务的动作和任务依赖. 任务动作定义了一个原子操作. 可以定义依赖其他任务、动作顺序和执行条件. 那任务有哪些相关操作呢??

任务主要操作动作:

dependsOn : 依赖相关操作
doFirst : 任务执行之前执行的方法
doLast、<< : 任务执行之后执行的方法

定义任务:

task t1{
    println 'hello t1'           //配置project时执行, 其他时候不执行, 就算依赖也不执行
}

task t2(dependsOn : 't1'){
    //t2执行前的操作
    doFirst {
        println 't2 do first'    //在调用任务或者依赖执行时调用
    }

    println 'hello t2'           //配置project时执行, 其他时候不执行, 就算依赖也不执行

    //t2执行后的操作
    doLast {
        println 't2 do last'     //在调用任务或者依赖执行时调用
    }
}

执行t1:

> Configure project :
hello t1
hello t2

> Task :t1 UP-TO-DATE

执行t2:

> Configure project :
hello t1
hello t2

> Task :t1 UP-TO-DATE

> Task :t2
t2 do first
t2 do last

结论:

直接定义在任务下的代码会在配置project时执行, 其他时候不执行, 就算依赖也不执行. 只有在doFirst或doLast中配置的操作才会在调用任务或者依赖执行时调用. 所以以后自定义任务执行代码需要写在doFirst或doLast中, 除非先在构建Project时就执行.

3.8 自定义任务

任务是Gradle构建中的两个基本概念之一, 而任务的定义和使用有多种形式.

3.8.1 任务的定义

1.定义任务基本语法

task tName1 {
   println '直接带闭包的定义方式'
}

task tName2() {
   println '带括号的定义方式'
}
//以上代码只会在构建Project时执行, gradle build, 其他方式不执行
//如果需要在任务调用时执行动作代码, 需要将代码定义到doFirst或doLast中

2.任务的常见定义方法

task t1{
    //任务调用前执行
    doFirst {
        println 't1 doFist'
    }
}

task t2(){
    //任务调用后执行
    doLast {
        println 't2 doLast'
    }
}

// << doLast的简写,任务调用后执行(报错)
//task t3<< {
//    println 't3 doLast'
//}
//测试只需要执行gradle t1 t2 t3或者直接工具点击t1、t2、t3
//配置Project时不会执行doFirst或者doLast中的动作代码,只有调用任务才会被执行

3.8.2 任务的依赖配置dependsOn

1.定义任务时参数依赖

task t1{
    doFirst {
        println 't1 doFist'
    }
}

//方法参数设置依赖
task t2(dependsOn: t1){
    doFirst {
        println 't2 doFirst'
    }
}

2.任务内部依赖

task t1{
    doFirst {
        println 't1 doFist'
    }
}

//内部设置依赖
task t3 {
    dependsOn 't1'
    doFirst {
        println 't3 doFirst'
    }
}

3.外部添加依赖

task t1{
    doFirst {
        println 't1 doFist'
    }
}

task t4 {
    doLast {
        println 't4 doFirst'
    }
}

//外部设置依赖
t4.dependsOn t1

3.8.3 动态任务(了解,无法build)

//4.times{val->
//    task "tk${val}"<<{
//            println 'The task is task${val}'
//    }
//}

3.8.4 给任务自定义属性

ext.myProperty="The property value"

如:

task t1{
    ext.myProperty="The property value"
    doFirst {
        println 't1 doFist'
        println "t1 ${myProperty}"
    }
}

执行:

> Task :t1
t1 doFist
t1 The property value

3.9 Gradle生命周期和钩子方法

Gradle项目构建生命周期

Gradle的生命周期分三个阶段, 初始化阶段、配置阶段、执行阶段, 那这三个阶段在做什么事情呢?

3.9.1 初始化阶段

通过setting.gradle判断有哪些项目需要初始化, 加载所有需要初始化的项目的build.gradle文件并为每个项目创建project对象.

打开setting.gradle 文件,添加如下代码:

//项目构建之前的钩子方法(回调的方法)
gradle.settingsEvaluated {
    println '初始化阶段: settingsEvaluated'
}

//项目加载时候的钩子方法
gradle.projectsLoaded {
    println '初始化阶段: projectsLoaded'
}

// 这个好像是配置阶段的钩子方法
gradle.beforeProject {
    println '初始化阶段: beforeProject'
}

3.9.2 配置阶段

执行各项目下的build.gradle脚本, 完成project的配置, 并且构造Task任务依赖关系图以便在执行阶段按照依赖关系执行Task中的配置代码.

配置代码:配置阶段就需要执行的代码, 如下:

task configCode{
  println 'configCode'
}

在build.gradle中添加下面钩子方法:

task t1 {
    //配置代码
    println 't1 configuration'

    //动作代码
    doFirst {
        println 't1 execute doFirst'
    }
    doLast {
        println 't1 execute doLast'
    }
}

gradle.afterProject {
    println '配置阶段: afterProject'
}
project.beforeEvaluate {
    println '配置阶段: beforeEvaluate'
}

gradle.projectsEvaluated {
    println '配置阶段: projectsEvaluated'
}

project.afterEvaluate {
    println '配置阶段: afterEvaluate'
}

//任务图
gradle.taskGraph.whenReady {
    println '配置阶段: taskGraph.whenReady'
}

3.9.3 执行阶段

通过配置阶段的Task图, 按顺序执行需要执行的任务中的动作代码, 就是执行任务中写在doFirst或doLast中的代码.

动作代码, 任务调用才会执行的代码:

task executeCode << {
   println 'executeCode'
}

在build.gradle中添加下面钩子方法:

gradle.taskGraph.beforeTask{
    println '执行阶段: taskGraph.beforeTask'
}

gradle.taskGraph.afterTask {
    println "执行阶段: taskGraph.afterTask"
}

gradle.buildFinished {
    println '执行阶段: buildFinished'
}

3.9.4 钩子方法

预先写的方法, 回掉使用

3.10 Gradle依赖管理关键点和依赖阶段配置

几乎所有基于jvm的软件项目都需要依赖外部的类库来重用现有的功能代码. 自动化依赖管理可以明确依赖的版本, 能解决传递性依赖带来的版本冲突问题.

依赖管理关键点:

1.工件坐标(jar包标志)
group: 指明jar包所在的分组
name: 指明jar包的名称
version: 指明jar包的版本

在dependencies中指明依赖的jar包.

2.仓库(jar包的存放位置)
公共仓库(中央仓库): mavenCentral/jcenter
    Gradle没有自己的中央仓库, 可配置使用maven的中央仓库: mavenCentral/jcenter
私有仓库: mavenLocal
    配置从本地maven仓库中获取依赖的jar包, 不远程加载jar包, 使用mavenLocal
自定义maven仓库:
    自定义仓库来源, 一般指向公司的maven私服(普遍做法)
文件仓库(用得少):

仓库使用, [注意]可配置多个仓库, 查找少按顺序来查找, 找到则返回, 没找到继续往下查找.:

repositories {
    //gradle没有自己的库,而是使用maven的仓库
    //本地库
    mavenLocal()
    //私服
    maven {
        url ''
    }
    //中央仓库
    mavenCentral()
}

依赖阶段配置:

在build.gradle中的dependencies中配置依赖, 依赖分以下四种依赖:
源码依赖:  compile、runtime
测试依赖: testCompile、testRuntime

依赖配置:

compile配置依赖的jar, 测试代码编译和运行以及源码运行一定存在.
runtime配置依赖的jar, 只有源码运行和测试运行存在 
testCompile配置依赖的jar, 测试代码的编译和运行存在
testRuntime配置依赖的jar, 只有测试代码的运行存在

以上的四种配置选用的主要判断依据是否仅是运行阶段需要依赖或是否仅是测试阶段需要依赖.

实战:

dependencies {
    testCompileOnly'junit:junit:4.12'
    compileOnly 'ch.qos.logback:logback-classic:1.2.2'
}

3.11 Gradle的版本冲突问题(不顺序)

版本冲突解决:

对比maven, 自动处理传递性依赖版本冲突, maven说按最短路径原则和优先声明原则(路径距离相同, 则按声明顺序)来处理.

Gradle的默认自动解决版本冲突的方案是选用版本最高的.

排除:

dependencies {
    compileOnly("org.hibernate:hibernate-core:3.6.3.Final"){
        //module是jar的name
        exclude group:"org.sf4j", module:"sf4j-api"
    }
}

修改默认配置策略, 对所有jar不做冲突自动解决(显示全部版本)

// dependencies同级位置, 上述exclude删除
configurations.all {
    resolutionStrategy{
        failOnVersionConflict()
    }
}

手动指定某个jar的版本

// dependencies同级位置, 上述exclude删除
configurations.all {
    resolutionStrategy{
        force 'org.slf4j:slf4j-api:1.7.24'
    }
}

3.12 Gradle的多项目构建

3.12.1 父项目settings.gradle

加入模块后, settings.gradle增加如下(另外, 父目录下的src可以删除):

include 'core'
include 'model'
include 'admin'
include 'web'

项目要求:

1.所有的项目都需要使用Java插件, web项目也需要依赖Java环境
2.web子项目需打为war包
3.统一配置公共属性, 例如: group、version
4.统一管理资源库
5.通用依赖配置, 例如logback日志功能的引入

build.gradle变化:

//所有项目的公用配置在allprojects中配置, 所有子模块的公用配置可以在subprojects中来配置, build.gradle针对项目的配置项都可以配置在allprojects/subprojects中.


3.12.2 子项目

子项目是没有settings.gradle文件的

3.12.2.1 model

build.gradle变化:

plugins {
//    id 'java'
}

//group 'com.tiannan.gradledemo'
//version '1.0-SNAPSHOT'

//repositories {
//    mavenCentral()
//}

dependencies {
//    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' //不需要测试
//    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' // 报错
}

test {
    useJUnitPlatform()
}
3.12.2.2 core

build.gradle变化:

plugins {
//    id 'java'
}

//group 'com.tiannan.gradledemo'
//version '1.0-SNAPSHOT'

//repositories {
//    mavenCentral()
//}

dependencies {
    //依赖model模块
    compile project(":model")
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
//    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' // 报错
}

test {
    useJUnitPlatform()
}
3.12.2.3 admin

build.gradle变化:

plugins {
//    id 'java'
    id 'war'
}

//group 'com.tiannan.gradledemo'
//version '1.0-SNAPSHOT'

//repositories {
//    mavenCentral()
//}

dependencies {
    //依赖model模块
    compile project(":core")
    testCompile group: 'junit', name: 'junit', version: '4.11'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
//    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' // 报错
}

test {
    useJUnitPlatform()
}
3.12.2.4 web

build.gradle变化:

plugins {
//    id 'java'
    id 'war'
}

//group 'com.tiannan.gradledemo'
//version '1.0-SNAPSHOT'

//repositories {
//    mavenCentral()
//}

dependencies {
    //依赖model模块
    compile project(":core")
    testCompile group: 'junit', name: 'junit', version: '4.11'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
//    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' // 报错
}

test {
    useJUnitPlatform()
}
3.12.2.5 root

build.gradle变化:

/*
plugins {
    id 'java'
}
 */
// 闭包,括号可以省略
//plugins({
//    id 'java'  //引入java插件
//    //id 'war' //web项目的时候用到,打war包
//})

//group 'com.tiannan.gradledemo'
//group('com.tiannan.gradledemo')
// 相当于pom.xml中的 <groupId>com.tiannan.gradledemo</groupId>

//version '1.0-SNAPSHOT'
//version('1.0-SNAPSHOT')
// 相当于pom.xml中的 <version>1.0-SNAPSHOT</version>

//配置统一的信息,包括root项目
allprojects {
//    plugins({
//        id 'java'
//    }) // 可以用 apply plugin替换
    apply plugin : 'java'
    sourceCompatibility = 1.8
    group 'com.tiannan.gradledemo'
    version '1.0-SNAPSHOT'

    repositories {
        //gradle没有自己的库,而是使用maven的仓库
        //本地库
        mavenLocal()
        //私服
        maven {
            url ''
        }
        //中央仓库
        mavenCentral()
    }
}

// 报错
//subprojects {
//    repositories {
//        //gradle没有自己的库,而是使用maven的仓库
//        //本地库
//        mavenLocal()
//        //私服
//        maven {
//            url ''
//        }
//        //中央仓库
//        mavenCentral()
//    }
//}

// 移动到allprojects内
//repositories {
//    //gradle没有自己的库,而是使用maven的仓库
//    //本地库
//    mavenLocal()
//    //私服
//    maven {
//        url ''
//    }
//    //中央仓库
//    mavenCentral()
//}

//手动指定某个jar的版本
configurations.all {
    resolutionStrategy{
        force 'org.slf4j:slf4j-api:1.7.24'
    }
}

 修改默认配置策略, 对所有jar不做冲突自动解决
//configurations.all {
//    resolutionStrategy{
//        failOnVersionConflict()
//    }
//}

//依赖,相当于pom.xml中的<dependencies></dependencies>
dependencies {
    //group:artifactId:version
    //testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testImplementation('org.junit.jupiter:junit-jupiter-api:5.6.0')
    //testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
    testCompileOnly 'junit:junit:4.12'
    compileOnly 'ch.qos.logback:logback-classic:1.2.2'
    compileOnly("org.hibernate:hibernate-core:3.6.3.Final"){
//        exclude group:"org.slf4j", module:"slf4j-api"
    }

}

test {
    useJUnitPlatform()
}

3.12.3 核心汇总

1.在root项目的build.gradle中使用allprojects/subprojects来做公共的配置
2.所有项目使用java, web项目使用war
3.属性配置文件可以抽取到gradle.properties中

3.13 Gradle项目的发布

3.13.1 要求

1.由gradle将项目打包和创建metadata文件
2.按要求发布到本地仓库或者远程仓库
这是发布的流程, 也是需要gradle需要做的事情.

3.13.2 gradle项目发布的实现步骤

1.添加maven-publish插件
2.配置发布任务
3.执行发布
  发布到远程私服
  发布到本地仓库
  发布到maven中央仓库
3.13.2.1 添加maven-publish插件

在任何项目的build.gradle文件加入

apply plugin :'maven-publish'
3.13.2.2 配置发布任务

在任何项目的build.gradle文件加入配置, 刷新后, 在Tasks下出现publishing任务

// 配置发布任务
publishing{
    publications{
        // publishProject为自定义名称, 可写多个发布任务
        publishProject(MavenPublication){
            from components.java // 发布jar包  (components是内置的)
            //from components.war // 发布war包
        }
    }
    //配置发布到哪里
    repositories {
        maven {
            // 指定要上传的maven私服仓库
            url = ""
            // 认证用户和密码,url有对应的私服后,把以下打开
//            credentials{
//                username 'root'
//                password 'admin'
//            }
        }
    }
}

在Tasks下出现publishing目录, 其下有几个操作:

generatePomFileForPublishProjectPublication : 生成pom文件
publish : 发布到repositories指定的仓库(一般为maven私服)
publishPublishProjectPublicationToMavenLocal : 执行publishProject指定操作到指定仓库
publishPublishProjectPublicationToMavenRepository : 执行publishProject中的操作并发布到指定仓库(私服)
publishToMavenLocal : 执行所有发布任务中的操作发布到本地仓库

一般在公司就是将项目发布到私服供其他项目使用, 直接操作publish, 发布到本地使用publishToMavenLocal即可.

3.13.3 打包源码

没成功???

task souresJar(type: Jar){
    from sourceSets.main.allJava
//    archiveClassifier = 'sources'
}

3.14 Gradle中加入web容器tomcat

3.14.1 gradle使用嵌入式tomcat部署web项目

在gradle中,嵌入式tomcat使用 gradle-tomcat-plugin
文档: https://github.com/bmuschko/gradle-tomcat-plugin/blob/master/README.md

build.gradle中如:

//1.创建web项目
//2.将二进制插件添加到build.gradle[注意:buildscript需要配置在所有的plugins配置之前,可用apply plugin引入插件]
buildscript {
    repositories {
        //gradlePluginPortal()
        jcenter()
    }
    dependencies {
        //classpath 'com.bmuschko:gradle-tomcat-plugin:2.7.0'
        classpath 'com.bmuschko:gradle-tomcat-plugin:2.5'
    }
}
//3.引入插件库(此插件用apply引入可以跟其他插件放一块, 如果用的是plugins来引入就只能放在buildscript之后)
apply plugin: 'com.bmuschko.tomcat'
//详细配置查看gradle-tomcat-plugin插件介绍,如果需要完全控制任务或不像使用预配置的任务则使用TomcatBasePlugin插件,即:apply plugin: 'com.bmuschko.tomcat-base'

//4.tomcat版本配置(刷新build.gradle后,Tasks下多出了web application任务)
dependencies{
    def tomcatVersion = '8.0.42'
    tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
            "org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}",
            "org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
}

//5.启动tomcat,运行项目(双击tomcatRun即可启动tomcat)
  //tomcatJasper : 调用jsp转换器,将jsp转为类并编译为class文件.
  //tomcatRun : 启动tomcat服务器部署项目.
  //tomcatRunWar : 启动tomcat服务器部署war.
  //tomcatStop : 关闭tomcat服务器.
  //运行后,默认访问地址 http://localhost:8080/admin

//6.tomcat常用配置修改(build.gradle一级任何位置定义tomcat属性)
//6.1单一配置
tomcat.httpPort=80
tomcat.contextPath='/'
//6.2统一配置
tomcat{
  httpPort=80
  contextPath='/'
}
//访问地址变为: http://localhost/

//7.编译器加入Servlet依赖
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'  //providedCompile只在编译时依赖,运行环境下不需要

//8.编写servlet类,并启动tomcat
//访问地址, 

3.14.2 HelloServlet

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import java.io.IOException;

/**
 * @author chyzhong
 * @datetime 2023/3/11 18:50
 */
@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("hello servlet");
    }
}

// 访问地址, http://localhost/HelloServlet

3.15 引入gretty插件和热部署属性介绍

gretty插件支持热部署、https、转发、调试、自动化运行环境等诸多特性, 支持jetty、tomcat等多种servlet容器.

3.15.1 加入gretty插件

gretty安装简单, 不过需要下载的依赖比较多.

//1.apply安装方式一
apply from:'https://raw.github.com/gretty-gradle-plugin/gretty/master/pluginScripts/gretty.plugin'
//2.plugins安装方式二
plugins{
  id 'java'
  id 'war'
  id "org.akhikhl.gretty" version "2.0.0"
}

以上两种方式选一种来安装就ok, 如果使用的gradle版本比较低, 可以使用以下方式来配置.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.akhikhl.gretty:gretty:+'
    }
}

3.15.2 启动使用gretty容器

刷新gradle后, Tasks下出现gretty任务.

默认使用jetty9容器, 在idea工具中可看到如下操作.

3.15.2.1 run系列
//无需执行停止线程操作
//1.编译项目 ->2.启动 web-app
gradle appRun/appRunDebug : 不依赖war, 点击停止按钮停止服务器
gradle appRunWar/appRunWarDebug : 依赖war, 点击停止按钮停止服务器
3.15.2.2 start系列
//需要执行停止线程操作
//1.编译项目 ->2.使用新线程开启服务, 等待http请求
gradle appStart/appStartDebug : 不依赖war, 用gradle appStop停止
gradle appRestart : 不依赖war重启
gradle appStartWar/appStartWarDebug : 依赖war, 用gradle appStop停止

//详情: gretty task: https://gretty-gradle-plugin.github.io/gretty-doc/Gretty-tasks.html

3.16 Gretty debug调试和导出可执行项目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值