当前位置: 首页 > news >正文

Java实现ECC密钥对生成:secp256k1与secp256r1完整指南

1. 项目概述:为什么ECC 256k1和256r1如此重要?

如果你正在开发一个需要高安全性的Java应用,比如数字钱包、HTTPS证书签发系统,或者一个需要轻量级数字签名的物联网设备通信模块,那么椭圆曲线密码学(ECC)几乎是你绕不开的技术。而在众多椭圆曲线中,secp256k1secp256r1(也称为prime256v1)无疑是两颗最耀眼的明星。前者是比特币和以太坊等主流区块链的基石,后者则是TLS/SSL、X.509证书等互联网安全协议中的默认或推荐选择。简单来说,secp256k1是“区块链的守护神”,而secp256r1是“互联网通信的通行证”。

很多开发者,尤其是刚接触密码学的朋友,一看到“密钥对生成”就觉得要配置复杂的BouncyCastle库、处理晦涩的KeyPairGenerator参数,或者被各种NoSuchProviderExceptionInvalidAlgorithmParameterException搞得焦头烂额。网上的教程要么过于理论化,要么代码片段零散,缺少一个能“开箱即用”的完整解决方案。这篇文章的目的,就是帮你把这些障碍一扫而空。我将以一个多年密码学模块开发者的视角,带你直接切入核心,用最简洁、最健壮的代码,在纯Java环境中(包括JDK内置支持和BouncyCastle增强两种方式)快速生成这两种密钥对。无论你是为了应对面试中“如何实现非对称加密”的八股文,还是为了在真实项目中集成安全功能,这篇文章提供的代码和思路都能让你直接“抄作业”。

2. 核心概念与方案选型:知其然,更知其所以然

在动手写代码之前,我们有必要花几分钟搞清楚几个关键概念。这能帮你理解后续的代码为什么那么写,以及在遇到问题时如何排查。

2.1 ECC 256k1 vs 256r1:不仅仅是名字不同

首先,256代表的是密钥长度(比特位),这决定了其安全强度,大致相当于RSA 3072位的水平,但计算和存储开销小得多。k1r1则代表了曲线参数的不同定义方式:

  • secp256r1: 参数由伪随机数生成。它由NIST(美国国家标准与技术研究院)标准化,因此应用极其广泛,从你的浏览器访问HTTPS网站,到手机App的安全通信,背后很可能就是它在工作。在Java标准库中,它通常以别名prime256v1出现。
  • secp256k1: 参数由一个特定、可验证的简单数学公式定义(选择了一个较小的常数)。这种透明性使其在密码朋克和区块链社区中备受青睐,因为避免了“后门”嫌疑。比特币的中本聪选择了这条曲线,从而奠定了其江湖地位。

注意:在JDK 15及更早版本的标准SunEC提供程序中,并不直接支持secp256k1。这是很多新手踩的第一个大坑。你必须使用像BouncyCastle这样的第三方密码学提供程序(Provider)来生成secp256k1密钥对。从JDK 16开始,SunEC才加入了对其的支持,但为了代码的兼容性和可控性,使用BouncyCastle依然是更稳妥和通用的选择。

2.2 方案选型:JDK内置 vs BouncyCastle

基于上述背景,我们的实现方案就很清晰了:

  1. 对于secp256r1(prime256v1): 优先使用JDK内置支持。因为它被广泛支持且性能稳定,无需引入外部依赖。
  2. 对于secp256k1: 必须使用BouncyCastle库。它是一个功能强大且成熟的Java密码学库,提供了对大量非标准算法的支持。

为什么不全用BouncyCastle?当然可以,但对于secp256r1,使用JDK内置方案更轻量,减少不必要的依赖。在实际项目中,依赖管理是一项重要工作。

2.3 工具与环境准备

你需要准备以下环境:

  • JDK版本: 建议使用JDK 8或以上。文中代码在JDK 11和JDK 17下测试通过。
  • 构建工具: Maven或Gradle,用于管理BouncyCastle依赖。
  • 集成开发环境(IDE): IntelliJ IDEA、Eclipse或VS Code均可。

