JavaScript MD5实战:从哈希原理到文件校验与接口签名

看了标题就知道,这文章大概率是在处理一个非常实际的前端痛点:我怎么知道用户上传的文件没被改过、接口请求的参数在传输过程中有没有被动手脚、或者说某些业务字段能不能对得上号。MD5在这个领域是老面孔了,虽然这几年它的名声不太好,但如果你把它用在“完整性验证”这个正确的场景里,它的效率、兼容性和上手成本,依然是很多工具比不了的。这篇文章我就结合自己多年的开发经验,从原理到实操,把JavaScript里折腾MD5这件事讲透。

1. MD5到底是什么,为何前端总是绕不开

1.1 一句话说清MD5的原理

先花一分钟把基础对齐,因为网上太多文章把MD5包装得神乎其神。

MD5,全称是Message Digest Algorithm 5,是一种哈希算法。所谓哈希,你可以把它想象成“数据榨汁机”:任意长度的输入,无论是一串字符、一个JSON对象、还是一个几百MB的文件,经过它处理后,都会固定输出一个128位(16字节)的摘要,通常表示成32位的十六进制字符串,例如9e107d9d372bb6826bd81d3542a419d6。

这个摘要具有两个关键特性:

  • 确定性:同一个输入,无论跑多少次,得到的摘要是完全一样的。这是做完整性校验的基础。
  • 雪崩效应:输入只要改动哪怕一个标点符号、一个空格,输出的摘要就会变得面目全非,完全找不到规律。

这两个特性结合起来,就给了我们一个能力:给数据生成一枚“指纹”。发送方算出指纹发给接收方,接收方收到数据后再算一次指纹,如果两次指纹一致,那基本能断定数据是完整的;如果不一致,说明数据在中间某个环节被动过了。

1.2 前端为什么需要MD5:三个最常见的业务场景

很多初中级开发会把MD5单纯等同于“加密”,这是理解偏差的重灾区。在我实际接触的项目里,前端用MD5,无外乎以下三类场景。

第一类,表单与请求参数的完整性校验。比如一个涉及资金操作的接口,前端把订单金额、用户ID、商品编号等参数收集好后打包发给后端,如果在传输过程中参数被篡改,后端拿到的就是非法数据。于是前端把这些参数拼成一个字符串,连同约定的密钥一起做MD5,生成一个签名串,请求发出后后端用相同的规则验签,不一致就直接拒绝。

第二类,上传文件的唯一性校验。用户上传一张图片,上传前前端先计算文件的MD5,服务端可以拿这个值判断文件是否已经存在。如果库里已经有相同MD5的文件,就可以直接引用已有文件,省掉一次重复的上传流量。这个做法在网盘、对象存储场景里很常用,术语叫去重。

第三类,大文件分片上传时的完整性兜底。文件被切成很多片分别上传,服务端需要确认合并后的文件和用户原始文件完全一致。这时候给整个文件算一个整体MD5,合并后校验,就能快速暴露哪一片传输出了问题。

1.3 必须认清的边界:MD5不是万能保险箱

MD5的争议主要来自它在密码存储领域的滥用。MD5生成速度快,容易被暴力穷举,而且针对MD5的彩虹表(预计算的大字典)非常成熟,弱口令一秒就能反查出来。最重要的是,MD5在学术上已经被证明存在碰撞攻击——即能够构造出两个不同的输入,产生相同的MD5摘要,这在理论上被广泛用于恶意文件的伪装。

所以我对MD5的态度很简单:

场景是否推荐
普通数据的完整性校验推荐
接口请求签名(配合密钥)可以,但要明白它防的不是高水平攻击者
用户密码存储绝不推荐,请用成熟的KDF算法(如bcrypt)
对安全要求极高的电子签名/证书强烈不推荐

这句话请刻在脑子里:MD5是“校验工具”,不是“安全保险箱”。它的价值是让你快速发现“数据坏了”,而不是让你“藏住数据”。

2. 动手前的准备:工具选型与工程引入方式

2.1 浏览器环境主力方案:CryptoJS

前端几乎所有涉及MD5的代码,都是基于开源库CryptoJS完成的。它在GitHub上有大量使用者,API稳定,支持各种WordArray类型的转换,浏览器和Node环境都能跑。

引入方式我经历过好几个时期,最早直接在HTML里写script标签,后来习惯了模块化管理。现在的主流做法是:

npm install crypto-js

