news 2026/9/7 9:49:40

C#实现国密算法SM2/SM3/SM4实战指南与踩坑总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C#实现国密算法SM2/SM3/SM4实战指南与踩坑总结

简介:面向需要在C#项目中集成国产密码算法的.NET开发者,这份资源实现了SM2非对称加密、SM3密码杂凑、SM4分组密码这三套国密算法,并提供完整的Winform界面示例,可直接用于政务系统、金融接口、企业内部数据加密等合规场景,帮助工程师快速掌握国密算法在工程中的落地方式。压缩包共51个文件、约5.33MB,代码主体是21个C#源文件,包含SM2Util、SM3Digest、SM3Crypto、SM4Context、SM4Utils等核心类,覆盖密钥生成、签名验签、摘要计算、分组加解密等常用操作;同时集成5个界面资源文件、4个第三方依赖库(内置BouncyCastle.Crypto动态库)、2个可直接运行的演示程序,以及解决方案和配置信息等辅助资源,在Visual Studio中打开即可编译运行,方便对照界面观察各类算法的输入输出。整体代码将算法实现与界面逻辑分离,脉络清晰,读者既可以运行演示程序快速验证结果,也可以深入源码学习.NET Framework 4.0下国密算法的封装方式与调用细节。目前已有1312人学习,适合需要实际落地或二次开发国密算法的中级以上.NET工程师。 国密算法这几年已经不只是金融、政务项目的专属需求了,做企业信息化的、做物联网平台的、做上位机软件的,都在陆陆续续接到国密改造的活儿。对C#技术栈的团队来说,SM2、SM3、SM4这三位几乎是绕不过去的坎:SM2做签名验签和加解密、SM3做完整性校验和摘要、SM4做对称加密,三件套组合在一起几乎覆盖了“传输加密 + 身份认证 + 数据完整性”的全部基本诉求。这篇文章我就以实际落地的C#实现为主线,把三套算法在.NET环境里的实现思路、关键代码、以及我在互操作测试中踩过的各种坑一次性捋清楚。

如果你正准备做信创适配、等保三级整改,或者只是单纯想把国密算法集成到自己维护的C#系统里,这篇内容可以直接给你的技术选型和开发排期提供参考。我不打算堆一堆网上到处能搜到的公式,而是重点讲那些真正影响你项目进度的细节:SM2的密文格式什么时候用C1C2C3、C#里怎么处理椭圆曲线的大数运算、SM3为什么和SHA-256长得像但绝对不能混用、SM4的PKCS7填充到底怎么和Java/JS端对齐。这些坑我在实际对接中都踩过,写出来帮你省点时间。

1. 国密算法选型与C#落地的整体思路

1.1 SM2、SM3、SM4分别解决什么问题

一句话概括这三者的关系:SM2是公钥密码体系里的“身份证和保险柜”,SM3是数据世界的“指纹提取器”,SM4是信息传输中的“保险箱锁芯”。

SM2是非对称算法,基于椭圆曲线密码体制(ECC),密钥长度256位,安全强度高于同长度的RSA。它承担两类核心任务:一是数字签名与验签,用于身份认证和防抵赖;二是数据加解密,用于交换密钥或加密小体积敏感数据。SM3则是一个密码杂凑算法,输出固定256位摘要,主要用途是数据完整性校验、消息认证、以及作为SM2签名过程中的一个关键构件——ZA值计算和消息摘要都需要调用SM3。SM4是对称分组密码算法,分组长度128位、密钥长度128位,专门负责高效加密大块数据,比如接口报文、文件内容、数据库字段。

在实际业务系统里,这三者的配合方式非常固定:用SM4加密业务数据,用SM2加密SM4的密钥或做签名认证,用SM3校验数据在传输过程中是否被篡改。理解了这套组合逻辑,你再去看等保和密评的整改要求,就会很清楚每一条规定对应到代码里究竟要改什么。

1.2 直接引库还是自研?先看清这几类方案

