Spring Boot集成iText实现电子签章

文章目录

  • 一 电子签章
    • 1.1 什么是电子签章
    • 1.2 签名流程
    • 1.3 技术选型
  • 二 实战
    • 2.1 生成数字证书
    • 2.2 生成印章图片
    • 2.3 PDF 签名

一 电子签章

1.1 什么是电子签章

基于《中华人民共和国电子签名法》等相关法规和技术规范,具有法律效力的电子签章一定是需要使用 CA 数字证书进行对文件签名,并把 CA 数字证书存放在签名后文件中。

如果一份签名后的电子文件中无法查看到 CA 数字证书,仅存在一个公章图片,那么就不属于法律意义上的电子签名。电子签名法规定电子文件签署时一定要使用CA数字证书,并没有要求一定需要含有电子印章图片,理论上电子签章不需要到公安局进行备案。

实际上,电子签章是在电子签名技术的基础上添加了印章图像外观,沿袭了人们所习惯的传统盖章可视效果。电子签章使用电子签名技术来保障电子文件内容的防篡改性和签署者的不可否认性。因此,电子签章中,印章图片并不是唯一鉴别是否签章的条件,还要鉴别是否使用高级电子签名技术和 CA 数字证书。

CA 数字证书是在互联网中用于识别身份的一种具有权威性的电子文档。CA 数字证书相当于现实中的身份证。

现实中,如同个人需要去公安局申请办理身份证一样,CA 数字证书需要在“电子认证服务机构”(简称 CA 机构)进行申请办理。中国工业和信息化部、工信部授权 CA 机构来制作、签发数字证书,用非对称加密的方式,生成一对密码即私钥与公开密钥,并绑定了数字证书持有者的真实身份,人们可以在电子合同的缔约过程中用它来证明自己的身份和验证对方的身份。

CA 机构颁发的数字证书为公钥证书和私钥证书:公钥证书是对外公开、任何人都可以使用的,而私钥是专属于签署人所有的。当需要签署文档时,签署人使用私钥证书对电子文件(文档哈希值)进行加密,形成电子签名。 (注:文档哈希值计算时包含待签 PDF 文档内容、印章图片和印章坐标位置信息)

哈希值是指将 PDF 文件按照一定的算法(目前主流是 SHA256 算法),形成一个唯一的文件代码,类似于人类的指纹,任何一个 PDF 文件只有一个哈希值,且不同 PDF 文件的哈希值不可能相同,而相同哈希值的 PDF 文件的内容肯定相同。哈希算法是不可逆的,从哈希值无法推导出 PDF 原文内容。

经签署人的私钥证书加密之后的 PDF 原文哈希值就是电子签名,电子签名中有签署人的姓名、身份证号码、证书有效期、公钥等信息,电子签名放在 PDF 原文的签名域中,就形成了带有电子签名的 PDF 文件。

1.2 签名流程

文件电子签名过程,如下图:

其他人收到这个文件,即可使用PDF文件的签名域中存储的公钥证书对电子签名进行解密,解密出来的文件哈希值如果与原文的哈希值一致,则代表这个文件没有被篡改。

电子签名文件验签过程,如下图:

1.3 技术选型

这块主要有两大技术体系:

  1. 开源组织 Apache 的 PDFBox。
  2. Adobe 的 iText,其中 iText 又分为 iText5 和 iText7。

