DevOps系列文章 之 Java使用jgit管理git仓库

Java工具类-JGit基本功能实现 一、简介 最近在写一个web前端代码覆盖率的工程(react),主要是负责后台的管理功能。目前的一个需求就是获取增量代码的覆盖率;需要去获取代码提交前后的代码差异,将代码差异信息提供给前端同事进行处理计算此覆盖率; 通过使用GitLab的API和Java提供的JGit后发现,java提供的JGit效果更好,差异的信息也准确; 此工具类基于Jacoc二开代码,修改部分代码,实现自己需要的功能的一个工具类。也仅仅只获取了java工程的代码差异,react工程的代码差异与此原理差不多,删除掉几个不重要的文件即可。 阅读详情

最近设计基于gitops新的CICD方案,需要通过java读写git仓库,这里简单记录下。

在jgit中,存在最核心的三个组件:Git类,Repository类。Git类中包含了push commit之类的常见git操作,而Repository则实现了仓库的初始化和基本的管理功能。
Git类的实例都会持有一个Repository实例。

Repository类的初始化

针对一个git仓库,我们一般会有三种方式获得
1.新建一个空仓库

 Git git = Git.init().setDirectory(localPath).call()

2.加载一个已存在的仓库

Repository repository = builder.setGitDir(repoDir)
                .readEnvironment() // scan environment GIT_* variables
                .findGitDir() // scan up the file system tree
                .build()

3.从远程仓库下载

Git result = Git.cloneRepository()
                .setURI(REMOTE_URL)
                .setDirectory(localPath)
                .call()

JGit是一款pure java的软件包,可以读写git仓库,下面介绍基本使用。

引入jgit#

maven引入:

        <!-- jgit -->
        <dependency>
            <groupId>org.eclipse.jgit</groupId>
            <artifactId>org.eclipse.jgit</artifactId>
            <version>5.8.0.202006091008-r</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

jgit 有一个Git类,可以用来执行常规的git操作

凭证管理#

通过CredentialsProvider管理凭证,常用的是UsernamePasswordCredentialsProvider

通过下面代码初始化:

public static CredentialsProvider createCredential(String userName, String password) {
        return new UsernamePasswordCredentialsProvider(userName, password);
}

clone远程仓库#

git 命令:

git clone {repoUrl}

通过Git.cloneRepository 来clone远程仓库,如果需要凭证,则需要指定credentialsProvider

 public static Git fromCloneRepository(String repoUrl, String cloneDir, CredentialsProvider provider) throws GitAPIException {
        Git git = Git.cloneRepository()
            .setCredentialsProvider(provider)
            .setURI(repoUrl)
            .setDirectory(new File(cloneDir)).call();
        return git;
}

commit#

git 命令:

git commit -a -m '{msg}'

commit比较简单,对应commit方法, 注意需要先add

public static void commit(Git git, String message, CredentialsProvider provider) throws GitAPIException {
        git.add().addFilepattern(".").call();
        git.commit()
            .setMessage(message)
            .call();
}

push#

git 命令:

git push origin branchName

push直接调用push即可, 需要指定credentialsProvider

   public static void push(Git git, CredentialsProvider provider) throws GitAPIException, IOException {
        push(git,null,provider);
    }

    public static void push(Git git, String branch, CredentialsProvider provider) throws GitAPIException, IOException {
        if (branch == null) {
            branch = git.getRepository().getBranch();
        }
        git.push()
            .setCredentialsProvider(provider)
            .setRemote("origin").setRefSpecs(new RefSpec(branch)).call();
    }

读取已有仓库#

如果git已经clone了,想直接读取,怎么办?

 public static Repository getRepositoryFromDir(String dir) throws IOException {
        return new FileRepositoryBuilder()
            .setGitDir(Paths.get(dir, ".git").toFile())
            .build();
 }

读取仓库日志#

