Java实现HmacSHA256加密:从接口签名翻车到工程实践
1. 从一个接口签名翻车现场说起去年帮一个做支付对接的朋友排查线上问题对方技术负责人甩过来一句“你们回调过来的签名我们验不过。”当时第一反应是密钥配错了结果两边把密钥贴出来一比对一模一样。再查参数拼接顺序也对。最后定位到问题出在签名算法本身——他们用Mac.getInstance(HmacSHA256)拿到实例之后直接doFinal了而我们在生成签名时对十六进制字符串做了小写处理他们那边用的是大写。就这么一个大小写差异导致验签失败排查了整整一个下午。这件事让我意识到HmacSHA256 这个算法看起来简单到不能再简单JDK 里几行代码就能跑通但真正落到生产环境里坑一点都不少。密钥怎么管理、输出格式怎么统一、时间戳怎么防重放、不同语言之间怎么对齐每一个环节都可能成为线上事故的导火索。这篇内容就围绕 Java 实现 HmacSHA256 加密这件事把我在实际项目里踩过的坑、总结出来的经验完整地梳理一遍。不管你是刚接触接口签名的新手还是已经用过但总觉得“哪里不太对”的老手应该都能从中找到一些有用的东西。核心关键词就一个HmacSHA256围绕它展开的密钥处理、编码转换、签名验签、安全加固都是实打实的工程问题。2. HmacSHA256 到底在做什么为什么接口签名都爱用它2.1 从“消息摘要”到“带密钥的摘要”先把这个算法的本质说清楚。SHA256 本身是一个哈希函数你给它任意长度的输入它输出一个 256 位32 字节的固定长度摘要。这个过程是单向的理论上无法从摘要反推原文。但单纯的 SHA256 有个致命问题任何人都可以对消息做哈希服务端拿到消息和哈希值没法判断这个消息是不是被篡改过因为攻击者可以同时改消息和哈希值。HMAC 的全称是 Hash-based Message Authentication Code基于哈希的消息认证码。它在哈希函数的基础上引入了一个密钥。只有持有相同密钥的双方才能计算出相同的摘要值。攻击者不知道密钥就无法伪造出合法的签名。这就是为什么接口签名场景几乎清一色选择 HMAC 系列算法——它同时解决了完整性校验和身份认证两个问题。用生活化的类比来说SHA256 就像给文件按了个指纹谁都能按HMAC 则是在按指纹之前先蘸了一种只有你和对方才有的特殊印泥按出来的指纹别人模仿不了。2.2 HmacSHA256 的运算流程拆解HMAC 的底层运算逻辑并不复杂RFC 2104 里定义得很清楚。以 HmacSHA256 为例核心步骤如下如果密钥长度超过 64 字节SHA256 的分组长度先用 SHA256 对密钥做一次哈希得到 32 字节的摘要作为实际密钥。如果密钥长度不足 64 字节在后面补零到 64 字节。将补零后的密钥与一个 64 字节的0x36序列做异或运算得到内层填充ipad。将 ipad 拼接到消息前面做一次 SHA256 哈希。将补零后的密钥与一个 64 字节的0x5c序列做异或运算得到外层填充opad。将 opad 拼接到上一步的哈希结果前面再做一次 SHA256 哈希得到最终结果。这个双重哈希的结构使得 HMAC 在面对长度扩展攻击时比单纯的SHA256(key message)要安全得多。很多人图省事直接拼接密钥和消息做哈希这种做法在安全上是不可取的。2.3 为什么是 SHA256 而不是 MD5 或 SHA1MD5 和 SHA1 都已经被证明存在碰撞攻击的可行性虽然 HMAC 结构本身对碰撞攻击有一定的抵抗能力但在新项目里继续使用 MD5 或 SHA1 作为 HMAC 的底层哈希已经不符合当前的安全基线要求。SHA256 目前没有已知的实用碰撞攻击性能也完全能满足绝大多数接口签名场景的需求。一次 HmacSHA256 运算在普通服务器上耗时通常在微秒级别即使每秒处理上万次签名也不构成瓶颈。3. JDK 原生实现从 Mac 实例到十六进制输出3.1 最核心的几行代码Java 标准库javax.crypto包已经内置了 HmacSHA256 的支持不需要引入任何第三方依赖。最精简的实现大概长这样import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; public class HmacSha256Demo { public static byte[] hmacSha256(byte[] key, byte[] data) throws Exception { Mac mac Mac.getInstance(HmacSHA256); SecretKeySpec secretKeySpec new SecretKeySpec(key, HmacSHA256); mac.init(secretKeySpec); return mac.doFinal(data); } }这段代码本身没有毛病但直接拿去用很快就会遇到几个问题返回的是byte[]怎么变成可传输的字符串密钥用String还是byte[]每次调用都getInstance会不会有性能问题下面逐个说。3.2 密钥的字节化处理别在编码上栽跟头SecretKeySpec的构造函数接收的是byte[]不是String。很多人在这一步随手写了key.getBytes()没指定字符集。这在本地测试时可能没问题因为操作系统默认字符集恰好和对方一致但一旦部署到不同环境的服务器上默认字符集不同算出来的签名就完全不一样了。正确的做法是显式指定字符集byte[] keyBytes key.getBytes(StandardCharsets.UTF_8);如果密钥本身是十六进制字符串或者 Base64 编码的那还需要先解码// 密钥是十六进制字符串的情况 byte[] keyBytes hexStringToByteArray(key); // 密钥是 Base64 字符串的情况 byte[] keyBytes Base64.getDecoder().decode(key);这里有个经验密钥的编码格式一定要和对接方书面确认清楚。是原始字符串、十六进制还是 Base64三种情况对应的字节数组完全不同算出来的签名自然也完全不同。我见过不止一次因为密钥编码理解不一致导致的联调失败。3.3 输出格式Hex 还是 Base64大写还是小写mac.doFinal()返回的是byte[]要变成可传输的字符串通常有两种选择十六进制Hex和 Base64。十六进制的好处是可读性强方便人工比对Base64 的好处是长度更短32 字节的摘要转成 Hex 是 64 个字符转成 Base64 只有 44 个字符。两者在安全性上没有本质区别。Hex 转换的实现public static String bytesToHex(byte[] bytes) { StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(String.format(%02x, b)); } return sb.toString(); }注意%02x是小写输出如果需要大写就用%02X。这个大小写问题就是我开头提到的那个翻车现场。在接口文档里一定要明确写清楚签名是 Hex 小写还是大写或者 Base64这种细节不写清楚联调时必然扯皮。Base64 转换更简单String sign Base64.getEncoder().encodeToString(mac.doFinal(data));但要注意 Base64 有标准 Base64 和 URL-safe Base64 的区别如果签名要放在 URL 参数里传输必须用 URL-safe 版本否则、/、这些字符在 URL 里会被转义导致对方收到的签名和实际签名不一致。3.4 Mac 实例的复用与线程安全Mac类不是线程安全的。如果你在多线程环境下共享一个Mac实例会出现计算结果错乱的问题。但每次调用都Mac.getInstance()再init()在高并发场景下会有一定的性能开销。实际项目中有两种常见做法每次新建实例简单直接适合并发量不高的场景。JIT 优化之后getInstance的开销其实没有想象中那么大。使用 ThreadLocal 缓存每个线程持有自己的Mac实例避免重复创建同时保证线程安全。private static final ThreadLocalMac MAC_THREAD_LOCAL ThreadLocal.withInitial(() - { try { return Mac.getInstance(HmacSHA256); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(HmacSHA256 not available, e); } });使用 ThreadLocal 时要注意在线程池环境下线程会被复用Mac实例的密钥状态需要通过init()重新设置。如果忘记重新init()可能会用上一个请求的密钥去计算当前请求的签名这是非常危险的。4. 接口签名场景下的完整工程实践4.1 待签名串的拼接规则设计HmacSHA256 本身只负责对一段字节做运算但实际接口签名中真正容易出问题的是“对什么内容做签名”。常见的做法是把请求参数按一定规则拼接成一个字符串再对这个字符串做 HMAC 运算。拼接规则通常包含以下要素要素说明常见处理方式请求参数业务参数键值对按 key 字典序排序后拼接时间戳防止重放攻击秒级或毫秒级双方约定随机数增加签名随机性UUID 或随机字符串请求路径区分不同接口部分方案会纳入签名请求方法GET/POST 等部分方案会纳入签名一个典型的拼接示例appIdabc123noncexyz789timestamp1700000000bizParamvalue拼接时要注意几个细节空值参数是否参与签名、参数值是否需要 URL 编码、中文参数用什么字符集编码。这些规则必须在接口文档里写死不能有歧义。4.2 时间戳与随机数的防重放设计单纯靠 HmacSHA256 只能保证消息没被篡改但无法防止攻击者把截获的请求原封不动地重放一次。所以签名方案里通常会加入时间戳和随机数。服务端收到请求后先校验时间戳与当前时间的差值是否在允许范围内比如 5 分钟超出则直接拒绝。然后再检查随机数是否在有效期内已经被使用过如果用过则拒绝。这两个检查配合起来就能有效防止重放攻击。这里有个实操中的坑时间戳的精度要和对方对齐。你用秒级对方用毫秒级算出来的签名串就不一样。另外服务器之间的时间同步也很重要如果两边服务器时间差了十几分钟时间戳校验就会大面积失败。建议在接口文档里明确写清楚时间戳的格式和允许的偏差范围。4.3 签名生成与验签的完整代码下面是一个相对完整的签名工具类实现涵盖了参数排序、拼接、HMAC 运算和 Hex 输出import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.TreeMap; public class SignUtil { public static String generateSign(MapString, String params, String secretKey) throws Exception { // 1. 按 key 字典序排序 TreeMapString, String sortedParams new TreeMap(params); // 2. 拼接成待签名串 StringBuilder sb new StringBuilder(); for (Map.EntryString, String entry : sortedParams.entrySet()) { if (entry.getValue() ! null !entry.getValue().isEmpty()) { sb.append(entry.getKey()).append().append(entry.getValue()).append(); } } // 去掉末尾的 if (sb.length() 0) { sb.deleteCharAt(sb.length() - 1); } // 3. HMAC-SHA256 运算 Mac mac Mac.getInstance(HmacSHA256); SecretKeySpec keySpec new SecretKeySpec( secretKey.getBytes(StandardCharsets.UTF_8), HmacSHA256); mac.init(keySpec); byte[] rawHmac mac.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8)); // 4. 转十六进制小写 return bytesToHex(rawHmac); } private static String bytesToHex(byte[] bytes) { StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(String.format(%02x, b)); } return sb.toString(); } }验签的逻辑和生成签名完全一样服务端用相同的密钥和相同的拼接规则对收到的参数重新计算一次签名然后和请求中携带的签名做比对。比对时要注意使用常量时间比较避免时序攻击。Java 中可以用MessageDigest.isEqual()boolean valid MessageDigest.isEqual( expectedSign.getBytes(StandardCharsets.UTF_8), actualSign.getBytes(StandardCharsets.UTF_8));普通的String.equals()在发现第一个不匹配的字符时就返回理论上可以通过测量比较耗时来逐字节猜测签名值。虽然在实际网络环境下这种攻击难度很大但用MessageDigest.isEqual()是更好的习惯。5. 那些年我踩过的 HmacSHA256 坑5.1 密钥编码不一致导致的“玄学”验签失败这是最常见的一类问题。双方约定了一个密钥字符串比如MySecretKey2024但一方把它当作原始字符串直接getBytes()另一方把它当作十六进制字符串先做了解码。结果就是两边算出来的签名完全不同但双方都觉得自己没写错。排查这类问题的思路很简单把双方实际参与 HMAC 运算的字节数组打印出来比对。不要打印字符串因为字符串看起来一样字节数组可能完全不同。用Arrays.toString(keyBytes)输出一眼就能看出差异。5.2 参数拼接顺序不一致参数排序规则不统一是另一个高频问题。你用TreeMap按字典序排对方用HashMap的遍历顺序算出来的待签名串就不一样。字典序排序时还要注意大小写敏感问题appId和appid在字典序中的位置是不同的。建议在接口文档里明确写清楚“按参数名 ASCII 码从小到大排序”。5.3 URL 编码的时机不对如果参数值里包含特殊字符比如、、空格、中文在拼接待签名串之前是否要做 URL 编码双方必须达成一致。常见的做法有两种先对参数值做 URL 编码再拼接签名或者用原始值拼接签名、发送请求时再编码。这两种做法算出来的签名不同如果双方理解不一致必然失败。我的建议是签名用原始值传输用编码值。这样签名逻辑更清晰也避免了编码方式差异带来的干扰。但前提是双方都遵循这个约定。5.4 空值和 null 的处理分歧参数值为空字符串和参数不存在在很多语言里是两种不同的状态。Java 里null和就是两回事。拼接签名时空字符串参数是否参与拼接、null参数是否直接忽略这些规则不明确的话联调时就会反复出现“我这边算出来是对的你那边算出来是错的”这种情况。5.5 换行符和空格被意外引入这个问题比较隐蔽。有时候密钥是从配置文件里读取的文件末尾不小心带了一个换行符或者从环境变量读取时带了空格。这些不可见字符会参与 HMAC 运算导致签名不一致。排查时可以用key.trim()先做一次清理但更根本的做法是在配置加载阶段就做好校验确保密钥字符串没有多余字符。6. 性能与安全层面的进阶考量6.1 高并发下的性能表现HmacSHA256 的性能开销主要来自两次 SHA256 哈希运算。在普通 4 核服务器上单次运算耗时大约在 1 到 3 微秒之间。即使每秒需要处理 10 万次签名CPU 占用也完全在可接受范围内。真正可能成为瓶颈的是Mac.getInstance()的调用和SecretKeySpec的创建。如果 QPS 很高可以考虑用 ThreadLocal 缓存Mac实例或者使用对象池。但要注意缓存Mac实例时每次使用前必须重新init()设置密钥否则会出现密钥串用的问题。另外如果待签名串很长比如包含了大量业务数据哈希运算的耗时也会相应增加。这种情况下可以考虑只对关键字段做签名而不是对整个请求体做签名。6.2 密钥管理的基本要求密钥不能硬编码在代码里这是基本的安全常识。常见的做法包括通过环境变量注入代码里只读取环境变量。通过配置中心下发支持密钥轮换。使用密钥管理服务密钥不落地到应用服务器。密钥轮换时要注意平滑过渡新密钥启用后旧密钥需要保留一段时间因为可能还有在途请求用的是旧密钥签名。可以给密钥打上版本号签名串里带上版本信息服务端根据版本号选择对应的密钥验签。6.3 和不同语言对接时的注意事项Java 和 PHP、Python、Go 等语言对接 HmacSHA256 时算法本身的结果是一致的差异通常出在以下几个地方差异点Java其他语言常见情况默认字符集需显式指定 UTF-8Python3 默认 UTF-8PHP 需注意Hex 输出大小写取决于格式化方式各语言默认不同Base64 变体标准 Base64部分语言默认 URL-safe密钥类型byte[]部分语言接受字符串直接运算对接时的最佳实践是先用一组固定的测试向量密钥、消息、预期签名在两边分别跑一遍比对结果。如果结果一致说明基础运算没问题再去排查拼接规则等业务层面的差异。7. 写在最后的一些个人体会HmacSHA256 这个算法本身并不复杂JDK 也提供了开箱即用的支持。真正考验工程能力的是围绕它构建的那一套签名规范密钥怎么编码、参数怎么排序、空值怎么处理、输出用什么格式、时间戳怎么校验。这些细节看起来琐碎但每一个都可能成为线上事故的根源。我的经验是在接口对接的初期就把这些规则一条一条写进文档并且用测试用例固化下来。双方各写一个单元测试用同一组输入验证输出是否一致。这比联调时出了问题再回头排查要高效得多。另外签名失败时不要靠猜要把中间过程打印出来密钥的字节数组、待签名串的原始内容、HMAC 运算后的字节数组、最终输出的字符串。把这四个东西对齐了问题基本就定位到了。最怕的就是双方都只说“我这边没问题”却不肯把中间结果拿出来比对。最后提一句如果你的项目还在用 MD5 做签名趁早换成 HmacSHA256。迁移成本并不高但安全性的提升是实实在在的。