那么这两个该如何选择呢?

  • PDFBox 的功能相对较弱,iText5 和 iText7 的功能非常强悍。
  • iText5 资料网上相对较多,如果出现问题容易找到解决方案。
  • PDFBox 和 iText7 的网上资料相对较少,如果出现问题不易找到相关解决方案。
  • PDFBox 目前提供的自定义签章接口不完整;而 iText5 和 iText7 提供了处理自定义签章的相关实现。
  • PDFBox 只能实现把签章图片加签到 PDF 文件;iText5 和 iText7 除了可以把签章图片加签到 PDF 文件,还可以实现直接对签章进行绘制,把文件绘制到签章上。
  • PDFBox 和 iText5/iText7 使用的协议不一样。PDFBox 使用的是 APACHE LICENSE VERSION 2.0(Licenses);iText5/iText7 使用的是 AGPL(https://itextpdf.com/agpl)。PDFBox 免费使用,AGPL 商用收费。

因此这里松哥就以 iText5 为例来和小伙伴们演示如何给一个 PDF 文件签名。

二 实战

2.1 生成数字证书

首先我们需要生成一个数字证书。

这个数字证书我们可以利用 JDK 自带的工具生成,为了贴近实战,松哥这里使用 Java 代码生成,生成数字证书的方式如下。

首先引入 Bouncy Castle,Bouncy Castle 是一个广泛使用的开源加密库,它为 Java 平台提供了丰富的密码学算法实现,包括对称加密、非对称加密、哈希算法、数字签名等。这个库由于其广泛的算法支持和可靠性而备受信任,被许多安全应用和加密通信协议所采用

<dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.70</version>
</dependency>
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-ext-jdk15on</artifactId><version>1.70</version>
</dependency>

接下来我们写一个生成数字证书的工具类,如下:

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;/*** @author:江南一点雨* @site:http://www.javaboy.org* @微信公众号:江南一点雨* @github:https://github.com/lenve* @gitee:https://gitee.com/lenve*/
public class PkcsUtils {/*** 生成证书** @return* @throws NoSuchAlgorithmException*/private static KeyPair getKey() throws NoSuchAlgorithmException {KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA",new BouncyCastleProvider());generator.initialize(1024);// 证书中的密钥 公钥和私钥KeyPair keyPair = generator.generateKeyPair();return keyPair;}/*** 生成证书** @param password* @param issuerStr* @param subjectStr* @param certificateCRL* @return*/public static Map<String, byte[]> createCert(String password, String issuerStr, String subjectStr, String certificateCRL) {Map<String, byte[]> result = new HashMap<String, byte[]>();try(ByteArrayOutputStream out= new ByteArrayOutputStream()) {// 标志生成PKCS12证书KeyStore keyStore = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());keyStore.load(null, null);KeyPair keyPair = getKey();// issuer与 subject相同的证书就是CA证书X509Certificate cert = generateCertificateV3(issuerStr, subjectStr,keyPair, result, certificateCRL);// 证书序列号keyStore.setKeyEntry("cretkey", keyPair.getPrivate(),password.toCharArray(), new X509Certificate[]{cert});cert.verify(keyPair.getPublic());keyStore.store(out, password.toCharArray());byte[] keyStoreData = out.toByteArray();result.put("keyStoreData", keyStoreData);return result;} catch (Exception e) {e.printStackTrace();}return result;}/*** 生成证书* @param issuerStr* @param subjectStr* @param keyPair* @param result* @param certificateCRL* @return*/public static X509Certificate generateCertificateV3(String issuerStr,String subjectStr, KeyPair keyPair, Map<String, byte[]> result,String certificateCRL) {ByteArrayInputStream bint = null;X509Certificate cert = null;try {PublicKey publicKey = keyPair.getPublic();PrivateKey privateKey = keyPair.getPrivate();Date notBefore = new Date();Calendar rightNow = Calendar.getInstance();rightNow.setTime(notBefore);// 日期加1年rightNow.add(Calendar.YEAR, 1);Date notAfter = rightNow.getTime();// 证书序列号BigInteger serial = BigInteger.probablePrime(256, new Random());X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(new X500Name(issuerStr), serial, notBefore, notAfter,new X500Name(subjectStr), publicKey);JcaContentSignerBuilder jBuilder = new JcaContentSignerBuilder("SHA1withRSA");SecureRandom secureRandom = new SecureRandom();jBuilder.setSecureRandom(secureRandom);ContentSigner singer = jBuilder.setProvider(new BouncyCastleProvider()).build(privateKey);// 分发点ASN1ObjectIdentifier cRLDistributionPoints = new ASN1ObjectIdentifier("2.5.29.31");GeneralName generalName = new GeneralName(GeneralName.uniformResourceIdentifier, certificateCRL);GeneralNames seneralNames = new GeneralNames(generalName);DistributionPointName distributionPoint = new DistributionPointName(seneralNames);DistributionPoint[] points = new DistributionPoint[1];points[0] = new DistributionPoint(distributionPoint, null, null);CRLDistPoint cRLDistPoint = new CRLDistPoint(points);builder.addExtension(cRLDistributionPoints, true, cRLDistPoint);// 用途ASN1ObjectIdentifier keyUsage = new ASN1ObjectIdentifier("2.5.29.15");// | KeyUsage.nonRepudiation | KeyUsage.keyCertSignbuilder.addExtension(keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));// 基本限制 X509Extension.javaASN1ObjectIdentifier basicConstraints = new ASN1ObjectIdentifier("2.5.29.19");builder.addExtension(basicConstraints, true, new BasicConstraints(true));X509CertificateHolder holder = builder.build(singer);CertificateFactory cf = CertificateFactory.getInstance("X.509");bint = new ByteArrayInputStream(holder.toASN1Structure().getEncoded());cert = (X509Certificate) cf.generateCertificate(bint);byte[] certBuf = holder.getEncoded();SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");// 证书数据result.put("certificateData", certBuf);//公钥result.put("publicKey", publicKey.getEncoded());//私钥result.put("privateKey", privateKey.getEncoded());//证书有效开始时间result.put("notBefore", format.format(notBefore).getBytes("utf-8"));//证书有效结束时间result.put("notAfter", format.format(notAfter).getBytes("utf-8"));} catch (Exception e) {e.printStackTrace();} finally {if (bint != null) {try {bint.close();} catch (IOException e) {}}}return cert;}public static void main(String[] args) throws Exception {// CN: 名字与姓氏    OU : 组织单位名称// O :组织名称  L : 城市或区域名称  E : 电子邮件// ST: 州或省份名称  C: 单位的两字母国家代码String issuerStr = "CN=javaboy,OU=产品研发部,O=江南一点雨,C=CN,E=javaboy@gmail.com,L=华南,ST=深圳";String subjectStr = "CN=javaboy,OU=产品研发部,O=江南一点雨,C=CN,E=javaboy@gmail.com,L=华南,ST=深圳";String certificateCRL = "http://www.javaboy.org";Map<String, byte[]> result = createCert("123456", issuerStr, subjectStr, certificateCRL);FileOutputStream outPutStream = new FileOutputStream("keystore.p12");outPutStream.write(result.get("keyStoreData"));outPutStream.close();FileOutputStream fos = new FileOutputStream(new File("keystore.cer"));fos.write(result.get("certificateData"));fos.flush();fos.close();}}

运行这个工具代码,会在我们当前工程目录下生成 keystore.p12keystore.cer 两个文件。

其中 keystore.cer 文件通常是一个以 DER 或 PEM 格式存储的 X.509 公钥证书,它包含了公钥以及证书所有者的信息,如姓名、组织、地理位置等。

keystore.p12 文件是一个 PKCS#12 格式的文件,它是一个个人信息交换标准,用于存储一个或多个证书以及它们对应的私钥。.p12 文件是加密的,通常需要密码才能打开。这种文件格式便于将证书和私钥一起分发或存储,常用于需要在不同系统或设备间传输证书和私钥的场景。

总结下就是,.cer 文件通常只包含公钥证书,而 .p12 文件可以包含证书和私钥。

2.2 生成印章图片

接下来我们用 Java 代码绘制一个签章图片,如下:

public class SealSample {public static void main(String[] args) throws Exception {Seal seal = new Seal();seal.setSize(200);SealCircle sealCircle = new SealCircle();sealCircle.setLine(4);sealCircle.setWidth(95);sealCircle.setHeight(95);seal.setBorderCircle(sealCircle);SealFont mainFont = new SealFont();mainFont.setText("江南一点雨股份有限公司");mainFont.setSize(22);mainFont.setFamily("隶书");mainFont.setSpace(22.0);mainFont.setMargin(4);seal.setMainFont(mainFont);SealFont centerFont = new SealFont();centerFont.setText("★");centerFont.setSize(60);seal.setCenterFont(centerFont);SealFont titleFont = new SealFont();titleFont.setText("财务专用章");titleFont.setSize(16);titleFont.setSpace(8.0);titleFont.setMargin(54);seal.setTitleFont(titleFont);seal.draw("公章1.png");}
}

这里涉及到的一些工具类文末可以下载。

最终生成的签章图片类似下面这样:

现在万事具备,可以给 PDF 签名了。

2.3 PDF 签名

最后,我们可以通过如下代码为 PDF 进行签名。

这里我们通过 iText 来实现电子签章,因此需要先引入 iText:

<dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13.4</version>
</dependency>
<dependency><groupId>com.itextpdf</groupId><artifactId>html2pdf</artifactId><version>5.0.5</version>
</dependency>

接下来对 PDF 文件进行签名:

public class SignPdf2 {/*** @param password pkcs12证书密码* @param keyStorePath pkcs12证书路径* @param signPdfSrc 签名pdf路径* @param signImage 签名图片* @param x* @param y* @return*/public static byte[] sign(String password, String keyStorePath, String signPdfSrc, String signImage,float x, float y) {File signPdfSrcFile = new File(signPdfSrc);PdfReader reader = null;ByteArrayOutputStream signPDFData = null;PdfStamper stp = null;FileInputStream fos = null;try {BouncyCastleProvider provider = new BouncyCastleProvider();Security.addProvider(provider);KeyStore ks = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());fos = new FileInputStream(keyStorePath);// 私钥密码 为Pkcs生成证书是的私钥密码 123456ks.load(fos, password.toCharArray());String alias = (String) ks.aliases().nextElement();PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());Certificate[] chain = ks.getCertificateChain(alias);reader = new PdfReader(signPdfSrc);signPDFData = new ByteArrayOutputStream();// 临时pdf文件File temp = new File(signPdfSrcFile.getParent(), System.currentTimeMillis() + ".pdf");stp = PdfStamper.createSignature(reader, signPDFData, '\0', temp, true);stp.setFullCompression();PdfSignatureAppearance sap = stp.getSignatureAppearance();sap.setReason("数字签名,不可改变");// 使用png格式透明图片Image image = Image.getInstance(signImage);sap.setImageScale(0);sap.setSignatureGraphic(image);sap.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);// 是对应x轴和y轴坐标sap.setVisibleSignature(new Rectangle(x, y, x + 185, y + 68), 1,UUID.randomUUID().toString().replaceAll("-", ""));stp.getWriter().setCompressionLevel(5);ExternalDigest digest = new BouncyCastleDigest();ExternalSignature signature = new PrivateKeySignature(key, DigestAlgorithms.SHA512, provider.getName());MakeSignature.signDetached(sap, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CADES);stp.close();reader.close();return signPDFData.toByteArray();} catch (Exception e) {e.printStackTrace();} finally {if (signPDFData != null) {try {signPDFData.close();} catch (IOException e) {}}if (fos != null) {try {fos.close();} catch (IOException e) {}}}return null;}public static void main(String[] args) throws Exception {byte[] fileData = sign("123456", "keystore.p12", "待签名.pdf",//"公章1.png", 100, 290);FileOutputStream f = new FileOutputStream(new File("已签名.pdf"));f.write(fileData);f.close();}
}

这里所需要的参数基本上前文都提过了,不再多说。

从表面上看,签名结束之后,PDF 文件上多了一个印章,如下:

本质上,则是该 PDF 文件多了一个签名信息,通过 Adobe 的 PDF 软件可以查看,如下:

之所以显示签名有效性未知,是因为我们使用的是自己生成的数字证书,如果从权威机构申请的数字证书,就不会出现这个提示。

好啦,是不是很 easy?

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

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

相关文章

Unbounded:一个无限生成式交互的角色生活模拟游戏

❤️ 如果你也关注大模型与 AI 的发展现状&#xff0c;且对大模型应用开发非常感兴趣&#xff0c;我会快速跟你分享最新的感兴趣的 AI 应用和热点信息&#xff0c;也会不定期分享自己的想法和开源实例&#xff0c;欢迎关注我哦&#xff01; &#x1f966; 微信公众号&#xff…

Vertx实现和spring的application.yml自动配置加载

前言 在用vertx写项目的时候&#xff0c;由于需要不同的环境加载不同的配置文件&#xff0c;这里就需要和spring架构的application.yml配置文件一样&#xff0c;可以根据环境变量加载不同的配置。 代码 引入vertx相关依赖 <dependency><groupId>io.vertx</gr…

ECharts饼图-饼图自定义样式,附视频讲解与代码下载

引言&#xff1a; 在数据可视化的世界里&#xff0c;ECharts凭借其丰富的图表类型和强大的配置能力&#xff0c;成为了众多开发者的首选。今天&#xff0c;我将带大家一起实现一个饼图图表&#xff0c;通过该图表我们可以直观地展示和分析数据。此外&#xff0c;我还将提供详…

猫头虎 分享:前端工具 NVM 的简介、安装、用法详解入门教程

&#x1f42f; 猫头虎 分享&#xff1a;前端工具 NVM 的简介、安装、用法详解入门教程 今天猫头虎带您深入了解 NVM&#xff01;最近&#xff0c;猫哥在项目中遇到一个前端小伙伴&#xff0c;询问如何在开发过程中高效地切换不同的 Node.js 版本。前端项目往往需要特定版本的 …

二分查找_在排序数组中查找元素的第一个和最后一个位置

1.朴素二分查找 .二分查找 二分查找思路&#xff1a; 1.left0,rightnums.size()-1&#xff08;最后一个元素下标&#xff09;&#xff0c;取中间元素下标 midleft(right-left)/2 &#xff08;防溢出&#xff09; 2.nums[mid]>target &#xff0c;说明mid右边的元素都大于ta…

Discuz 论坛开发一套传奇发布站与传奇开服表

Discuz 论坛开发一套传奇发布站与传奇开服表 随着互联网技术的飞速发展&#xff0c;网络游戏已成为人们休闲娱乐的重要方式之一。在众多网络游戏中&#xff0c;传奇系列以其独特的魅力吸引了大量忠实玩家。为了满足这些玩家的需求&#xff0c;并促进游戏信息的交流与分享&…

QtCreator通过CMake多文件编译.cpp、.qss、.h、.ui文件,达到MVC三层架构的效果

博主在构建C项目的时候&#xff0c;一般都喜欢将头文件和源文件分开为不同的文件夹&#xff0c;比如include目录下只存放.h文件和.ui文件&#xff0c;src目录下只存放.cpp和.qss文件&#xff0c;res目录下只存放图片、音频等文件&#xff0c;这时候使用CMake对项目进行分文件管…

qml圆形图片,qml圆形头像制作

代码比较简单&#xff0c;就不细讲了&#xff0c;大家直接看下面源码吧&#xff01;如果对你有所帮助&#xff0c;可以帮角角点个关注嘛&#xff1f; import QtQuick import QtQuick.Effects import Qt5Compat.GraphicalEffectsWindow {width: 640height: 480visible: truetit…

使用代理服务器后sse数据合并问题

如果是使用本地代理服务器devServer compress:false,如果是发布到生产环境下的代理服务器nginx 增加如下配置&#xff0c;该配置同时支持websocket和sse proxy_http_version 1.1; #设置代理请求使用 HTTP/1.1 版本。WebSocket 需要 HTTP/1.1&#xff0c;因为它支持持久连接和更…

【每日一题】LeetCode - 盛最多水的容器

给定一个长度为 n 的整数数组 height。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i])。要求找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 输入示例&#xff1a; height [1,8,6,2,5,4,8,3,7]输出&#xff1a; 4…

Python快速入门教程

目录 1. Python 简介 2. 环境准备 3. 第一个 Python 程序 4. 变量与数据类型 5. 基本操作与控制结构 6. 函数与模块 7. 实践项目 结语 Python 是一种非常友好的编程语言&#xff0c;特别适合初学者。它的语法简洁&#xff0c;容易上手&#xff0c;并且广泛应用于各种领…

C++结合图形编程与物联网:你更偏向哪种方式来学习信息学奥赛?

随着信息学奥赛在全国范围内的热度逐年攀升&#xff0c;学生和家长们越来越重视如何有效备赛。传统的编程学习方式侧重于算法和数据结构&#xff0c;但随着科技的发展&#xff0c;图形化编程与物联网&#xff08;IoT&#xff09;项目逐渐成为新兴的学习路径。通过C结合图形化编…

Rust 力扣 - 1. 两数相加

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们使用一个全局的备忘录&#xff0c;然后我们遍历数组&#xff0c;如果当前元素在备忘录里面找到了&#xff0c;就返回备忘录里面记录的下标和当前下标记录&#xff0c;没找到就把当前元素匹配的元素和当前元素…

人工智能_神经网络103_感知机_感知机工作原理_感知机具备学习能力_在学习过程中自我调整权重_优化效果_多元线性回归_逻辑回归---人工智能工作笔记0228

由于之前一直对神经网络不是特别清楚,尤其是对神经网络中的一些具体的概念,包括循环,神经网络卷积神经网络以及他们具体的作用,都是应用于什么方向不是特别清楚,所以现在我们来做教程来具体明确一下。 当然在机器学习之后还有深度学习,然后在深度学习中对各种神经网络的…

Java对称加密:AES 高级加密标准

1、对称加密简述 对称加密&#xff0c;又称对称密钥加密或私钥加密&#xff0c;是一种在加密和解密过程中使用相同一个密钥的加密算法。这种加密方式的核心在于&#xff0c;发送方使用某个密钥对数据进行加密&#xff0c;接收方则使用完全相同的密钥对数据进行解密。由于加密和…

基于物联网的智慧考场系统设计(论文+源码)

1. 功能设计 &#xff08;1&#xff09;温度监测与控制功能&#xff1a; 系统需要能够实时采集考场内的温度信息&#xff0c;通过DS18B20传感器获取准确的数据&#xff0c;并在OLED屏幕和APP上显示。当温度异常过高时&#xff0c;系统应自动启动继电器&#xff0c;模拟空调开启…

数字IC后端实现 | Innovus各个阶段常用命令汇总

应各位读者要求&#xff0c;小编最近按照Innovus流程顺序整理出数字IC后端项目中常用的命令汇总。限于篇幅&#xff0c;这次只更新到powerplan阶段。有了这份Innovus常用命令汇总&#xff0c;学习数字IC后端从此不再迷路&#xff01;如果大家觉得这个专题还不错&#xff0c;想继…

C语言_动态内存管理

本章重点 为什么存在动态内存分配 动态内存函数的介绍 malloc free calloc realloc 常见的动态内存错误 几个经典的笔试题 柔性数组 1. 为什么存在动态内存分配 我们已经掌握的内存开辟方式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] {0}…

Maven进阶——坐标、依赖、仓库

目录 1.pomxml文件 2. 坐标 2.1 坐标的概念 2.2 坐标的意义 2.3 坐标的含义 2.4 自己项目的坐标 2.5 第三方项目坐标 3. 依赖 3.1 依赖的意义 3.2 依赖的使用 3.3 第三方依赖的查找方法 3.4 依赖范围 3.5 依赖传递和可选依赖 3.5.1 依赖传递 3.5.2 依赖范围对传…

算法的学习笔记—数组中的逆序对(牛客JZ51)

&#x1f600;前言 在算法和数据结构领域&#xff0c;"逆序对"是一个经典问题。它在数组中两个数字之间定义&#xff0c;若前面的数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。我们要做的就是&#xff0c;给定一个数组&#xff0c;找出数组中所有的逆…