可以通过RevWalk读取仓库日志。

  • revWalk.parseCommit 可读取一条commit
  • 遍历revWalk,可读取所有日志
 public static List<String> getLogs(Repository repository) throws IOException {
        return getLogsSinceCommit(repository, null, null);
    }

    public static List<String> getLogsSinceCommit(Repository repository, String commit) throws IOException {
        return getLogsSinceCommit(repository, null, commit);
    }

    public static List<String> getLogsSinceCommit(Repository repository, String branch, String commit) throws IOException {
        if (branch == null) {
            branch = repository.getBranch();
        }
        Ref head = repository.findRef("refs/heads/" + branch);
        List<String> commits = new ArrayList<>();
        if (head != null) {
            try (RevWalk revWalk = new RevWalk(repository)) {
                revWalk.markStart(revWalk.parseCommit(head.getObjectId()));
                for (RevCommit revCommit : revWalk) {
                    if (revCommit.getId().getName().equals(commit)) {
                        break;
                    }
                    commits.add(revCommit.getFullMessage());
                    System.out.println("\nCommit-Message: " + revCommit.getFullMessage());
                }
                revWalk.dispose();
            }
        }

        return commits;
    }

测试#

我们来先clone仓库,然后修改,最后push

package com.devops.autocicdstore.git;

import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.transport.CredentialsProvider;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;

import static com.devops.autocicdstore.git.GetRepositoryMgr.*;

/**
 * @Author 虎哥
 * @Description //TODO
 * |要带着问题去学习,多猜想多验证|
 **/
public class TestMain {
    public static void main(String[] args) throws GitAPIException, IOException {
        String yaml = "dependencies:\n" +
                "- name: springboot-rest-demo\n" +
                "  version: 0.0.5\n" +
                "  repository: http://hub.hubHOST.com/chartrepo/ainote\n" +
                "  alias: demo\n" +
                "- name: exposecontroller\n" +
                "  version: 2.3.82\n" +
                "  repository: http://chartmuseum.jenkins-x.io\n" +
                "  alias: cleanup\n";
        CredentialsProvider provider = createCredential("USR_NAME", "PASSWORD");

        String cloneDir = "/tmp/test";

        Git git = fromCloneRepository("https://gitee.com/JavaBigDataStudy/auto-cicd-store.git", cloneDir, provider);

        // 修改文件

        FileUtils.writeStringToFile(Paths.get(cloneDir, "env", "requirements.yaml").toFile(), yaml, "utf-8");

        // 提交
        commit(git, "deploy(app): deploy  springboot-rest-demo:0.0.5 to env test", provider);
        // push 到远程仓库
        push(git, "master", provider);

        git.clean().call();
        git.close();

        FileUtils.deleteDirectory(new File(cloneDir));
    }
}

package com.devops.autocicdstore.git;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author 虎哥
 * @Description //TODO
 * |要带着问题去学习,多猜想多验证|
 **/
public class GetRepositoryMgr {
    public static CredentialsProvider createCredential(String userName, String password) {
        return new UsernamePasswordCredentialsProvider(userName, password);
    }

    public static void main(String[] args) throws GitAPIException, IOException {
//        String localPath = "D:\\SoftwareDev\\Code\\auto-cicd-store\\git-res";
//        String remotePath = "https://gitee.com/JavaBigDataStudy/auto-cicd-store.git";
//        CredentialsProvider provider = createCredential("XXXX","XXX");
//        //1
//
//        Git git = fromCloneRepository(remotePath,localPath,provider);
//        //2
//        commit(git,"test",provider);
//        //3
//        push(git,"dev",provider);
//        //4
//      //  getLogs()l

        Repository repository = getRepositoryFromDir("D:\\\\SoftwareDev\\\\Code\\\\auto-cicd-store\\\\git-res");
        List<String> logs = getLogs(repository);
        System.out.println(logs);

    }

