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…

插槽 - 2024最新版前端秋招面试短期突击面试题【100道】

插槽 - 2024最新版前端秋招面试短期突击面试题【100道】 在前端开发中&#xff0c;插槽是一个非常重要的概念&#xff0c;尤其是在使用 Vue.js 时。本文将深入探讨插槽的使用&#xff0c;包括默认插槽、具名插槽和作用域插槽&#xff0c;帮助你在面试中更好地理解和回答相关问…

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;并促进游戏信息的交流与分享&…

CSP-S2024游记

考前 前一天晚上提前回家了&#xff0c;晚上十一点上床睡觉&#xff0c;上午九点半自然醒了&#xff0c;在床上刷了半个小时手机&#xff0c;成功略过了早饭。 午饭前看了一会板子&#xff0c;tarjan、KMP之类的简单板子&#xff0c;但好像都没考到。 午饭吃的很简单&#x…

QT编辑框带行号

很可惜&#xff0c;qt的几个编辑框并没有相关功能。所以我们要自己实现一个。 先讲讲原理&#xff1a; QPlainTextEdit继承自QAbstractScrollArea&#xff0c;编辑发生在其viewport&#xff08;&#xff09;的边距内。我们可以通过将视口的左边缘设置一个空白区域&#xff0c;…

VScode插件:前端每日一题

大文件上传如何做断点续传&#xff1f; 在前端实现大文件上传的断点续传&#xff0c;通常会将文件切片并分块上传&#xff0c;记录每块的上传状态&#xff0c;以便在中断或失败时只上传未完成的部分。以下是实现断点续传的主要步骤和思路&#xff1a; 1. 文件切片 (File Slici…

ubuntu 20.4 安装 openssl 3.x

ubuntu 20.4 安装 openssl 3.x ubuntu 20.4 自带了openssl 1.0.2&#xff0c;升级为 openssl 3.x&#xff1a; # 下载 openssl 源代码压缩包 wget https://www.openssl.org/source/openssl-3.0.10.tar.gz# 安装编译包 sudo apt-get install -y g sudo apt-get install -y mak…

python把一张小图粘贴到一张大图上

在Python中&#xff0c;你可以使用Pillow库&#xff08;Python Imaging Library的一个分支&#xff09;来实现将一张小图粘贴到一张大图的左上角&#xff08;0, 0&#xff09;位置。以下是一个示例代码&#xff0c;展示了如何完成这一任务&#xff1a; 首先&#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;因为它支持持久连接和更…

【python库】PandasGUI介绍

Github地址&#xff1a;https://github.com/adamerose/PandasGUI 在数据科学和分析过程中&#xff0c;数据的可视化和交互操作是非常重要的环节。尽管 Pandas 是一个强大的数据处理库&#xff0c;但其缺乏用户友好的图形界面&#xff0c;这使得数据探索和分析变得相对繁琐。pan…

【每日一题】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…

2024年1024程序人生总结

2024-1024 0.大环境0.1.经济0.2.战争 1.我的程序人生1.1.游戏 2.节日祝福 0.大环境 今年的1024最大的感触就是没有节日氛围&#xff0c;往年公司还会准备节日礼物&#xff0c;今年没有&#xff0c;由此可见大环境有多么糟糕。 除此之外&#xff0c;就是到公司应聘的程序员越来…

如何理解前端与后端开发

前端与后端开发是构建现代Web应用的两个主要部分&#xff0c;它们共同工作&#xff0c;为用户提供完整的在线体验。以下是对前端和后端开发的理解和它们之间的主要区别&#xff1a; 前端开发&#xff08;客户端开发&#xff09; 用户界面&#xff08;UI&#xff09;&#xff…

2025前端面试-浏览器的事件循环和浏览器的事件循环的区别是什么---002

浏览器的事件循环和浏览器的事件循环的区别是什么 JS是单线程的浏览器中JS执行和DOM渲染公用一个线程异步 异步中又有宏任务和微任务 宏任务 setTimtout setInterval微任务 Promise async await(先执行同步任务后执行异步任务&#xff09;微任务在下一轮DOM渲染之前执行&…