在C#里做国密算法,第一个抉择是引库还是自研。如果项目周期紧张,直接使用成熟类库是性价比最高的选择。目前.NET生态里最常用的是BouncyCastle,它在较新的版本中已经原生支持SM2、SM3、SM4。你只需要通过NuGet安装BouncyCastle.Cryptography包,不需要额外处理底层数学运算,调用API就能完成加解密、签名验签。它的好处是经过大量项目验证,而且能和Java、Go等语言的国密实现做互操作。另一个常见方案是使用GmSSL的C#封装,GmSSL是官方推荐的算法开源库,很多国产化平台都在底层调用它,如果你们项目已经依赖了GmSSL的C库,封装一层P/Invoke也很合理。

如果你们有内网部署、等保审计、代码可控性方面的硬性要求,或者团队想深度掌握算法细节,自研也是一条可行的路。自研并不意味着从零推公式,而是用C#的System.Numerics.BigInteger实现大数运算,按标准规定的参数和流程搭出SM2/SM3/SM4的完整功能。这个方案的优点是完全掌控实现细节,不依赖第三方程序集,利于深度定制;缺点也很明显——密码算法实现的正确性验证极其耗时,必须用官方标准文档中的测试向量逐条校验,否则上线后出了问题极难排查。

我的建议是:产品化项目优先选BouncyCastle,先把功能跑通;自研实现可以作为技术储备或用于特殊约束场景。接下来讨论的算法细节,两种路线都有参考价值,因为理解底层逻辑才能正确处理格式、编码、填充这些容易出错的地方。

1.3 C#实现相比Java/JS的特殊难点

C#实现国密算法,有几个天然要比Java和JS多花心思的地方。首先是跨平台问题,.NET Core/.NET 5+虽然实现了跨平台,但C#生态里部分依赖本机库的组件在不同操作系统上行为可能不一致,尤其是在国产化操作系统(如麒麟、统信UOS)上部署时,要提前验证算法库的兼容性。其次是编码问题,C#默认字符串是UTF-16,而国密标准文档中的测试向量和业务数据通常使用UTF-8或GBK编码,转换不当会导致摘要结果完全不同。最后是大端小端问题,C#的BitConverter默认依赖运行平台,而国密算法的字节序有严格定义,处理多字节整数时必须显式指定。

这三个问题在实际开发中引发的bug比例相当高,我后面会在对应章节展开讲。

2. SM2算法实现:椭圆曲线运算与签名验签

2.1 从椭圆曲线参数开始:推荐曲线与ZA值计算

SM2使用的是256位素域Fp上的椭圆曲线,标准推荐参数是固定的。在C#里实现时,曲线参数建议写成常量,不要每次实例化都重新计算。核心参数如下:

  • p = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
  • a = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
  • b = 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
  • n = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
  • Gx = 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
  • Gy = BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0

签名验签和密钥交换中有一个非常关键的构造——ZA值。按照标准,ZA = SM3(ENTLA || IDA || a || b || xG || yG || xA || yA),其中IDA是用户的身份标识,ENTLA是IDA的比特长度转换成的两个字节。这个设计很有意思,它把用户身份绑定进了签名数据里,意味着即使私钥相同,使用不同身份ID产生的签名上下文也不同,能有效防止签名重放和跨场景复用。C#实现时,要注意IDA默认值在部分标准中约定的长度是16字节(如1234567812345678),在与Java/JS端对接时要确认对方使用的默认ID是什么,否则ZA计算不一致会导致验签失败。

2.2 C#中大数运算与点乘的工程实现

SM2性能的关键在于椭圆曲线点乘kP的运算,底层依赖大数模乘和模逆。C#的BigInteger类已经封装了任意精度整数运算,直接拿来做模运算没有问题,但性能方面有两个可以优化的点:一是模乘时尽量用BigInteger.ModPow而不是自己写乘法和模运算,二是在点乘计算中可以使用二进制展开法,把k的每一位对应到点加和倍点操作上。

下面是一个简化的核心框架,演示如何定义椭圆曲线点和点加运算:

public struct ECPoint { public BigInteger X; public BigInteger Y; public bool IsInfinity; // 无穷远点 } public static ECPoint PointAdd(ECPoint P, ECPoint Q, BigInteger a, BigInteger p) { if (P.IsInfinity) return Q; if (Q.IsInfinity) return P; BigInteger lambda; if (P.X == Q.X) { if ((P.Y + Q.Y) % p == 0) return new ECPoint { IsInfinity = true }; // 倍点: lambda = (3x^2 + a) / (2y) BigInteger numerator = (3 * P.X * P.X + a) % p; BigInteger denominator = BigInteger.ModPow(2 * P.Y % p, p - 2, p); // 模逆 lambda = numerator * denominator % p; } else { // 加法: lambda = (y2 - y1) / (x2 - x1) BigInteger numerator = (Q.Y - P.Y + p) % p; BigInteger denominator = BigInteger.ModPow((Q.X - P.X + p) % p, p - 2, p); lambda = numerator * denominator % p; } BigInteger xR = (lambda * lambda - P.X - Q.X + 2 * p) % p; BigInteger yR = (lambda * (P.X - xR + p) % p - P.Y + p) % p; return new ECPoint { X = xR, Y = yR, IsInfinity = false }; }

这段代码不是完整的国密实现,但已经能说明两个实际问题:所有中间结果都要做mod p约束,防止BigInteger无限增长导致性能劣化;模逆运算用的是费马小定理,即ModPow(a, p-2, p),在p为素数时是正确的,但在性能要求高的场合建议预计算或换用扩展欧几里得算法。真正常用的点乘算法还需要实现从左到右的二进制扫描,并在点加和倍点之间做选择,循环次数对应到k值的二进制位数,256位的私钥大约要跑256轮迭代。

2.3 加解密与签名验签的格式坑:C1C2C3还是C1C3C2

SM2加解密有一个极其容易出错的细节——密文排列格式。旧版标准中密文排列是C1C2C3,即先椭圆曲线点C1,再对称加密的数据C2,最后是杂凑值C3;而较新的行业标准(GM/T 0003.4-2012以后)推荐的是C1C3C2,也就是把摘要C3挪到了中间。很多在线工具默认输出C1C3C2,而一些老系统实现的是C1C2C3,两边对接时都不报错,解密出来却是乱码,原因就在这里。

C#实现SM2解密时,强烈建议同时支持两种格式的识别。判断方法很简单:C1是椭圆曲线点,在未压缩形式下固定为65字节(前缀04 + x坐标32字节 + y坐标32字节),剩下的字节中,标准的密文部分C2长度等于原始明文长度,C3长度固定为32字节。如果你能提前知道明文长度,就可以通过总长度 - 65 - 32来推测是哪种排列。如果不能确定明文长度,则需要尝试用两种排列分别解密,再对解密结果做业务校验(比如看是否为合法UTF-8字符串、是否包含预期字段)。我在项目中就遇到过老系统用C1C2C3加密的存量数据,新系统上线后全靠自动识别才完成了平滑迁移。

另一个和格式相关的坑是密钥的编码。SM2密钥对可以表示为裸的64字节私钥和130字节公钥,也可以包装成PKCS#8/PEM格式。与Java的KeyPairGenerator对接时,很多框架会把公钥编码成04 || X || Y的未压缩点格式(65字节),私钥则常用PKCS#8 DER编码。C#端用BouncyCastle解析这些格式时,需要调用PrivateKeyFactoryPublicKeyFactory,不要试图自己拼字节,因为DER编码内部还有嵌套结构和长度字段,手写极易出错。

3. SM3算法实现:杂凑计算的工程级细节

3.1 SM3算法流程拆解:填充、扩展、压缩

SM3的整体结构和SHA-256非常相似——都是Merkle-Damgard结构,消息分组512位,输出256位。实现时核心三件事:消息填充、消息扩展、压缩函数迭代。

消息填充规则是:在原始消息尾部先补一个1bit,再补若干个0bit,直到长度模512等于448,最后附加一个64位的原始消息长度(按bit计)。这意味着即使消息长度恰好满足条件,也至少要填充1bit。C#实现时,建议直接操作byte[]数组:先算出填充后的总长度,创建一个新的字节数组,复制原始数据,再按规则设置填充位和长度字段。这里有坑:长度字段是大端序的64位整数,一定要用IPAddress.HostToNetworkOrder或手动移位转换,不能直接用BitConverter.GetBytes

消息扩展阶段会把512位的消息分组扩展成132个32位字(W0~W67和W'0~W'63),压缩阶段则有64轮迭代,每轮使用两个32位字和一个常量Tj。这些常量都是标准固定的,写代码时直接定义成uint[]数组即可。

3.2 C#实现与性能实测

SM3的压缩函数里涉及一个P置换和多个32位循环左移操作。C#实现时,循环左移要自己写一个辅助方法,因为CLR没有内置的循环移位指令。可以用(x << n) | (x >> (32 - n))实现,但要注意n为0时右移32位的未定义行为,最好封装时先对n取模32。

性能方面,纯C#实现的SM3在主流x64机器上可以达到每秒几百MB的处理速度,对于大部分业务系统的报文摘要、文件校验场景已经完全够用。如果需要对超大文件做SM3摘要,建议用Stream分块读取而不是一次性加载到内存,每次读取64KB到1MB的缓冲区,从性能上不会明显低于一次性读取,内存占用却能降低几个量级。实时性要求极高的场景,可以考虑用System.Runtime.Intrinsics的SIMD指令做并行优化,但这属于锦上添花,绝大多数项目不需要走到这一步。

多线程环境下要注意:SM3的上下文中包含内部状态,非线程安全。如果同一个摘要对象被多个线程并发调用,会出现状态错乱。最简单的做法是每次计算新建实例,不要尝试复用,除非你实现的是有状态的分步哈希(即TransformBlock/TransformFinalBlock),那种场景我会建议给每个线程单独维护一个上下文实例。

3.3 容易被忽略的应用场景:ZA与HMAC-SM3

SM3不只是用来算摘要。在SM2的签名验签流程里,前面提到的ZA值计算依赖SM3;在SM4密钥派生和消息认证场景里,还常用到HMAC-SM3。HMAC的构造方法可以参考RFC 2104的思路,把SM3作为底层哈希函数替换掉MD5/SHA-1,密钥填充块长度取64字节(SM3分组长度),ipad为0x36、opad为0x5C。

我在对接第三方平台时,遇到过对方要求对请求体先做HMAC-SM3再参与签名的情况。很多开发只记得调SM3,忘记外面还要套HMAC结构,结果摘要值怎么都对不上。这里有个排查经验:先算一遍纯SM3,看是否和对方给的摘要一致;不一致再检查消息编码和填充规则;还是不对,就考虑对方是否用了自定义密钥处理方式(比如密钥截断或补零)。密码学对接里最忌讳猜测,手里拿着标准逐条对才是正解。

另一个容易踩的编码坑是中文消息。C#里的string实际存储的是UTF-16,如果不加处理直接Encoding.Default.GetBytes(),在不同系统上得到的字节序列完全不同。我明确建议所有涉及哈希计算的地方统一使用Encoding.UTF8.GetBytes(),并在设计接口文档时就约定“所有待签名/摘要内容一律UTF-8编码”,这样可以避免和Java(默认UTF-8)、JavaScript(通常UTF-8)对接时出现技术债务。

4. SM4算法实现:对称加密的模式与填充

4.1 SM4算法结构与轮密钥扩展

SM4是32轮非平衡Feistel结构的分组密码,分组长度和密钥长度都是128位(16字节)。它的核心部件是一个8进8出的S盒,加上一个线性变换L。加密时,128位明文分成4个32位字X0、X1、X2、X3,每轮用公式X_{i+4} = X_i ⊕ T(X_{i+1} ⊕ X_{i+2} ⊕ X_{i+3} ⊕ RK_i)更新,经过32轮后输出密文。

SM4的S盒是固定的256字节表,直接定义成byte[]数组即可。轮密钥生成算法与加密结构几乎相同,只是输入的密钥和固定的系统参数FK以及固定参数CK不同。实现时有一个细节:解密算法和加密算法结构完全一样,只是轮密钥的使用顺序相反,这意味着你只需要实现一遍加解密主流程,解密时把32个轮密钥倒过来传入即可。这一点和DES类似,但比AES简单——AES解密还需要额外的逆S盒和逆列混合变换。

C#中处理SM4的字节操作时,建议把16字节的块转换为4个uint,用位运算处理完后再转回字节数组。注意字节序,国密标准中规定多字节整数是高位在前(大端序),如果你用BitConverter.ToUInt32去转换,默认在小端机器上会得到相反的结果,必须先用BinaryPrimitives.ReadUInt32BigEndian或者手动做(b0 << 24) | (b1 << 16) | (b2 << 8) | b3

4.2 ECB/CBC/CTR模式与PKCS7填充怎么选

SM4本身是分组密码,一次处理16字节。实际业务数据几乎不可能恰好是16字节的整数倍,所以必须引入分组模式和填充方式。最常见的组合是ECB和CBC模式,配合PKCS7填充。

ECB模式实现最简单,每个16字节块独立加密,缺点是相同明文块会产生相同密文块,数据模式会泄露,因此不推荐用于长报文加密。CBC模式引入了前一个密文块对当前块的扰动,需要16字节的初始向量IV,安全强度明显更好,是接口报文加密的首选。CTR模式把SM4变成一个流式加密器,不需要填充,可以按任意字节长度加密,但需要保证IV和计数器组合的唯一性,否则会破坏机密性。

PKCS7填充规则是:缺几个字节就补几个值为该字节数的字节。如果明文长度恰好是16的倍数,PKCS7仍然会额外填充整整16字节的0x10,这是为了让解密端能正确判断末尾是否包含填充。C#里用BouncyCastle的Pkcs7Padding或者手动实现都很容易,但要注意和Java端对接时,部分Java框架默认使用PKCS5Padding,对于AES(块大小16字节)和SM4(块大小16字节)来说PKCS5Padding实际就是PKCS7Padding,二者可以互通,不用纠结名称差异。

4.3 C#实现技巧与跨平台互操作

如果采用自研方案,SM4的核心循环体建议提前把扩展后的轮密钥计算好并缓存,不要每次加密都重新生成。对于需要加密大量独立数据块的场景,还可以考虑并行化——CBC模式由于每个块依赖前一个块的密文,无法直接并行;但CTR模式每个块独立,可以使用Parallel.For加速,前提是处理计数器分片的逻辑要正确。实际项目中我测过,在4核机器上CTR模式的并行化可以带来接近线性的加速比。

在跨平台互操作方面,最值得注意的还是字符串转字节的规则和十六进制编码的格式。C#中常用的Convert.ToHexString()输出的是大写十六进制,而部分Java库输出的是小写,如果不做统一,日志里的密文看起来完全不同,但实际上字节内容是一致的。调试时建议写一个标准的字节数组比较工具函数:先把两边数据都转成十六进制字符串并统一大小写,再逐字节比较,可以快速定位是算法问题还是编码问题。

SM4的密钥和IV长度都是16字节,很多开发者习惯用字符串直接当密钥,比如"1234567890abcdef"。这种做法本身没有错,但必须约定字符串的编码方式。我在项目中吃过一次亏:Java端把一个中文密码字符串用UTF-8转字节做了SM4密钥,取到的字节数不是16而是更多,于是又做了截断或哈希处理,而C#端直接按ASCII取前16字节,两边的密钥完全不同,加密结果自然对不上。最稳妥的做法是:密钥和IV统一用16字节的十六进制字符串表示,在代码里通过Convert.FromHexString()解析成字节数组,避免编码歧义。

5. 从0到1的几个常见问题与排查实录

5.1 本地结果与在线工具对不上?先查这五处

对接国密算法时,最先遇到的大概率是“我的代码和在线工具跑出来的结果不一样”。这种问题九成以上不是算法实现错了,而是输入参数或格式没对齐。按出现频率排序,我给出一份排查清单:

  • 字符串编码不一致:C#端用了UTF-16或系统默认编码,在线工具和Java端通常是UTF-8。
  • 密文格式不一致:SM2的C1C2C3/C1C3C2,SM4的ECB/CBC模式,SM3是否参与HMAC套壳。
  • 密钥和IV的表示方式:十六进制字符串的大小写、是否有0x前缀、是否额外做了Base64编码。
  • 填充方式:SM4使用的是NoPadding还是PKCS7,SM2内部是否有特定Padding规则。
  • 公钥/私钥格式:裸字节还是DER/PEM,是否包含04前缀,是否使用了未压缩点格式。

如果这五处都检查过仍然对不上,就要怀疑两端使用的算法参数是否一致,比如SM2的曲线参数是否被某端换成了其他曲线。

注意:在线工具是不可靠的排障依据,它们本身也可能有bug或格式兼容问题。最权威的校验方式是用国家标准文档附件中的测试向量,比如GM/T 0003、GM/T 0004、GM/T 0002中每组算法都给出了多组输入/输出样例,能够逐字节验证实现正确性。

5.2 签名验签不通过的典型原因

SM2验签不通过是最折磨人的问题,因为签名算法本身就涉及私钥、公钥、ZA值、消息摘要、随机数k等多个变量。总结我遇到的案例,最主要的原因集中在三方面。

第一是ZA值计算时身份ID不一致。默认ID不同(比如空字符串和1234567812345678),签名的消息内容就不同,验签必然失败。第二是消息摘要的编码或字节顺序不一样,尤其是中文消息和多字节数字转字符串再参与签名时,两端的编码一旦不同,摘要就变了。第三是签名值的DER编码和裸r||s格式的混淆。SM2签名结果在标准里定义为r和s两个大整数,但传输时可能被封装成ASN.1 DER结构,也可能直接拼接成64字节。BouncyCastle的SM2Signer默认输出DER格式,如果对端期望的是64字节裸签名,需要调用SignerUtilities之外的底层API做编码转换。

排查验签问题时,一个非常有效的技巧是:让对端提供一个已知的(消息、签名、公钥)样本,在自己的代码里先用同一个消息和公钥去验签。如果验签失败,把ZA值分别打印出来对比;如果ZA值一致,再把消息摘要打印出来对比。这种逐步缩窄问题范围的方式,通常能在十分钟内定位到具体是哪一层出了偏差。

5.3 性能调优与内存分配的实操经验

国密算法的性能优化,要分场景讨论。如果是大量小报文的加解密(比如每秒几千次的接口验签),开销主要不是计算本身,而是对象分配和上下文创建。SM3计算时每次新建一个哈希对象、SM2验签时每次从DER重新解析公钥,都会频繁触发GC。优化方案是:对于无状态的SM3/SM4操作,可以复用对象,只要保证访问互斥;对SM2密钥解析,建议在服务启动时把所有需要的公钥/私钥解析成内部对象并缓存,验签时直接传入缓存引用。

如果是大文件或大数据流的场景,瓶颈则在字节拷贝上。C#中Array.CopyBuffer.BlockCopy是性能较好的选择,尽量使用Span<byte>Memory<byte>来避免中间字节数组的重复分配。在.NET 6+中,把SM4的加密结果直接写入Span<byte>可以大幅减少GC压力。实测下来,同样的SM4-CBC加密逻辑,用Span<byte>重写后吞吐量大约能提升20%到30%,这个优化成本很低,值得做。

提示:性能优化时要遵循先测量再优化的原则,不要凭直觉改代码。用BenchmarkDotNet对加解密、摘要、验签分别做基准测试,找出真正的热点再去优化,否则很容易浪费时间在无关痛痒的细节上。

最后说点实在的

三套算法全部跑通并和Java、JS端完成互操作测试之后,我最大的体会是:国密算法本身并不神秘,数学原理和实现难度也就是SHA-256加上一个ECC的量级,真正在项目里耗费时间的永远是格式、编码、填充这些“细节中的细节”。所以如果你现在正准备动手,我建议你开工前先做两件事:一是从国家标准文档里把测试向量摘出来写成单元测试,确保算法核心的正确性;二是提前和对接方约定好密钥格式、密文格式、字符编码、默认用户ID,把这些写进接口文档的协议说明里。这两步做到位,后面联调阶段能少熬好几个通宵。就分享到这,希望这篇能帮你少踩点坑。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 9:48:23

GENESIS2000菜单全解析:从入门到脚本自动化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:48:03

IEC61850与变电站程序化操作:原理、流程与工程落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:47:34

单端登录的后端实现:Redis互踢与Session/JWT方案全解析

简介&#xff1a;JSP开发中&#xff0c;同一账号同一时间仅允许登录一次是常见的账户安全需求&#xff0c;这份轻量级示例工程基于Session机制和过滤器实现&#xff0c;面向Java Web开发人员&#xff0c;适合需要快速掌握单点登录或会话唯一性控制的中初级学习者。rar压缩包共3…

作者头像 李华
网站建设 2026/9/7 9:46:20

从能跑到全能:腾讯云AI Skills实战与Agent工程化部署指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华