    public static Git fromCloneRepository(String repoUrl, String cloneDir, CredentialsProvider provider) throws GitAPIException {
        Git git = Git.cloneRepository()
                .setCredentialsProvider(provider)
                .setURI(repoUrl)
                .setDirectory(new File(cloneDir)).call();
        return git;
    }
    public static Repository getRepositoryFromDir(String dir) throws IOException {
        return new FileRepositoryBuilder()
                .setGitDir(Paths.get(dir, ".git").toFile())
                .build();
    }
    public static void commit(Git git, String message, CredentialsProvider provider) throws GitAPIException {
        git.add().addFilepattern(".").call();
        git.commit()
                .setMessage(message)
                .call();
    }

    public static void push(Git git, CredentialsProvider provider) throws GitAPIException, IOException {
        push(git,null,provider);
    }

    public static void push(Git git, String branch, CredentialsProvider provider) throws GitAPIException, IOException {
        if (branch == null) {
            branch = git.getRepository().getBranch();
        }
        git.push()
                .setCredentialsProvider(provider)
                .setRemote("origin").setRefSpecs(new RefSpec(branch)).call();
    }

    public static List<String> getLogs(Repository repository) throws IOException {
        return getLogsSinceCommit(repository, null, null);
    }

    public static List<String> getLogsSinceCommit(Repository repository, String commit) throws IOException {
        return getLogsSinceCommit(repository, null, commit);
    }

    public static List<String> getLogsSinceCommit(Repository repository, String branch, String commit) throws IOException {
        if (branch == null) {
            branch = repository.getBranch();
        }
        Ref head = repository.findRef("refs/heads/" + branch);
        List<String> commits = new ArrayList<>();
        if (head != null) {
            try (RevWalk revWalk = new RevWalk(repository)) {
                revWalk.markStart(revWalk.parseCommit(head.getObjectId()));
                for (RevCommit revCommit : revWalk) {
                    if (revCommit.getId().getName().equals(commit)) {
                        break;
                    }
                    commits.add(revCommit.getFullMessage());
                    System.out.println("\nCommit-Message: " + revCommit.getFullMessage());
                }
                revWalk.dispose();
            }
        }

        return commits;
    }

}

读取已有仓库的日志:

 Repository repository = getRepositoryFromDir("GIT_DIR");
        List<String> logs = getLogs(repository);
        System.out.println(logs);

        RevCommit head = getLastCommit(repository);
        System.out.println(head.getFullMessage());

不止于EGit:盘点那些基于JGit构建的开源神器(Gerrit、Gitiles等) 本文深入探讨了基于JGit构建的开源工具如何革新开发流程,重点介绍了Gerrit、Gitiles等项目的核心技术实现。通过分析JGit的架构优势及其在企业级应用中的性能优化策略,展示了这些工具如何提升代码审查效率和大型仓库管理能力,为开发者提供超越传统Git的创新解决方案。 阅读详情

相关推荐

不止于EGit插件:深挖JGit在自动化构建与代码审计中的隐藏用法

本文深入探讨了JGit在自动化构建与代码审计中的高级应用,超越了常见的EGit插件使用场景。通过JGit的API驱动模式,开发者可以实现智能分支拉取策略、增量构建优化以及代码审计等复杂功能,显著提升DevOps流程效率。文章还分享了企业级定制化工具开发和性能优化的实战经验。

weixin_31083901的博客 312

Jgit基础教程(Java调用git)

前言 最近公司需要做一个java调用git的工具,这里简单的介绍了一下基本操作方法以及一些衍生的信息获取,或有不对的地方请大家批评指正。转载请注明出处。 一、Jgit依赖导入 <dependency> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit</artifactId> <version>4.4.1.20160715

qq_44299928的博客 2万+

01|开发精准测试平台,需要具备哪些基础、核心编程能力和领域技术?

这篇文章系统梳理开发精准测试平台需要掌握的基础能力、核心编程能力和测试领域知识,适合想从 0 到 1 搭建精准测试平台的研发、测试开发和架构师阅读。

专注 AI 精准测试平台实战落地,分享从代码 Diff 到回归决策的完整工程闭环。主张“先建数据链路,再上 AI 智能”,让测试从“靠经验”走向“基于风险”。 951

java操作Git工具-----Jgit使用

JGit正确食用方式,可以做一些小工具

Luo God 的博客 4428

Gitblit 1.9.3部署与internal error排查实战指南

Gitblit是一个基于JGit的轻量级Git服务管理工具,它无需在服务器上安装原生Git,通过Java层即可完成仓库操作。与GitLab等重量级方案相比,Gitblit部署简单、内存占用低,非常适合内网小团队、离线环境或临时交付场景。本文从Gitblit 1.9.3的部署流程入手,介绍JDK选型、核心配置及HTTP/HTTPS设置,并重点分析常见“internal error”报错背后的真正原因,如仓库对象损坏、权限文件解析失败等,给出具体排查与修复步骤。同时,还总结了团队协作中的权限分配模型、HTTP使

weixin_28841265的博客 293

JGit工具类包 超全 可用

JGit工具类,giylabapi操作,认证 push, pull 等

DevOps系统文章之 JGit--将 Git 嵌入你的应用

如果你想在一个 Java 程序中使用 Git ,有一个功能齐全的 Git 库,那就是 JGit。JGit 是一个用 Java 写成的功能相对健全的 Git 的实现,它在 Java 社区中被广泛使用。JGit 项目由 Eclipse 维护,它的主页。依赖添加有很多种方式可以将 JGit 依赖加入到你的项目,并依靠它去写代码。最简单的方式也许就是使用 Maven。你可以通过在你的 pom.xml 文件里的 标签中增加像下面这样的片段来完成这个整合。

Coder_Boy_的博客 1029

数字通信:信源编码、信道编码、调制、物理信道、解调、信道解码、信源解码

在通信系统中,信息的传输通常需要经过一系列的处理步骤,确保数据可以在信道中高效且可靠地传输。以下是信源编码、信道编码、调制、物理信道、解调、信道解码、信源解码的具体含义及其常见技术的解释。

weixin_44012667的博客 4804

【Git】使用程序控制Git(20230515更新)

简而言之,jgit仅支持basic and digest authentication,对于tfs(现Azure devops services)使用的ntlm authentication是不兼容的。

Andy_corsaracing的博客 947

【校招】SHL 的 General Ability (GA, 通用能力测试)

SHL 的 General Ability (GA, 通用能力测试) 文章目录SHL 的 General Ability (GA, 通用能力测试)1、归纳推理题1.1 圈圈线条推理1.2 图形颜色位置推理1.3 箭头指向推理2. 数字推理题攻略3. 语言推理题 1、归纳推理题   归纳推理题我把它总结为三种类型:圈圈线条推理;图形颜色位置推理;箭头指向推理。 1.1 圈圈线条推理   你别看这个题目看起来很复杂,其实很简单,而且这个题型的题目背后原理都一样!我把我的 tips 偷偷分享给你们嘿嘿~先从 A

Sunnyside_ 2万+

【AI编程】用AI开发STM32软件(1):环境配置+AI编写LED闪烁

现在AI编程这么火,不拿来开发嵌软也太可惜了。笔者最近刚好有个2学分的实践课,要自己做项目,这不,机会就来了。笔者特意记录下来其过程,以供大家学习和参考。使用:VSCode+Copilot+EIDE+STM32CubeMX,单片机用最经典的stm32f103c8t6+STLink。

qq_23220445的博客 4570

OneDev一体化DevOps平台:企业级自托管Git服务与CI/CD解决方案的技术架构深度解析

