[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site":3,"post-tu-pian-dao-di-cun-na-cong-dui":59,"related-tu-pian-dao-di-cun-na-cong-dui":85},{"name":4,"url":5,"description":6,"bio":7,"avatar":8,"skills":9,"tools":16,"social":34,"hero":47,"heatmap":52,"icp":55,"police":56,"policeIcon":57,"shortName":58},"时过境迁技术笔记","https:\u002F\u002Fhsgjq.top","一个记录前端与 Node 实践的个人博客，内容围绕 Vue、Nuxt、NestJS 与工程化展开，附源码级的原理笔记。","一名热爱技术的全栈开发者，专注 TypeScript 与 Web 工程化，习惯把踩过的坑和思考记录在这里。","\u002Fuploads\u002Fc485c101-179e-48db-96d1-238693784c38.jpg",[10,11,12,13,14,15],"TypeScript","Vue 3","Nuxt","NestJS","Node.js","工程化",[17,23,28],{"items":18,"title":22},[19,20,21],"Cursor","Git","Chrome DevTools","开发工具",{"items":24,"title":27},[25,26],"Vite","npm","构建工具",{"items":29,"title":33},[30,31,32],"Docker","CI\u002FCD(GitHub Actions)","Vercel","部署运维",[35,39,43],{"href":36,"icon":37,"label":38},"https:\u002F\u002Fgithub.com\u002Fhsgjq","github","GitHub",{"href":40,"icon":41,"label":42},"2971861643@qq.com","mail","邮箱",{"href":44,"icon":45,"label":46},"\u002Frss.xml","rss","RSS",{"lines":48,"subtitle":51},[49,50],"人生若只如初见，","何事秋风悲画扇。","《木兰花·拟古决绝词柬友》",{"enabled":53,"platform":37,"username":54},true,"hsgjq","桂ICP备2024046985号","桂公网安备45011002000103号","\u002Fuploads\u002F72c67ef3-bc86-4b64-b0df-0aba71065050.png","时过境迁",{"slug":60,"title":61,"excerpt":62,"date":63,"updatedAt":64,"tags":65,"content":66,"series":67,"seriesIndex":68,"seriesNav":69,"prev":79,"next":82},"tu-pian-dao-di-cun-na-cong-dui","图片到底存哪：从对象存储的念头回到自建目录","算了一下流量和成本，先放在自己的卷里，把上传路径和访问路径分开设计，将来要换存储只改一处。","2026-07-14","2026-10-20",[15],"上传的图片以前放在服务器本地的 uploads 目录，跟着 Docker 卷走。文章多了之后我开始想换对象存储，研究了一轮，最后没换。\n\n算的账是这样的：图片存本地，成本是磁盘和带宽——磁盘几十 G 才几十块，带宽包含在服务器里。对象存储按存储量加请求数加下行流量计，理论上更便宜，而且自带 CDN、图片处理（缩放、转格式）这些功能。\n\n让我犹豫的是三件事。\n\n流量费。我的图片是从页面里直接引用的，走对象存储的外网下行要单独计费，而服务器带宽是包月的。以我这个访问量，后者划算得多。\n\n迁移成本。换存储意味着上传逻辑、访问 URL、历史文件三处都要动，还要处理\"一部分在本地、一部分在云上\"的中间状态。\n\n厂商锁定。这个理由听起来虚，但把 URL 写进数据库之后，换供应商就是批量改数据。\n\n所以我做了一个便宜得多的决定：把存储层抽象出来，业务代码只调 save(buffer, mime) 和 url(key)，具体落在哪由配置决定。目录实现先跑着，将来真要换，改的是这一个类。\n\n配套改的一件事是访问路径。以前上传接口返回的是绝对 URL，写进数据库。现在返回 \u002Fuploads\u002Fxxx.png 这种相对路径，域名由前端根据站点设置拼。这样换域名、上 CDN、甚至换存储，都不用动库里的数据。\n\n这个改动比换存储本身有用得多。","工程化与部署",11,[70,71,75],{"slug":60,"title":61,"seriesIndex":68},{"slug":72,"title":73,"seriesIndex":74},"swap-dui-shang-xian-he-gou-jian-nei","swap、堆上限和构建内存墙",12,{"slug":76,"title":77,"seriesIndex":78},"cong-da-bao-shang-chuan-dao-push-ji","从打包上传到 push 即上线：把 CD 接通",13,{"slug":80,"title":81},"zhong-wen-zi-ti-zi-ji-hua-zhi","中文字体子集化之后首屏少了 900KB",{"slug":83,"title":84},"dai-ma-kuai-li-de-san-ge-fan","代码块里的三个反引号把正文截断了",[86,90,96,106,111],{"slug":76,"title":77,"excerpt":87,"date":88,"tags":89},"私有仓库的镜像包默认也是私有的，健康检查的探测地址在宿主机有 nginx 时会打错地方。两处都是配好之后才验出来的。","2026-10-02",[30,15],{"slug":91,"title":92,"excerpt":93,"date":94,"tags":95},"jian-li-de-xie-gang-hui-bian-cheng","键里的斜杠会变成冒号：清缓存清了个空","按源码里的分组名去匹配存储键，结果一条都没删掉，接口还回 200。返回一个可被证伪的数字，这类问题当场就能发现。","2026-09-30",[12,15],{"slug":97,"title":98,"excerpt":99,"date":100,"tags":101},"gang-deng-lu-jiu-guo-qi-yi-ci","一个 401，查出四个前端机制：缓存回放、隐藏页的 rAF、百分比圆角、缓动过冲","响应头里的 token 被 304 原样回放，把刚签发的新 token 顶掉，表现成刚登录就过期。顺带记三个前端细节：隐藏标签页里 rAF 不执行、扁盒子的 50% 圆角是胶囊、缓动曲线的过冲只有 0.0008。","2026-10-13",[102,103,104,105],"CSS","JavaScript","Vue","浏览器",{"slug":107,"title":108,"excerpt":109,"date":88,"tags":110},"dian-xiang-mu-ye-mian-jiu-si-yi","点「项目」页面就死：一次客户端无限重定向","为了保留旧地址大小写加的一条跳转规则，被注册成了客户端路由；路由匹配不区分大小写，于是它自己跳自己。",[12,105],{"slug":112,"title":113,"excerpt":114,"date":115,"tags":116},"shang-xian-na-tian-yu-ming-zheng-shu","上线那天：域名、证书，和一条我打错的 IP","把 69 打成 96，于是照着\"安全组没放行\"这条错线索查了一整晚。分层排查的前提是先确认敲的是哪扇门。","2026-10-01",[117],"随笔"]