BouncyCastle依赖添加:

  • Maven: 在你的pom.xml文件中添加以下依赖。
    <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> <!-- 请使用最新稳定版 --> </dependency>
  • Gradle
    implementation 'org.bouncycastle:bcprov-jdk15on:1.70'

3. 核心代码实现:两种曲线的密钥对生成

下面,我将分步骤给出完整的、可运行的Java代码。代码包含了详细的注释,并遵循了生产级代码的健壮性要求(如异常处理、资源清理)。

3.1 生成 secp256r1 (prime256v1) 密钥对(使用JDK)

这是最直接的方法,利用了Java标准库的KeyPairGenerator

import java.security.*; import java.security.spec.ECGenParameterSpec; public class ECCKeyGeneratorJDK { /** * 使用JDK内置算法生成 secp256r1 (prime256v1) 密钥对 * @return 生成的密钥对 * @throws NoSuchAlgorithmException 如果当前环境不支持EC算法 * @throws InvalidAlgorithmParameterException 如果曲线参数错误 */ public static KeyPair generateSecp256r1KeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { // 1. 获取椭圆曲线(EC)的密钥对生成器实例 // 这里没有指定Provider,会使用JDK默认的(通常是SunEC) KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); // 2. 定义我们要使用的椭圆曲线参数:secp256r1,它在JDK中的标准名称是 "secp256r1" // 别名 "prime256v1" 同样可用,指向同一条曲线。 ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); // 3. 用指定的曲线参数初始化密钥对生成器 // 这里使用默认的随机数源(SecureRandom)。对于更高安全要求,可以自行初始化一个SecureRandom实例。 keyPairGenerator.initialize(ecSpec, new SecureRandom()); // 4. 生成密钥对 KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 5. (可选)打印密钥信息,用于调试 PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); System.out.println("=== Secp256r1 密钥生成成功 (JDK) ==="); System.out.println("算法: " + publicKey.getAlgorithm()); System.out.println("格式: " + publicKey.getFormat()); // 通常是 X.509 System.out.println("私钥格式: " + privateKey.getFormat()); // 通常是 PKCS#8 return keyPair; } public static void main(String[] args) { try { KeyPair keyPair = generateSecp256r1KeyPair(); // 在实际应用中,你可以从这里获取公钥和私钥的字节数组进行存储或传输 // byte[] publicKeyEncoded = keyPair.getPublic().getEncoded(); // byte[] privateKeyEncoded = keyPair.getPrivate().getEncoded(); } catch (Exception e) { e.printStackTrace(); System.err.println("生成secp256r1密钥对失败: " + e.getMessage()); } } }

代码解析与实操要点:

  • KeyPairGenerator.getInstance("EC")"EC"是椭圆曲线算法的通用名称。JDK会根据你后面提供的ECGenParameterSpec来具体化是哪条曲线。
  • ECGenParameterSpec("secp256r1"): 这是最关键的一步,指定了曲线。你也可以使用"prime256v1",它们是等价的。
  • SecureRandom: 密码学安全的随机数生成器是密钥安全的生命线。使用默认构造函数在绝大多数场景下是安全的。在极端安全要求的场景(如硬件安全模块HSM),可能需要配置特定的随机数源。
  • getEncoded(): 这个方法返回的是密钥的标准编码格式(如X.509用于公钥,PKCS#8用于私钥)。这是你持久化存储或网络传输密钥时最常用的形式。

3.2 生成 secp256k1 密钥对(使用BouncyCastle)

由于JDK标准库在旧版本中不支持secp256k1,我们必须借助BouncyCastle。

import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.*; public class ECCKeyGeneratorBC { // 静态代码块,确保BouncyCastle提供程序被注册到JVM中 static { Security.addProvider(new BouncyCastleProvider()); } /** * 使用BouncyCastle生成 secp256k1 密钥对 * @return 生成的密钥对 * @throws NoSuchAlgorithmException * @throws InvalidAlgorithmParameterException * @throws NoSuchProviderException 如果未找到BouncyCastle提供程序 */ public static KeyPair generateSecp256k1KeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException { // 1. 从BouncyCastle的表中获取secp256k1曲线的规范参数 ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1"); // 2. 获取密钥对生成器,并明确指定使用BouncyCastle提供程序 ("BC") KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC"); // 3. 使用BouncyCastle的参数规范初始化生成器 keyPairGenerator.initialize(ecSpec, new SecureRandom()); // 4. 生成密钥对 KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 5. (可选)打印密钥信息 PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); System.out.println("=== Secp256k1 密钥生成成功 (BouncyCastle) ==="); System.out.println("算法: " + publicKey.getAlgorithm()); System.out.println("格式: " + publicKey.getFormat()); System.out.println("私钥格式: " + privateKey.getFormat()); return keyPair; } public static void main(String[] args) { try { KeyPair keyPair = generateSecp256k1KeyPair(); // 同样,可以获取编码后的字节数组 // byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); // byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); } catch (Exception e) { e.printStackTrace(); System.err.println("生成secp256k1密钥对失败: " + e.getMessage()); // 常见失败原因:1. BouncyCastle JAR未正确引入;2. Provider未注册。 } } }

代码解析与实操要点:

  • Security.addProvider(new BouncyCastleProvider()): 这行代码必须在任何使用BouncyCastle功能的代码之前执行。通常放在静态代码块或应用初始化阶段。它向Java的Security框架注册了BouncyCastle这个“插件”。
  • KeyPairGenerator.getInstance("EC", "BC"): 注意这里的第二个参数"BC",它明确告诉JVM:“请使用BouncyCastle提供程序来获取EC密钥对生成器”。这是与JDK方式的核心区别。
  • ECNamedCurveTable.getParameterSpec("secp256k1"): BouncyCastle通过一个预定义的“表”来管理各种命名的椭圆曲线参数,这里我们直接按名称查找。
  • 异常处理NoSuchProviderException是使用BouncyCastle时特有的异常,如果Provider没有成功注册,就会抛出此异常。

3.3 统一的工具类封装(生产环境推荐)

在实际项目中,我们通常会将功能封装成一个工具类,提高代码的复用性和可维护性。下面是一个综合了两种生成方式,并增加了密钥序列化示例的工具类。

import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import java.security.*; import java.security.spec.ECGenParameterSpec; import java.util.Base64; /** * ECC密钥对生成工具类 * 支持 secp256r1 (JDK) 和 secp256k1 (BouncyCastle) */ public class ECCKeyPairUtil { static { // 确保BouncyCastle Provider被加载 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } } public enum CurveType { SECP256R1, // JDK内置支持 SECP256K1 // 需要BouncyCastle } /** * 生成指定椭圆曲线的密钥对 * * @param curveType 曲线类型 * @return 生成的密钥对 * @throws GeneralSecurityException 密码学相关异常 */ public static KeyPair generateKeyPair(CurveType curveType) throws GeneralSecurityException { KeyPairGenerator keyPairGenerator; AlgorithmParameterSpec paramSpec; switch (curveType) { case SECP256R1: // 使用JDK标准方式 keyPairGenerator = KeyPairGenerator.getInstance("EC"); paramSpec = new ECGenParameterSpec("secp256r1"); // 或 "prime256v1" break; case SECP256K1: // 使用BouncyCastle方式 keyPairGenerator = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); ECNamedCurveParameterSpec bcSpec = ECNamedCurveTable.getParameterSpec("secp256k1"); paramSpec = new org.bouncycastle.jce.spec.ECNamedCurveSpec( bcSpec.getName(), bcSpec.getCurve(), bcSpec.getG(), bcSpec.getN(), bcSpec.getH(), bcSpec.getSeed() ); // 注意:这里将BC的参数适配成了标准的AlgorithmParameterSpec // 另一种更BC原生的方式是直接使用 keyPairGenerator.initialize(bcSpec, secureRandom); break; default: throw new IllegalArgumentException("不支持的曲线类型: " + curveType); } SecureRandom secureRandom = new SecureRandom(); keyPairGenerator.initialize(paramSpec, secureRandom); return keyPairGenerator.generateKeyPair(); } /** * 将公钥转换为Base64编码的字符串(便于存储和传输) */ public static String publicKeyToBase64(PublicKey publicKey) { return Base64.getEncoder().encodeToString(publicKey.getEncoded()); } /** * 将私钥转换为Base64编码的字符串(务必安全存储!) */ public static String privateKeyToBase64(PrivateKey privateKey) { return Base64.getEncoder().encodeToString(privateKey.getEncoded()); } /** * 示例:生成并打印两种曲线的密钥对 */ public static void main(String[] args) { try { System.out.println("开始生成ECC密钥对...\n"); // 1. 生成 secp256r1 密钥对 KeyPair keyPairR1 = generateKeyPair(CurveType.SECP256R1); System.out.println("=== Secp256r1 密钥对 ==="); System.out.println("公钥 (Base64): " + publicKeyToBase64(keyPairR1.getPublic()).substring(0, 80) + "..."); System.out.println("私钥 (Base64): " + privateKeyToBase64(keyPairR1.getPrivate()).substring(0, 80) + "..."); System.out.println(); // 2. 生成 secp256k1 密钥对 KeyPair keyPairK1 = generateKeyPair(CurveType.SECP256K1); System.out.println("=== Secp256k1 密钥对 ==="); System.out.println("公钥 (Base64): " + publicKeyToBase64(keyPairK1.getPublic()).substring(0, 80) + "..."); System.out.println("私钥 (Base64): " + privateKeyToBase64(keyPairK1.getPrivate()).substring(0, 80) + "..."); } catch (GeneralSecurityException e) { System.err.println("密钥生成失败: " + e); e.printStackTrace(); } } }

工具类设计要点:

  • 枚举定义: 使用CurveType枚举清晰地定义了支持的曲线类型,避免了魔法字符串,提高了代码的可读性和可维护性。
  • 统一的接口generateKeyPair方法对外提供统一的接口,内部根据曲线类型选择不同的实现路径。这种设计模式使得调用方无需关心底层是JDK还是BouncyCastle。
  • 安全的Provider检查: 静态代码块中先检查BouncyCastle是否已注册,避免重复注册。
  • 密钥序列化: 提供了publicKeyToBase64privateKeyToBase64工具方法。Base64编码是将二进制密钥转换为文本格式的通用方法,便于存入数据库、配置文件或通过网络传输。切记,私钥的Base64字符串是高度敏感的,必须加密存储!
  • 参数适配: 在SECP256K1分支中,演示了如何将BouncyCastle的ECNamedCurveParameterSpec适配成标准的AlgorithmParameterSpec。这是一种更通用的写法。你也可以直接使用keyPairGenerator.initialize(bcSpec, secureRandom),因为BouncyCastle的KeyPairGenerator接受它自己的参数类型。

4. 常见问题、排查技巧与实战心得

即使代码看起来很简单,在实际集成到项目时,你依然可能会遇到一些“坑”。下面是我在多年开发中总结的一些典型问题和解决方案。

4.1 问题排查清单

问题现象可能原因解决方案
NoSuchAlgorithmException: EC KeyPairGenerator not available1. 运行环境(如某些精简版JRE)未包含SunEC或其他EC提供程序。
2. 在Android等特定平台上,算法名称可能不同。
1. 确保使用标准JDK/JRE。
2. 尝试明确指定Provider:KeyPairGenerator.getInstance("EC", "SunEC")
3. 在Android上,可能需要使用KeyPairGenerator.getInstance("EC", "BC”)KeyGenParameterSpec
InvalidAlgorithmParameterException传入的曲线名称字符串错误或不被当前Provider支持。1. 检查曲线名称拼写,secp256r1prime256v1是等价的,但必须完全正确。
2. 对于secp256k1,确保已正确引入并注册BouncyCastle,且使用"BC"Provider。
NoSuchProviderException: BCBouncyCastle的JAR包未在类路径中,或Provider未成功注册。1. 检查Maven/Gradle依赖是否引入成功,项目libs文件夹下是否有bcprov-*.jar
2. 确保在调用相关代码之前执行了Security.addProvider(new BouncyCastleProvider())。静态代码块是最佳位置。
生成的密钥对无法用于签名/验证可能使用了不兼容的Signature算法实例。ECC密钥通常与特定的签名算法搭配使用,如SHA256withECDSA。确保使用正确的算法:Signature sig = Signature.getInstance("SHA256withECDSA”);。对于secp256k1,在区块链中常用SHA256withECDSA或更特定的ECDSA
性能问题(生成速度慢)SecureRandom的初始化在首次使用时可能较慢,因为它需要收集足够的系统熵(随机性)。这是正常现象,首次生成后速度会恢复正常。对于需要频繁生成密钥的场景,可以考虑在应用启动时预先初始化一个SecureRandom实例并复用。切勿为了性能使用Random类替代SecureRandom,这是严重的安全漏洞。

4.2 实战心得与进阶技巧

  1. 密钥存储是重中之重: 生成密钥只是第一步。私钥绝不能以明文形式存储在任何地方(代码、配置文件、数据库日志)。生产环境中,必须:

    • 使用密钥库(Keystore): Java的KeyStore类(如JKS、PKCS12格式)可以密码保护私钥。
    • 硬件安全模块(HSM): 对于金融、区块链等高安全场景,私钥应在HSM中生成且永不导出。
    • 环境变量或密钥管理服务(KMS): 将加密后的私钥或获取私钥的凭证放在环境变量或专业的KMS(如AWS KMS, HashiCorp Vault)中。
  2. 明确你的使用场景

    • 如果你在做区块链开发: 生成secp256k1密钥对后,通常需要从中导出公钥的未压缩或压缩坐标,然后计算其对应的区块链地址(如比特币地址是公钥哈希的Base58Check编码)。这需要额外的编码库(如BitcoinJ或自己实现编码逻辑)。
    • 如果你在做TLS/SSL或一般数据签名: 使用secp256r1,并将生成的X509EncodedKeySpec(公钥)和PKCS8EncodedKeySpec(私钥)妥善保存,用于初始化Signature对象进行签名和验证。
  3. JDK版本兼容性: 从JDK 16开始,SunEC原生支持了secp256k1。你可以通过KeyPairGenerator.getInstance(“EC”).initialize(new ECGenParameterSpec(“secp256k1”))来尝试。但为了代码在JDK 8/11等主流LTS版本上能稳定运行,坚持使用BouncyCastle方案是更兼容、更明确的选择

  4. 测试!测试!测试!: 编写单元测试,验证生成的密钥对是否能成功用于一次完整的签名和验证流程。这是检验密钥是否可用的金标准。

    // 简化的测试代码片段 KeyPair keyPair = generateKeyPair(CurveType.SECP256R1); Signature signer = Signature.getInstance("SHA256withECDSA"); signer.initSign(keyPair.getPrivate()); signer.update("测试数据".getBytes()); byte[] signature = signer.sign(); Signature verifier = Signature.getInstance("SHA256withECDSA"); verifier.initVerify(keyPair.getPublic()); verifier.update("测试数据".getBytes()); boolean isValid = verifier.verify(signature); System.out.println("签名验证结果: " + isValid); // 应该输出 true
  5. 依赖管理: 确保团队所有成员和构建服务器使用的BouncyCastle版本一致,避免因版本差异导致的奇怪问题。在pom.xmlbuild.gradle中固定版本号。

5. 从密钥生成到实际应用:一个简化的签名示例

为了让你更清楚生成的密钥对如何被使用,我们来看一个完整的、使用secp256r1密钥对进行数据签名和验证的示例。这个模式可以平移到secp256k1

import java.security.*; import java.util.Base64; public class ECCSignatureDemo { public static void main(String[] args) throws Exception { // 1. 生成密钥对 (使用之前工具类中的方法,这里简写) KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); kpg.initialize(new java.security.spec.ECGenParameterSpec("secp256r1")); KeyPair keyPair = kpg.generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic(); String originalData = "这是一条需要确保完整性和来源的重要消息。"; // 2. 签名过程 System.out.println("=== 签名过程 ==="); Signature ecdsaSign = Signature.getInstance("SHA256withECDSA"); ecdsaSign.initSign(privateKey); ecdsaSign.update(originalData.getBytes("UTF-8")); byte[] digitalSignature = ecdsaSign.sign(); String signatureB64 = Base64.getEncoder().encodeToString(digitalSignature); System.out.println("原始数据: " + originalData); System.out.println("数字签名 (Base64): " + signatureB64); // 3. 验证过程 System.out.println("\n=== 验证过程 ==="); // 模拟接收方:拥有原始数据、签名和发送者的公钥 Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA"); ecdsaVerify.initVerify(publicKey); ecdsaVerify.update(originalData.getBytes("UTF-8")); boolean isVerified = ecdsaVerify.verify(digitalSignature); // 使用字节数组验证 // boolean isVerified2 = ecdsaVerify.verify(Base64.getDecoder().decode(signatureB64)); // 使用Base64字符串验证 if (isVerified) { System.out.println("验证成功!数据完整且来源可信。"); } else { System.out.println("验证失败!数据可能被篡改或签名无效。"); } // 4. 尝试验证被篡改的数据 System.out.println("\n=== 测试篡改数据 ==="); String tamperedData = originalData + "(已被修改)"; ecdsaVerify.initVerify(publicKey); // 重新初始化验证器 ecdsaVerify.update(tamperedData.getBytes("UTF-8")); boolean isTamperedVerified = ecdsaVerify.verify(digitalSignature); System.out.println("验证篡改数据结果: " + isTamperedVerified + " (应为 false)"); } }

这个示例清晰地展示了从密钥生成到实际应用(签名/验证)的闭环。在真实项目中,originalData可能是合同的哈希值、交易数据或任何需要防篡改的信息。公钥可以公开分发,而私钥则必须由签名方严密保管。

最后,记住密码学实践的第一原则:不要自己发明加密算法或协议。使用像本文这样经过充分验证的库和标准算法,并严格遵循密钥管理的最佳实践。希望这份详尽的指南能让你在Java中处理ECC密钥时更加得心应手。

http://www.cnnetsun.cn/news/3098055.html

相关文章:

  • AI 时代大龄程序员的优势凸显:从技术执行者到系统编排者的历史性跃迁
  • AI Agent:智能体如何重塑我们的数字生活
  • 亦唐科技在人工智能领域的创新与应用:引领智能化时代的变革
  • yansongda/pay分布式支付架构深度解析:多平台安全集成实现原理
  • 第07篇:GPT / LLaMA 架构演进——从 GPT-1 到 LLaMA-3 的“黄金三角“
  • 083、DCNv3 在 YOLOv11 中的适配代码:分组可变形加多尺度机制的联合改进
  • OpenCore Legacy Patcher终极指南:4步解决老Mac显卡驱动与系统升级兼容性问题
  • VSCode扩展生态实战:Task与AI编程工具协同的5类高频插件组合
  • AI获客培训常见误区:从风口焦虑到长期运营
  • C++移动语义开发实践
  • C++线程同步实践指南
  • .数据库内核开发入门:从B+树到MVCC与SQL执行引擎的实现路径
  • C++内存池设计实践
  • CQRS模式在电商系统应用
  • 凋亡金标准直观验证!细胞凋亡 DNA Ladder 抽提试剂盒
  • 从研发效率看业务系统嵌入数据分析能力:如何避免一个功能变成数据工程
  • 深度共识:AI时代的四种人类姿态
  • AI 电动刨冰机智能功率 MOSFET 核心驱动方案
  • 小米穿戴表盘设计终极指南:无需代码打造个性化智能表盘
  • NGA论坛优化摸鱼体验:20+项功能全面提升你的论坛浏览效率
  • 企业文件防泄密用什么软件?推荐这3款成熟经过验证的产品
  • 互联网企业降本实操:地图 API 年付从 5 万降到 3.5 万,选型经验全分享
  • 教你从零搞懂推荐系统 —— 以及 Microsoft Recommenders 究竟怎么玩
  • Biotinyl-Preangiotensiongen (1-14) (human) ;Bio-DRVYIHPFHLVIHN
  • config.json 文件是固定名称,存储描述信息,比如需要的变量名称、描述等。下面是一个 completion 类型的插件配置文件示例,除了一些跟提示模板相关的配置,还有一些聊天的配置,如最大 t
  • 云康e家最新消息,资金减损核定方案公布。
  • 异步方法调用详解
  • 零食生产线爬坡转弯输送系统(双爬坡机+转弯机)选型指南
  • 透明质酸敷料批发商实力之选:四川昂宇医疗器械有限公司深度解析
  • WinBtrfs完全指南:在Windows系统上无缝访问Linux Btrfs文件系统