在当今快速迭代的软件开发环境中,企业面临的核心挑战是如何在保障代码质量的同时,实现高效的团队协作和自动化交付流程。传统的DevOps工具链通常需要集成多个独立的系统,导致数据孤岛、配置复杂和维护成本高昂。OneDev作为一款集Git服务器、CI/CD流水线、看板管理和包仓库于一体的自托管平台,通过一体化架构解决了这些痛点,为技术团队提供了从代码管理到部署运维的完整解决方案。 ## 行业痛点与On

gitblog_00947的博客 149

ANSYS命令流黑箱揭秘:流体渗透压力载荷的七种收敛优化策略

本文深入解析ANSYS Workbench中流体渗透压力载荷的七种收敛优化策略,重点探讨接触探测点迭代逻辑、PPCN阈值动态调整及FPAT参数优化等关键技术。通过命令流示例和实战技巧,帮助工程师有效解决流体与结构相互作用仿真中的收敛难题,提升计算效率和精度。

weixin_29274337的博客 523

Java应用自动化漏洞修复实战:基于Trivy与Maven的Coze-Loop系统构建

在DevSecOps实践中,软件供应链安全与自动化响应是保障应用安全的核心环节。其基本原理是通过持续集成与持续部署(CI/CD)流程,将安全测试左移,实现漏洞的早期发现与快速修复。这项技术的核心价值在于显著缩短漏洞暴露窗口,降低安全风险,并提升开发运维效率。典型的应用场景包括拥有大量微服务、迭代频繁的互联网企业,以及对合规性要求极高的金融、政务系统。本文聚焦于**Java应用**的自动化漏洞修复,详细阐述了如何利用**Trivy**等漏洞扫描工具,结合Maven项目结构解析,构建一个名为Coze-Loop的

chutisun0039的博客 411

阿里云大模型 ACP 认证教材内容突变?这些改动藏着通关密码,不看亏大!

【摘要】核心章节大换血,安全合规融入实战场景,大模型微调用数学深度解析,部署章节带来阿里云产品对比实战攻略,甚至整章内容被替换成藏满学习彩蛋的行业趋势指南。这些改动不仅重塑考试重点,更暗藏官方命题风向——安全合规考场景方案,微调原理考几何解析,部署设计考分析框架。无论你是数学小白还是部署新手,这篇攻略都将带你吃透变动逻辑,附赠急救包和考点清单,帮你抓住这些“隐形加分项”,速来解锁通关秘籍!

davidkorhenn的博客 1755

Google Earth Engine:LandTrendr (Landsat Collection 2)全更新使用(代码调用+UI交互)

本文介绍了使用Landsat Collection 2进行连续变化监测的分析方法,包含代码实现和可视化界面两部分。代码部分通过Google Earth Engine平台运行LandTrendr算法,设置参数包括时间范围、光谱指数、变化检测阈值等,最终生成变化幅度和检测年份的可视化图层,并将结果导出至Google Drive。可视化界面部分展示了基于LandTrendr的土地覆盖变化结果,结合Landsat影像进行分析。整个方法实现了从数据处理到结果展示的完整流程,适用于森林覆盖等环境变化监测。

此星光明博客 210

**Designer** —— 一个基于 Web 的图形化 BPMN 2.0 流程建模与仿真工具