然后在模块化的项目里按需导入:

import CryptoJS from 'crypto-js'; // 或按需引入MD5部分,减少打包体积 import MD5 from 'crypto-js/md5';

如果项目比较老,没有模块化环境,直接在公开CDN上引入核心文件也行。但这里要提醒一个低调又关键的细节:CDN资源有被劫持的风险,哪怕概率极低,用于生产环境的静态资源最好还是下载到本地,或者配合SRI(子资源完整性)校验。

注意:我见过有的项目为了一个MD5方法,手动引入一整个crypto-js的完整包,打包体积白白增大了几十KB。按需引入crypto-js/md5这个子模块,体积会小很多,加载速度差异肉眼可见。

2.2 Node.js环境:内置crypto模块就够了

如果跑在Node环境里,比如构建脚本、服务端校验逻辑的联调,根本不需要装第三方库。Node自带的crypto模块就封装了MD5算法:

const crypto = require('crypto'); function md5(str) { return crypto.createHash('md5').update(str, 'utf8').digest('hex'); } console.log(md5('hello')); // 5d41402abc4b2a76b9719d911017c592

要注意update()方法里的编码参数。如果你前端的字符串默认是UTF-8,Node端也必须指定utf8,否则遇到中文时摘要对不上,这个问题我们后面专门讲。

2.3 强烈建议先封装一个工具函数

不管你用哪种方案,我都会建议在项目里预先封装一个统一的MD5入口,而不是在业务代码里到处直接调用库API。

原因很简单:调用方不关心你背后用的是CryptoJS还是Node内置模块,也不希望将来某个安全升级要求把MD5换成SHA-256时,满项目到处找代码改。

我的习惯是在utils目录下建一个hash.js:

import MD5 from 'crypto-js/md5'; /** * 对字符串进行MD5哈希 * @param {string} str 需要哈希的字符串(UTF-8) * @param {boolean} uppercase 是否返回大写(默认小写) */ export function md5(str, uppercase = false) { const result = MD5(String(str)).toString(); return uppercase ? result.toUpperCase() : result; }

函数体只有三四行,但它隔离了库的细节。以后如果项目要求统一升级算法,你只需要改这一个文件,业务代码零改动,这就是好的工程习惯。

3. 实操:从字符串到文件再到签名串的完整落地

3.1 第一版:浏览器里给字符串算MD5

假设现在有个场景:用户在注册页输入密码,出于某些历史遗留原因,前端需要把密码做一次MD5再传给后端(如果新项目,我不建议这样处理密码,但存量系统里大量存在,前端人员往往没有选择权)。

页面引入CryptoJS后,核心就一行:

const hash = CryptoJS.MD5('用户输入的密码').toString();

看起来简单,但有几个细节值得说:

  • .toString()默认输出小写32位字符串。如果后端约定的是大写,就需要调用.toString().toUpperCase()。很多前后端联调对不上号,头号原因就是这个大小写约定不一致。
  • MD5的入参内部会被当成字符串处理。如果你传入的是一个大对象,它会先做隐式字符串转换,结果可能不是你想要的,所以最稳妥的是先JSON.stringify再哈希。

我实际开发时会在控制台先验算几个固定值。比如空字符串的MD5是d41d8cd98f00b204e9800998ecf8427e,"hello"是5d41402abc4b2a76b9719d911017c592。先拿这两个值跑通链路,再叠加业务逻辑,排查问题会省力很多。

3.2 进阶:大文件分块计算完整性的完整方案

这是性能敏感场景里最实用的一段。

如果你需要给一个2GB的视频文件算MD5,最直接的做法是把整个文件读进内存再计算——不好意思,浏览器会直接卡死甚至崩溃。正确姿势是分块读取,增量更新哈希。

CryptoJS的MD5对象支持增量模式,核心用法是多次调用update(),最后统一finalize()。前端配合FileReader或Blob.arrayBuffer(),可以实现流式的哈希计算:

async function computeFileMd5(file, chunkSize = 2 * 1024 * 1024) { const chunkCount = Math.ceil(file.size / chunkSize); let currentChunk = 0; // 创建一个可增量更新的MD5对象 const md5Instance = CryptoJS.algo.MD5.create(); while (currentChunk < chunkCount) { const start = currentChunk * chunkSize; const end = Math.min(start + chunkSize, file.size); // 用Blob.slice取出当前分片 const chunk = file.slice(start, end); const buffer = await chunk.arrayBuffer(); // 将ArrayBuffer转成WordArray后喂给MD5实例 const wordArray = CryptoJS.lib.WordArray.create(buffer); md5Instance.update(wordArray); currentChunk++; // 这里可以回调进度: (currentChunk / chunkCount * 100).toFixed(2) } return md5Instance.finalize().toString(); }

这段代码有几个关键细节:

  • chunkSize选2MB是一个实践经验。太小的块会导致切片次数过多、循环等待时间变长;太大的块又会占用较大内存。实测下来2MB~4MB之间体验比较稳。
  • CryptoJS.lib.WordArray.create(buffer)在绝大多数浏览器上能正常工作,但如果你遇到哈希值和服务端对不上的情况,首先要怀疑的就是这块ArrayBuffer到WordArray的字节序处理。某些版本的CryptoJS对大端数据解析有兼容性差别,这是网上很少有人讲但实际容易踩的坑。
  • 如果你觉得CryptoJS增量模式太绕,还有一个更原生、更稳的思路:用crypto.subtle.digest('SHA-256', buffer)分块算——但SubtleCrypto不支持增量模式,你需要先合并完整数据或做分层哈希,本质上绕回了大内存问题。所以大文件场景,目前CryptoJS增量更新仍然是我推荐首选。

3.3 集成场景:接口请求签名到底怎么拼

签名场景是MD5在前端最高频的用法,也是坑最深的地方。之前在某电商项目里,遇到过一个很典型的问题:前端怎么算签名,后端都验不过。后来排查发现,双方对参数字典序排序的处理不一致。

一个通用的签名流程长这样。

**第一步,收集参与签名的参数。**通常是业务参数里的全部非空字段,排除掉sign本身和sign_type这类签名相关字段。

**第二步,字典序排序。**按键名的ASCII码升序排列。

**第三步,拼接字符串。**排好序的参数对拼成key1=value1&key2=value2。

**第四步,拼接密钥。**在字符串末尾追加约定的密钥,比如&key=secretKey。

**第五步,MD5。**对拼接结果取MD5,得到签名串。

举个简化的例子:

const params = { amount: '100.00', orderId: 'ORDER202501010001', userName: '张三' }; // 1. 过滤空值并按键名排序 const sortedKeys = Object.keys(params) .filter(key => params[key] !== '' && params[key] != null) .sort(); // 2. 拼接 key=value 串 const queryString = sortedKeys .map(key => `${key}=${params[key]}`) .join('&'); // 3. 拼接密钥,密钥只存在于后端配置,不要明文硬编码(前端做不到绝对安全,至少混淆) const signStr = `${queryString}&key=your_secret_key`; // 4. 计算MD5 const sign = md5(signStr); // 或者大写

为什么必须排序?因为请求参数的键顺序在URL拼接、表单提交、JSON解析的各种环节中是不稳定的,如果不约定排序,同一个业务请求在不同端拼出来的字符串顺序就可能不同,MD5结果自然千差万别。排完序,前后端才能得到同一份待签名串。

至于密钥明文出现在前端这种看似“不安全”的做法,要分开看:签名防的是参数在传输过程中被第三方篡改,而不是防用户自己逆向源码。前端代码本来就没有绝对秘密,所以敏感业务的防篡改还是需要后端二次签名或HTTPS+WS签名机制兜底的。

4. 我踩过的坑与排查套路(含速查表)

4.1 中文编码不一致导致签名死活对不上

这是签名联调时最高频的问题,没有之一。

前端浏览器字符串默认按UTF-8编码,但某些后端框架、某些桌面工具在组装签名串时,可能用的是平台默认编码(比如GBK)。同一个“张三”字符,UTF-8和GBK编码出的字节完全不同,MD5摘要自然对不上。

排查方法很简单:让后端把收到的待签名串原样打出来,前端也把拼接前的字符串打出来,逐字节对比。两边显示的字符串可能肉眼看是一模一样的,但十六进制字节码不同,一眼就能定位到编码问题。解决方案就是以某一端约定为准,例如统一切换到UTF-8。

4.2 WordArray与ArrayBuffer字节序的隐形坑

我在3.2节提过这个坑,这里展开讲。CryptoJS内部用WordArray表示数据,它把数据当成32位字的序列处理。而浏览器的ArrayBuffer是字节序列。当你直接WordArray.create(buffer)时,它按内存连续字节复制,在x86小端平台上,很多场景是正常的;但如果你混合使用了Uint8Array的视图、或者从Node端传入数据,容易出现高低位颠倒、哈希结果与预期不一致。

我的建议是:浏览器端大文件哈希,优先用Blob分片加WordArray.create的方式,做好端到端自测;一旦发现与服务端不一致,果断改用Node端或后端另行计算交叉验证。

4.3 CDN资源挂掉导致线上白屏

有次在某个后台管理系统里,某同事直接在HTML里外链了公开CDN的CryptoJS,结果某天那个CDN域名出了故障,整个页面所有和哈希相关的初始化逻辑全挂了,用户页面卡在登录状态识别阶段。

从那以后,凡是我经手的项目,第三方库一律走本地打包,或者至少把关键资源下载放进项目静态目录。开源社区的CDN服务是很方便,但生产环境稳定性永远要自己掌控。

4.4 常见问题速查表

直接给一张表,遇到问题先对照自查:

现象常见原因解决方向
同一字符串两次计算,结果不同输入里混入了不可见字符(换行、BOM头)排查字符串来源,必要时先trim
前端结果与后端结果不一致编码不一致、大小写约定不同、签名串拼接顺序不同统一定UTF-8、确认大小写约定、严格按字典序排序
大文件MD5算到一半页面卡死把整个文件读进内存了改用分块增量更新
上传文件MD5与服务端不同文件流被中间环节改变(如代理、压缩)对比文件大小,再确认分块逻辑是否正确
签名在测试环境正常,线上偶尔失败测试与生产环境密钥配置不一致检查环境变量与密钥配置
CryptoJS引入后报全局对象错误模块化环境混用了CommonJS和ESModule统一用import方式,或检查构建配置

这些坑我在不同项目里都实打实遇到过,其中编码和大小写这两个看似不起眼的点,居然频率最高,也最容易让前后端吵起来。以后遇到对不上,先别急着质疑对方的算法,按这个表排查一遍再说。

5. 一个容易被忽略的优化方向与个人体会

如果你只是在浏览器端偶尔对短字符串做一次MD5,那前面这些已经够了。但如果你负责的系统里哈希计算非常频繁——比如每个文件上传都要算、每个请求都要签名——有两个性能方向值得投入。

第一个是Web Worker。MD5计算虽然比现代对称加密算法快,但在处理大文件时依然是同步计算,会阻塞主线程。把哈希计算扔给Web Worker,主线程保持画面流畅,进度条照常渲染,用户体验提升非常明显。

第二个是后端联合计算。某些超大体量文件,前端算一次整体MD5可能要好几十秒,这时候可以考虑前端只负责算分片Hash,由后端把分片Hash列表合并后再统一做完整性校验。前端代码量不多,但架构上更合理,也避免了用户在页面上等太久。

最后分享一点我个人的工程体会:MD5和SHA-256的关系,不是“谁替代谁”那么简单,而是“谁适配什么场景”。完整性校验、缓存去重、签名拼接这类不涉及对抗攻击者的场景,MD5足够高效;密码存储、证书签名、防恶意篡改对抗,则必须用更强的算法。把自己的工具函数封装好,将来在md5()旁边加一个sha256()就是一次顺手的事,没必要死守某一种算法。

在实际项目里,我更愿意把MD5当作一个“忠心耿耿的保安”,它能帮你在日常业务里做很多事情;但你要知道它挡不住真正的“特种兵”,所以别把它派去守金库。搞清楚这一点,JavaScript里的MD5就是你工具箱里那把顺手的小螺丝刀,随取随用,不用有负担。

相关新闻

LongCat-2.5-Preview上架:接口格式比参数更值得关注

LongCat-2.5-Preview上架:接口格式比参数更值得关注

DMXAPI 上架 LongCat-2.5-Preview 的消息传开后&#xff0c;群里讨论最多的是参数规模、上下文窗口、跑分成绩&#xff0c;但我盯着控制台看了半天&#xff0c;觉得这次真正值得聊的&#xff0c;反而是同一个模型给了两套接口格式这件事。如果你只用过 OpenAI 兼容接口&#xf…

2026/10/20 6:59:49 阅读更多 →
C/C++内存管理底层剖析:从malloc到智能指针的硬核指南

C/C++内存管理底层剖析:从malloc到智能指针的硬核指南

写这个系列已经到第十一篇了&#xff0c;前面聊了语法、模板、容器&#xff0c;但真正让 C/C 区别于其他语言、让人又爱又恨的&#xff0c;还是内存管理。标题我起的是“全站最全面的C/C内存管理的底层剖析与硬核指南”&#xff0c;说实话“最全面”这种话有点夸张&#xff0c;…

2026/10/20 6:59:49 阅读更多 →
SpringBoot + PageHelper + Vue 分页实战:从后端到前端的完整链路

SpringBoot + PageHelper + Vue 分页实战:从后端到前端的完整链路

1. 为什么分页这件事值得单独拎出来聊做后台管理系统的人都有一个共识&#xff1a;列表页是绕不过去的坎&#xff0c;而分页是列表页的命门。我见过太多项目&#xff0c;前端把数据一股脑全查出来再在浏览器里切片&#xff0c;数据量小的时候看着挺美&#xff0c;一旦表里过了几…

2026/10/20 6:59:49 阅读更多 →

最新新闻

Python数据分析与可视化源码包实战:从数据清洗到图表输出的完整链路拆解

Python数据分析与可视化源码包实战:从数据清洗到图表输出的完整链路拆解

简介&#xff1a;这份资源面向希望系统掌握Python数据分析与可视化的学习者&#xff0c;覆盖从编程基础到实战项目的完整技能链路&#xff0c;适合高校学生、转行者及需要处理结构化数据的从业者。压缩包共162个文件&#xff0c;约31.27MB&#xff0c;以67个ipynb实验代码、11个…

2026/10/20 7:58:20 阅读更多 →
QClaw实测:基于大模型的微信数字分身从入门到避坑

QClaw实测:基于大模型的微信数字分身从入门到避坑

1. 项目概述与产品定位拆解1.1 先说清楚&#xff1a;QClaw到底是个什么东西大概几个月前我就留意到了QClaw这个项目&#xff0c;那会儿还是内测灰度阶段&#xff0c;圈子里已经有人开始传“微信里的AI分身”这个概念。这几天全面公测一开&#xff0c;群里热闹得不行。我用了一周…

2026/10/20 7:58:20 阅读更多 →
AI Agent沦为“流氓智能体”:权限失控、提示注入与责任归属全解析

AI Agent沦为“流氓智能体”:权限失控、提示注入与责任归属全解析

前阵子一个做零售的朋友跟我吐槽&#xff1a;他们给客服智能体开了退单权限&#xff0c;结果一个晚上它因为一位客户反复输入“给我退钱”&#xff0c;把同一个小订单退了17次&#xff0c;财务第二天早上脸都绿了。这事本身不大&#xff0c;但它特别能说明问题——当智能体从一…

2026/10/20 7:58:20 阅读更多 →
自动化失败了怎么办?从麦睿菱项目看AI自动化恢复与复盘

自动化失败了怎么办?从麦睿菱项目看AI自动化恢复与复盘

自动化翻车这件事&#xff0c;做AI落地的团队迟早都会遇到一次。麦睿菱是我们内部一个AI自动化改造项目的代号&#xff0c;前后推进了三个多月&#xff0c;结果在试运行阶段就栽了跟头。今天想把它完整复盘一下&#xff0c;重点不是讲成功经验&#xff0c;而是聊聊自动化失败之…

2026/10/20 7:58:20 阅读更多 →
Agent模型选型不再难:运行时路由规则设计与实现

Agent模型选型不再难:运行时路由规则设计与实现

1. 为什么模型选型这件事&#xff0c;最后都变成了路由问题做过 Agent 项目的人大概都有过这么一个阶段&#xff1a;一开始兴致勃勃地对比各家模型&#xff0c;跑分、看榜单、测延迟、算成本&#xff0c;折腾了两周&#xff0c;最后在代码里写下了一个硬编码的模型名字。然后项…

2026/10/20 7:58:20 阅读更多 →
OpenCV基础扫盲:图片数组、BGR颜色与视频帧核心概念详解

OpenCV基础扫盲:图片数组、BGR颜色与视频帧核心概念详解

这一篇原本是写给组里新同学的一份内部分享稿&#xff0c;后来想想干脆整理成公开文章。原因是带了好几次新人&#xff0c;发现大家第一次接触OpenCV时&#xff0c;最痛苦的根本不是算法&#xff0c;也不是深度学习模型&#xff0c;而是连“图片长什么样”“颜色为什么是反的”…

2026/10/20 7:57:19 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →