2026-11-02素材日 2026-10-13 ~ 10-14shot.html · 缓存

代码改了三遍、测试全过,他打开还是 12000

「其他网页打开的确是 12.22,但是我 360 浏览器打开它保存的是最近的一次 12000」——这句话让我把排查方向整个掉了个头。

先交代一下背景。我这站上有个跑步截图生成器,就是我拿来吃饭的那个工具页——访客填个公里数、配速、画条轨迹,页面上直接出一张 Keep 风格的运动截图,配速、心率、轨迹渐变全都有。这玩意儿流量不小,抄的人也不少。

十月底那两天,我跟 AI 搭子在这个页面上连轴干了一大轮活。有多大?这么说吧,光轨迹的渐变配色就推翻重来六版,域名水印在地图上挪了六轮,最后还照着他桌面上那个加密工具,给整个页面上了把锁。这种强度干完,人已经麻了,第二天早上收到一条消息,我寻思着顶多是小修补,结果在这上头又耗了一上午。

这条消息是:「还有这个公里数,填的这个公里数最大不能超过 400,然后每次打开网页的时候,默认值是 12.22。」

我一看,嘿,简单活。默认值改一行,上限 400 就在出图前加道钳制,超了就压回 400。十分钟的事,改完、按老流程加锁、传服务器,md5 报过去。心里还想着今天能早点收工。

你猜怎么着。

12000 是个什么鬼

第二天一睁眼,群里躺着一句:「还是不行啊,默认的 12:22,我打开还是 12000。」

12000?我先给不知道内情的你捋一下这个 12000 是哪来的。那个公里数输入框,值是会自动存进浏览器的——跟昵称、配速一个机制,你填过一次,下次打开就给你还原回来,省得访客每次重填。这是好意。但坏也就坏在这:他之前不知道手怎么滑的,填了个 12000 进去。一万两千公里,兄弟,人这辈子跑不了这么多的,地球周长才四万公里。就这么个数,被好心地存下来了,然后每次打开页面,都还给他。

行,那就修。我修了三层,一层比一层狠:

第一层,出图之前先过一遍,超过 400 的直接摁回 400,顺手把输入框里的数也改掉,让他亲眼看见:

if (dist > 400) { dist = 400; $('#inDist').value = '400'; }

第二层,读保存值的时候再过一遍——你这浏览器里存的历史数据要是超了 400,我直接当没存过,回退默认值:

inDist: (c.inDist && parseFloat(c.inDist) <= 400) ? c.inDist : '12.22',

第三层,新访客的默认值改成 12.22,这是他的原需求。

修完还没完,这活我们已经养成肌肉记忆了——必须测。拿 Playwright 模拟三种人:浏览器里存着 12000 的老倒霉蛋、干干净净的全新访客、故意手填 12000 的杠精。跑下来三个全过:老倒霉蛋打开显示 12.22,新访客 12.22,杠精填 12000 出图的时候自动变 400。截图,md5(463b4b1e…),报过去。这一套动作我们俩做得熟极了,毕竟前几天才干过十几轮。

我以为这就完了。

「还是不行啊」

结果他说:「还是不行啊,默认的 12:22,我打开还是 12000。」

我盯着这句话看了半天。还是不行?

那时候我的第一反应,真是一头扎进代码里。localStorage 的键名是不是写错了?钳制那段是不是没被执行到?init 的顺序是不是先渲染后填充,把默认值又盖回去了?我把读保存值那几行翻来覆去看了三遍,一行一行过,逻辑是真没毛病。那会儿我有点烦了,明明三个模拟场景全绿,还能有什么幺蛾子。

现在回头看,我当时犯了个方向性错误:他说「不行」,我就只往代码上想。可他说的「打开」,跟我测试的「打开」,压根不是一回事——这是后话。

然后他补了一句,就这一句,把窗户纸捅破了:

其他网页打开的确是 12.22,但是我 360 浏览器打开它保存的是最近的一次 12000,这是怎么回事

看到这句我愣了两秒,然后一拍大腿。

你想啊,同一份代码,部署在同一个服务器上,其他浏览器打开都对,就他那台 360 不对。这说明什么?说明代码根本没病。有病的只可能是「他那个浏览器本地的差异」。本地差异能有几种?一种是浏览器里存的数据邪门——不可能,数据再邪门,我第二层钳制也会把它摁回去,他其他浏览器就是活证。那就只剩一种了:

他那台 360 在跑昨天的页面。我改的每一行、测的每一种情况,全是新代码上的事;他那边浏览器压根没下载过新代码,还在啃硬盘里那份昨天缓存的旧货。我们俩对着两个版本的页面吵了一整个早上。

就这么个事。

顺手把根拔了

知道了病因,第一步是让他自愈:360 里按个 Ctrl+F5 强刷一下,拉最新版本,页面立刻就是 12.22。这招立竿见影,但这招治标——你总不能指望每个访客都会强刷吧?访客凭什么知道你改版了?所以必须从服务端根治:给这个页面加上禁缓存响应头,告诉所有浏览器「这个文件每次都要来服务器拿最新的,别自己攒旧货」。

nginx 里加一段的事。但有个细节:这站配了裸域和 www 两个 server 块,一个管 52run.cn 一个管 www.52run.cn,都得加,漏一个就是留个坑给未来的自己。

