[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site":3,"post-tou-xiang-cong-base64-gai-cheng-wen-jian":59,"related-tou-xiang-cong-base64-gai-cheng-wen-jian":83},{"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":68,"series":69,"seriesIndex":70,"seriesNav":71,"prev":77,"next":80},"tou-xiang-cong-base64-gai-cheng-wen-jian","头像从 base64 改成文件存储","base64 塞进数据库之后每次查询都拖一大坨。改成存路径、文件进卷，还要处理历史数据怎么迁移。","2026-05-19","2026-10-17",[66,67],"MySQL","Node","最早的实现很省事：头像上传后转成 base64 字符串，直接存进数据库字段。开发环境跑得很好，直到有人传了一张 3MB 的原图。\n\n那个字段里存进去是四百万字符（base64 比原图大三分之一）。后果不是一条数据慢，是所有的查询都慢——列表接口的 SELECT 里带上了这个字段，一次取十行就是四十兆字符在网络上跑。我一开始怀疑是索引失效，因为现象确实像\"加了数据之后突然变慢\"。\n\n改法分三步。\n\n存文件。上传时写进 uploads 目录，数据库只存路径。文件名用随机 UUID，避免重名覆盖（这是另一个坑，见 multer 那篇）。\n\n迁移历史数据。写一个脚本把已有的 base64 解码成文件、更新路径字段，跑完再删列。顺序不能反：先删列，历史数据就没了。\n\n把字段从默认查询里排除。列表接口只需要头像路径，Prisma 的 select 明确列出要哪些字段，别用默认的全字段返回。\n\n现在回头看，base64 唯一适合存的场景是\"小于几 KB 且不需要单独访问\"的图，比如签名、二维码。头像不属于这一类，因为它要能被 \u003Cimg src> 直接引用、要能被缓存、要能单独换。\n\n还有一条：改成文件存储之后，备份要跟着改。数据库 dump 里不再包含图片，图片在卷里，得单独备。这件事我是在第一次做恢复演练时才发现漏了的。","MySQL 与 Prisma",9,[72,76],{"slug":73,"title":74,"seriesIndex":75},"yao-bu-yao-wei-xi-lie-dan-du","要不要为系列单独建一张表",8,{"slug":60,"title":61,"seriesIndex":70},{"slug":78,"title":79},"re-li-tu-cong-github-zhua-shu-ju","热力图从 GitHub 抓数据，私有仓库不算",{"slug":81,"title":82},"fen-ye-zu-jian-zai-shou-ji-shang","分页组件在手机上顶掉一行",[84,88,92,102,108],{"slug":78,"title":79,"excerpt":85,"date":86,"tags":87},"匿名抓取只有公开贡献。换成 GraphQL 加令牌之后数字才对得上，代价是要管一个会过期的令牌。","2026-05-06",[67],{"slug":73,"title":74,"excerpt":89,"date":90,"tags":91},"一个字段能解决的事不要建表，除非要排序、要页面、要统计。我把三个判断条件列出来，最后选了建表。","2026-03-28",[66],{"slug":93,"title":94,"excerpt":95,"date":96,"tags":97},"gang-deng-lu-jiu-guo-qi-yi-ci","一个 401，查出四个前端机制：缓存回放、隐藏页的 rAF、百分比圆角、缓动过冲","响应头里的 token 被 304 原样回放，把刚签发的新 token 顶掉，表现成刚登录就过期。顺带记三个前端细节：隐藏标签页里 rAF 不执行、扁盒子的 50% 圆角是胶囊、缓动曲线的过冲只有 0.0008。","2026-10-13",[98,99,100,101],"CSS","JavaScript","Vue","浏览器",{"slug":103,"title":104,"excerpt":105,"date":106,"tags":107},"dian-xiang-mu-ye-mian-jiu-si-yi","点「项目」页面就死：一次客户端无限重定向","为了保留旧地址大小写加的一条跳转规则，被注册成了客户端路由；路由匹配不区分大小写，于是它自己跳自己。","2026-10-02",[12,101],{"slug":109,"title":110,"excerpt":111,"date":106,"tags":112},"cong-da-bao-shang-chuan-dao-push-ji","从打包上传到 push 即上线：把 CD 接通","私有仓库的镜像包默认也是私有的，健康检查的探测地址在宿主机有 nginx 时会打错地方。两处都是配好之后才验出来的。",[30,15]]