相关新闻

沙盒隔离实战:Sandboxie安装配置与疑难排障全攻略

沙盒隔离实战:Sandboxie安装配置与疑难排障全攻略

1. 沙盒到底是什么:先搞懂Sandboxie隔离的逻辑很多人第一次听说沙盒(Sandbox),脑子里冒出来的画面是:一个装着程序的大盒子里,程序在里面怎么折腾都出不来,关掉盒子之后一切恢复原样。这个比喻方…

2026/10/17 21:23:32 阅读更多 →
卡尔曼滤波实战:从状态空间模型到Q/R参数调优

卡尔曼滤波实战:从状态空间模型到Q/R参数调优

做时序分析的人,迟早会撞上卡尔曼滤波。这个算法在状态空间模型里几乎就是“下篇”的代名词:上一篇文章我们把状态空间模型的框架搭好,状态方程、观测方程都摆出来了,接下来最核心的问题就是——怎么从一堆带噪声的观测里&#xf…

2026/10/17 21:23:32 阅读更多 →
韩语词汇表构建与评测:从词表设计到分词、拼写检查与检索召回

韩语词汇表构建与评测:从词表设计到分词、拼写检查与检索召回

简介:这份韩语词汇表资源面向韩语学习者、语言研究者及需要构建韩语词典数据的开发者,提供从原始词条到结构化词典的完整数据集合。资源包共5个文件,以json词典数据、txt单词列表和md说明文档为主,压缩包约28.37MB,jso…

2026/10/17 21:22:31 阅读更多 →

最新新闻

3ds Max森林插件Forest Pack 7散布引擎原理与性能优化实战

3ds Max森林插件Forest Pack 7散布引擎原理与性能优化实战

简介:这份PDF是3ds Max森林树木植物散布插件Forest Pack 7的官方帮助文档,面向建筑可视化、游戏开发与影视特效领域的三维设计师,尤其适合需要搭建大规模自然或城市景观的中高级用户。Forest Pack作为最流行的3ds Max植物散布插件&#xff0c…

2026/10/17 22:05:06 阅读更多 →
为什么关键词工具总是慢两周?new-term-radar:AI新词雷达完全指南

为什么关键词工具总是慢两周?new-term-radar:AI新词雷达完全指南

【免费下载链接】new-term-radar Daily radar for new AI terms: names of new models, products and repos, caught on X, Hugging Face, GitHub and sitemaps before keyword tools list them. 项目地址: https://gitcode.com/gh_mirrors/ne/new-term-radar 点击查…

2026/10/17 22:05:06 阅读更多 →
移动端调试工具实战:vConsole、Eruda与Spy-Debugger选型指南

移动端调试工具实战:vConsole、Eruda与Spy-Debugger选型指南

做H5/WAP开发的人应该都有过这种体验:桌面上把页面调得妥妥当当,一扔到手机里就“原形毕露”,报错信息看不到、接口状态不知道、元素位置对不上。PC端一个F12能解决的事,到移动端要同时面对浏览器兼容、真机差异、WebView内核差异…

2026/10/17 22:05:06 阅读更多 →
Ajax请求中hide与show时序控制:浏览器渲染机制与四种解决方案

Ajax请求中hide与show时序控制:浏览器渲染机制与四种解决方案

1. 从一个“按钮点下去没反应”的经典场景说起做前端开发的人,几乎都遇到过这样一种情况:用户点了一个按钮,你希望先把某个区域隐藏起来,等数据从服务端回来之后再显示新的内容。逻辑上看起来天衣无缝——先hide(),再发…

2026/10/17 22:05:06 阅读更多 →
FrankenPHP 原生支持 Windows:PHP 应用服务器新选择

FrankenPHP 原生支持 Windows:PHP 应用服务器新选择

FrankenPHP 原生支持 Windows 了。这句话如果放在两年前,可能只是又一个热门项目的更新日志,但真在 Windows 上折腾过 FrankenPHP 的开发者,应该知道其中的分量。这是一个基于 Caddy 的 PHP 应用服务器,内置 PHP-FPM,可…

2026/10/17 22:05:06 阅读更多 →
Flutter for OpenHarmony 实战:将番茄钟与习惯打卡融合的效率助手

Flutter for OpenHarmony 实战:将番茄钟与习惯打卡融合的效率助手

我一直觉得,效率工具最大的问题不是功能不够多,而是功能之间彼此孤立。你打开一个番茄钟应用,专注完了关掉,再打开一个习惯打卡应用,记录一笔,数据零零散散,最后根本形成不了正向反馈。这次用 F…

2026/10/17 22:04:04 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/16 14:23:36 阅读更多 →