location = /shot.html {
    add_header Cache-Control "no-cache, no-store, must-revalidate" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

写到这里必须记一笔我自己摔的跤,因为这跤摔得特别隐蔽。

往 nginx 配置里插这段的时候,我用的 shell 命令往里塞,那段命令里引号套引号——外层单引号、里头双引号、路径里还有斜杠要转义——插完命令「执行成功」,没报错。我当时就以为成了,都准备收工了。还好按习惯拿 curl 验了一下响应头,一验傻眼:Cache-Control 根本不在。回去翻配置文件才发现,插是插进去了,但只插进了第一个 server 块,他实际访问的 www 那个块压根没动。后来学乖了,不玩命令行了,直接用文件读写的方式把配置整个打开、插好、存回去,这才插对。

这里面还埋着一个 nginx 的特性坑,不知道的人能被坑到怀疑人生:add_header 这个指令是整条继承链一起覆盖的——你在 location 块里写了任何一条 add_header,server 级别配的那些安全响应头就全部失效,一条都不给你留。我不知道这个,插完缓存头一刷新,好家伙,安全头全没了。所以你看上面那段代码里,缓存头下面还老老实实把 HSTS、nosniff、DENY 那几条重新抄了一遍。不是手欠,是必须抄。

最后 curl 里蹦出那行 cache-control: no-cache, no-store, must-revalidate,这事儿才算真落地。

同一个早上还发生了什么

缓存这事收尾之后我回头一看,嚯,这一轮任务的体量远不止一个 12000。挑几件值得记的,捎带写下来,不写以后就真忘了。

轨迹渐变配色,改了六版。他一开始扔给我十三个色值,从青蓝到红,让我做「跑得快偏蓝、跑得慢偏红」的热力渐变。做完,嫌黄色太多;改成纯色谱过渡,又嫌整体效果差;改成双色,嫌刺眼;后来他干脆发来一张 Keep 的截图,说照这个来——照着做出来才知道,人家 Keep 的渐变条里蓝色和红色只占两端一小截,中间一大片全是绿,所以人家看着自然。收窄区间之后才算像样。你以为完了?他又指着说主色不对,给了个 #27C588;换上又说「同色值显示出来比 Keep 浅」——这条把我们俩都整不会了,同色值怎么会浅?后来扒到原因:轨迹线是三层描边叠出来的,最里头的亮芯层往白里混了 55% 的白,等于给颜色蒙了层纱。混白比例从 55% 砍到 28%,颜色才真正压得住。

域名水印,在地图上挪了六轮。这事的起因特别朴素:怕人白嫖。截图做得越好,被人拿走去掉源头上传的就越多,得让人一眼看到出处。第一版放地图右侧悬浮,他说占地方;挪到两张预览图中间,他说太靠下;改到顶部对齐,他算认可了位置;但样式又来回折腾——先塞进胶囊里,又放地图左下角跟「腾讯地图」logo 并排,白字,他说泛白看不清;加深加大,他说上边下边要平行;最后落到右下角快慢图例胶囊的正上方居中,灰色粗体,才算定稿。一个几十像素的字,六轮。

加锁,照着他的工具复刻的。他扔来一个桌面上现成的网页加密工具,让我照那个逻辑给页面加锁。那工具就三招:CSS 压缩混淆、域名白名单锁定(把允许打开的域名 base64 编码藏进源码,别人另存了 HTML 到自己电脑或者传到自己服务器上,打开直接白屏)、防用户操作(禁右键、禁复制、禁 F12,还每秒钟量一次浏览器窗口的内外尺寸差,超了 160 就判定开着开发者工具,白屏伺候)。我照着复刻了一遍,部署完拿自动化浏览器验了三项:白名单域名正常出图、右键被拦、换个野域名打开白屏。全过,这才敢上线。

还有个坐标系偏移的旧账。就前几天发现的:访客在弹窗地图上画好一条轨迹,传回页面之后,位置能偏出去几百米,画在朝阳显示在丰台那种。查了半天,是坐标系叠了两层——弹窗里的腾讯地图吐出来的本来就是火星坐标(GCJ-02),页面拿到手当成真实坐标(WGS-84)存着,渲染的时候又规规矩矩做了一次「真实转火星」的纠偏。等于原地转两次,偏上加偏。修法倒是简单:接回来的时候先转回真实坐标再落库,后续所有环节就都在一个坐标系上了。几百米的偏移,就因为中间多转了一次。

最后说两句

这个早上最值得记的,不是缓存这个知识点——缓存谁都知道——是我那半天的排查路径。

他说不行,我的第一反应是怀疑自己的代码,把逻辑翻了三遍。这个反应对不对?对了一半。先怀疑自己没错,错的是只怀疑自己:验证「代码逻辑对不对」和验证「用户浏览器拿到的是不是我刚部署的这份」,是两个完全不同的检查项,我只做了前者。而他那句「其他网页打开的确是 12.22」,才是真正把方向掰过来的钥匙——同一份代码,不同浏览器结果不同,差异只能在本地。这句话一出,答案基本就在脸上了。

所以以后再遇到「我这边不对,你那边对」的怪事,先别急着翻代码。先问一句:你跑的,是我刚部署的这份吗?

代码改对了、测试全过,和用户看到的是对的,中间隔着一层缓存。这层不亲眼踩一次,书上看一百遍也想不到。

本文由真实开发记录整理 · 52run.cn · 素材日 2026-10-13 ~ 10-14