引擎**,支持缩放/对齐/网格/自动布局;属性面板集成 **表达式语言(FEEL/SpEL)语法高亮与上下文提示**,支持数据对象自动推导与类型绑定 | | **仿真能力** | 原生集成 BPSIM 1.0 仿真(通过独立 Simulation Engine),支持图形化配置仿真参数(如到达率、服务时间分布),可导出 `.bpsim` 文件 | **仿真功能已移除**:KIE 7+ 官方不再维护 BPSIM 支持;流程性能分析转向运行时监控(Prometheus + Grafana)、日志追踪(Jaege

[Blog][Domain] programb.blog.youkuaiyun.com 400

优快云独家 | 全网首发 | 《目标检测蓝皮书》目录

10篇文章从目标检测小白变大神

Coding... 3万+

企业AI编程落地指南:TRAE、Copilot与Amazon Q实战选型与治理

企业AI编程已从代码补全工具演进为嵌入DevOps全流程的工程能力,其核心在于对结构化工程语义的理解与受控知识注入。不同于个人开发者依赖通用模式,企业场景要求AI具备上下文感知、安全合规、可审计可计量等刚性能力。TRAE的workspace-aware技能机制、GitHub Copilot Enterprise的Private Code Graph、Amazon Q Developer的云原生RAG集成,分别代表了本地可控、生态协同、云原生耦合三种主流技术路径。本文聚焦真实产线中的上下文供给、RBAC权限管

cuanwei9356的博客 441

把 ABAP ALV 表头做成两层分组:用 TOP-OF-PAGE 手工绘制双行列标题

摘要:本文介绍了在SAP ALV List中实现双层表头的方法。通过关闭系统默认列标题(no_colhead='X'),在TOP_OF_PAGE事件中手动绘制表头,使用sy-uline和sy-vline绘制线条实现分组效果。关键点包括:1)关闭系统表头;2)在事件处理中分两行输出分组标题和字段名;3)利用系统自动拼接线条的特性绘制表格边框。文中还指出了常见问题如线条自动粘连、颜色不生效等,并提供了完整示例代码和详细解释。这种方法适用于需要对字段进行业务分组的报表场景。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 258

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

EviACT框架:基于证据驱动的LLM智能体程序自动修复实践

程序自动修复(APR)是软件工程中提升代码质量与开发效率的关键技术,其核心原理在于通过自动化手段定位并修正代码缺陷。传统APR方法依赖预定义规则或模式库,难以应对复杂多变的缺陷场景。随着大语言模型(LLM)的兴起,基于LLM的APR展现出强大的代码生成能力,但普遍面临“黑盒”决策、幻觉问题及可解释性不足的挑战。EviACT框架通过引入“证据驱动”的智能体架构,将修复过程升级为“收集证据-评估证据-基于证据决策”的理性工作流,有效提升了修复方案的可信度与可验证性。该框架集成了静态分析、动态测试、代码上下文提取

weixin_34387468的博客 345

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

【Gitea】Java 使用 JGit 创建 Git 代码仓库

使用 JGit 创建完成推送创建

小子宝丁的博客 1715

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

jgit_JGit入门

jgit 如果您想知道如何在JGit中执行git init , git checkout等基本的Git命令,请继续阅读。 本教程概述了最常用的git命令及其在JGit中的对应命令。 它逐步执行以下步骤:创建存储库,从远程获取内容,向历史记录添加文件或从历史记录中删除文件,检查历史记录,最后将更改推回原始存储库。 JGit提供了一个类似于Git高级命令的API。 代替 git comm...

danpu0978的博客 2168

JGit介绍及使用

简介 JGit是一个可以用java来对git进行操作的插件,可以通过代码连接git远程仓库,在本地建立版本库,进行拉取、提交、推送等操作。 依赖 <dependency> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit</artifactId> <version>5.9.0.202

y368769的博客 3969

Jgit的使用

当需要对系统中某些页面管理的文件做版本管理时,使用git作为其基本组件。此时需要在页面上做一些按钮,操作时会执行git指令。使用java作为开发语言时,需要引用jgit依赖例如

lmchhh的博客 835

【JGit 】简述及学习资料整理

JGit是一个用于Java编程语言的开源Git实现。它提供了一组Java库和API,使开发人员可以在他们的Java应用程序中使用Git功能。JGit是由Eclipse基金会开发和维护的一个项目,它是Eclipse IDE的一部分,也是许多其他Java开发工具和应用程序的基础。

小子宝丁的博客 2299
上一篇: DevOps系统文章之 JGit--将 Git 嵌入你的应用
下一篇: DevOps系列文章 之 SnakeYAML解析与序列化YAML
Coder_Boy_
博客等级 码龄10年 2804粉丝 819原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coder_Boy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值