[{"data":1,"prerenderedAt":595},["ShallowReactive",2],{"navigation_docs":3,"-blog-picdepot":181,"-blog-picdepot-surround":590},[4,27,34,41,116,146,153,169],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"博客","i-lucide-pen-line","\u002Fblog","0.blog",[10,12,17,22],{"title":5,"path":7,"stem":11,"icon":6},"0.blog\u002Findex",{"title":13,"path":14,"stem":15,"icon":16},"创建基于 UIKit 的原生 iOS 项目","\u002Fblog\u002Fcreate-uikit-ios-project","0.blog\u002F01.create-uikit-ios-project","i-lucide-app-window",{"title":18,"path":19,"stem":20,"icon":21},"Lockify：把账号和密码留在自己的手机里","\u002Fblog\u002Flockify","0.blog\u002F02.lockify","i-lucide-lock",{"title":23,"path":24,"stem":25,"icon":26},"PicDepot：只靠 Cloudflare 的自建图床","\u002Fblog\u002Fpicdepot","0.blog\u002F03.picdepot","i-lucide-images",{"title":28,"path":29,"stem":30,"children":31,"icon":33},"年度总结","\u002Fannual-review","1.annual-review\u002Findex",[32],{"title":28,"path":29,"stem":30,"icon":33},"i-lucide-git-commit",{"title":35,"path":36,"stem":37,"children":38,"icon":40},"故事","\u002Fstories","2.stories\u002Findex",[39],{"title":35,"path":36,"stem":37,"icon":40},"i-lucide-feather",{"title":42,"icon":43,"path":44,"stem":45,"children":46},"Android","i-simple-icons-android","\u002Fandroid","3.android",[47,49,54,112],{"title":42,"path":44,"stem":48,"icon":43},"3.android\u002Findex",{"title":50,"path":51,"stem":52,"icon":53},"初探Navigation","\u002Fandroid\u002Fcompose-navigation","3.android\u002F01.compose-navigation","i-lucide-route",{"title":55,"icon":56,"path":57,"stem":58,"children":59},"安卓代码加固","i-lucide-shield","\u002Fandroid\u002Fhidden-hardening","3.android\u002F02.hidden-hardening\u002Findex",[60,63,67,72,77,82,87,92,97,102,107],{"title":61,"path":57,"stem":58,"icon":62},"安卓代码加固：总览","i-lucide-map",{"title":64,"path":65,"stem":66,"icon":21},"安卓代码加固（1）：加密DEX","\u002Fandroid\u002Fhidden-hardening\u002Fencrypt-dex","3.android\u002F02.hidden-hardening\u002F01.encrypt-dex",{"title":68,"path":69,"stem":70,"icon":71},"安卓代码加固（2）：Native解密","\u002Fandroid\u002Fhidden-hardening\u002Fnative-decrypt","3.android\u002F02.hidden-hardening\u002F02.native-decrypt","i-lucide-cpu",{"title":73,"path":74,"stem":75,"icon":76},"安卓代码加固（3）：内存加载","\u002Fandroid\u002Fhidden-hardening\u002Fmemory-load","3.android\u002F02.hidden-hardening\u002F03.memory-load","i-lucide-memory-stick",{"title":78,"path":79,"stem":80,"icon":81},"安卓代码加固（4）：指令分发","\u002Fandroid\u002Fhidden-hardening\u002Fopcode-dispatch","3.android\u002F02.hidden-hardening\u002F04.opcode-dispatch","i-lucide-hash",{"title":83,"path":84,"stem":85,"icon":86},"安卓代码加固（5）：XOR混淆","\u002Fandroid\u002Fhidden-hardening\u002Fxor-obfuscate","3.android\u002F02.hidden-hardening\u002F05.xor-obfuscate","i-lucide-shuffle",{"title":88,"path":89,"stem":90,"icon":91},"安卓代码加固（6）：密钥外置","\u002Fandroid\u002Fhidden-hardening\u002Fkey-external","3.android\u002F02.hidden-hardening\u002F06.key-external","i-lucide-key",{"title":93,"path":94,"stem":95,"icon":96},"安卓代码加固（7）：依赖隔离","\u002Fandroid\u002Fhidden-hardening\u002Fdep-isolate","3.android\u002F02.hidden-hardening\u002F07.dep-isolate","i-lucide-layers",{"title":98,"path":99,"stem":100,"icon":101},"安卓代码加固（8）：构建校验","\u002Fandroid\u002Fhidden-hardening\u002Fbuild-verify","3.android\u002F02.hidden-hardening\u002F08.build-verify","i-lucide-shield-check",{"title":103,"path":104,"stem":105,"icon":106},"安卓代码加固（9）：防录屏","\u002Fandroid\u002Fhidden-hardening\u002Fflag-secure","3.android\u002F02.hidden-hardening\u002F09.flag-secure","i-lucide-eye-off",{"title":108,"path":109,"stem":110,"icon":111},"安卓代码加固（10）：ABI门禁","\u002Fandroid\u002Fhidden-hardening\u002Fabi-gate","3.android\u002F02.hidden-hardening\u002F10.abi-gate","i-lucide-git-compare",{"title":113,"path":114,"stem":115,"icon":96},"状态管理","\u002Fandroid\u002Fstate-management","3.android\u002F03.state-management",{"title":117,"icon":118,"path":119,"stem":120,"children":121},"iOS","i-simple-icons-apple","\u002Fios","4.ios",[122,124,129,134,138,141],{"title":117,"path":119,"stem":123,"icon":118},"4.ios\u002Findex",{"title":125,"path":126,"stem":127,"icon":128},"iOS面试题1","\u002Fios\u002Fios-interview1","4.ios\u002F01.ios-interview1","i-lucide-sparkles",{"title":130,"path":131,"stem":132,"icon":133},"设置启动图","\u002Fios\u002Fset-launch-screen","4.ios\u002F02.set-launch-screen","i-lucide-image",{"title":135,"path":136,"stem":137,"icon":53},"路由管理","\u002Fios\u002Frouting","4.ios\u002F03.routing",{"title":113,"path":139,"stem":140,"icon":96},"\u002Fios\u002Fstate-management","4.ios\u002F04.state-management",{"title":142,"path":143,"stem":144,"icon":145},"事件传递","\u002Fios\u002Fevent-delivery","4.ios\u002F05.event-delivery","i-lucide-pointer",{"title":147,"path":148,"stem":149,"children":150,"icon":152},"Flutter","\u002Fflutter","5.flutter\u002Findex",[151],{"title":147,"path":148,"stem":149,"icon":152},"i-simple-icons-flutter",{"title":154,"icon":155,"path":156,"stem":157,"children":158,"page":168},"面试","i-lucide-graduation-cap","\u002Finterview","6.interview",[159,164],{"title":160,"path":161,"stem":162,"icon":163},"Flutter面试题1","\u002Finterview\u002Fflutter-interview1","6.interview\u002F01.flutter-interview1","i-lucide-boxes",{"title":165,"path":166,"stem":167,"icon":71},"Flutter面试题2","\u002Finterview\u002Fflutter-interview2","6.interview\u002F02.flutter-interview2",false,{"title":170,"icon":171,"path":172,"stem":173,"children":174},"算法学习","i-lucide-binary","\u002Falgorithm","7.algorithm",[175,177],{"title":170,"path":172,"stem":176,"icon":171},"7.algorithm\u002Findex",{"title":178,"path":179,"stem":180},"算法基础1：复杂度分析","\u002Falgorithm\u002Ftime-space-complexity","7.algorithm\u002F01.time-space-complexity",{"id":182,"title":23,"body":183,"description":566,"extension":567,"links":568,"meta":579,"navigation":585,"path":24,"seo":586,"stem":25,"__hash__":589},"docs\u002F0.blog\u002F03.picdepot.md",{"type":184,"value":185,"toc":554},"minimark",[186,197,215,249,253,256,259,262,265,268,330,337,356,359,363,366,396,403,414,427,430,441,454,466,473,480,483,486,489,492,495,499,502,511,514,523,526,544,547,550],[187,188,189,196],"p",{},[190,191,195],"a",{"href":192,"rel":193},"https:\u002F\u002Fgithub.com\u002FArturoYi\u002FPicDepot",[194],"nofollow","PicDepot"," 是我写的一套自建图床。登录后上传，文件库里管理，复制出来的是 R2 公网直链。运行时只有 Cloudflare：页面和 API 在 Workers 上，元数据在 D1，文件在 R2。",[187,198,199,200,204,205,209,210,214],{},"当前版本是 ",[201,202,203],"strong",{},"0.1.0","。仓库可以直接拉下来自己部署。用法、配置和接口写在文档站里，中文是默认语言，英文在 ",[206,207,208],"code",{},"\u002Fen\u002F","。站点的 ",[190,211,213],{"href":212},"\u002Fdemos?cat=web","Web 示例"," 里也能直接打开仓库。",[216,217,218,226,241],"ul",{},[219,220,221,222],"li",{},"仓库：",[190,223,225],{"href":192,"rel":224},[194],"github.com\u002FArturoYi\u002FPicDepot",[219,227,228,229,234,235,240],{},"文档：",[190,230,233],{"href":231,"rel":232},"https:\u002F\u002Farturoyi.github.io\u002FPicDepot\u002F",[194],"arturoyi.github.io\u002FPicDepot","（",[190,236,239],{"href":237,"rel":238},"https:\u002F\u002Farturoyi.github.io\u002FPicDepot\u002Fen\u002F",[194],"English","）",[219,242,243,244],{},"部署步骤：",[190,245,248],{"href":246,"rel":247},"https:\u002F\u002Farturoyi.github.io\u002FPicDepot\u002Fguide\u002Fdeploy",[194],"部署到 Cloudflare",[250,251,252],"h2",{"id":252},"项目初衷",[187,254,255],{},"我需要一个自己的图床，给笔记配图，也给自己传点文件。常见方案会再接 Telegram、S3、WebDAV 或者 Docker，我只想留在 Cloudflare 这一套里：一个账号、一次部署、账单也能看懂。",[187,257,258],{},"读文件走 R2 直链，不经过 Worker。列表只查 D1，不扫存储桶。这样看图的流量尽量不吃 Worker 的免费额度。",[187,260,261],{},"这个站点里的截图，就是从我自己部署的实例上取的直链。它是给自己用的图床，上传要登录，公开直链一旦发出去谁都能打开。如果你要的是防盗链、私有读，或者不经 Cloudflare 的对象存储，这里还不是那个产品。",[250,263,264],{"id":264},"它做了什么",[187,266,267],{},"0.1.0 已经能日常传图、找文件、复制链接。",[216,269,270,288,294,308,314,320],{},[219,271,272,275,276,279,280,283,284,287],{},[201,273,274],{},"上传","。首页要先登录。可以拖拽、点选、粘贴，也可以选整个文件夹。单文件上限 ",[201,277,278],{},"20MB","，不分片。目录可以手填，已有目录会联想；留空时图片进 ",[206,281,282],{},"image","，视频进 ",[206,285,286],{},"video","，其它文件留在根目录。选过的目录会记在这台浏览器里。",[219,289,290,293],{},[201,291,292],{},"预览格式","。HEIC、HEIF、TIFF 会在浏览器里转成 WebP，转不了再退回 JPEG，质量拉满，尽量少压一次。多页 TIFF 取面积最大的那一页。解码只在浏览器里做，不打进 Worker。",[219,295,296,299,300,303,304,307],{},[201,297,298],{},"链接","。上传成功后可以复制直链、Markdown、HTML ",[206,301,302],{},"\u003Cimg>"," 和 BBCode。直链域名来自你配置的 ",[206,305,306],{},"R2_PUBLIC_BASE_URL","。",[219,309,310,313],{},[201,311,312],{},"文件库","。分页、搜索，按目录或类型筛选。可以预览、改名、改目录，也可以批量删除、批量移动。图片预览支持缩放和平移。默认先藏起名为「博客」的目录，避免配图把列表撑满；点进这个目录仍然能看到。",[219,315,316,319],{},[201,317,318],{},"账号","。库里还没有用户时，登录页可以做首次初始化，创建管理员。之后只有管理员能在后台加账号。上传和管理都校验登录会话。",[219,321,322,325,326,329],{},[201,323,324],{},"状态","。后台能看已用空间、类型和目录分布，以及近 24 小时、7 日、30 日的上传量。占用百分比默认按 10GB 来算，只用于展示，上传时不会因为超过这个数被拦住。设成 ",[206,327,328],{},"0"," 就不显示百分比。",[187,331,332,333,336],{},"对象路径按 ",[206,334,335],{},"files\u002F年\u002F月\u002F随机 id.扩展名"," 存放。删文件时会同时去掉 R2 对象和 D1 记录。",[187,338,339,340,343,344,347,348,351,352,355],{},"这一版还没做的，代码里留了位置：API Token（",[206,341,342],{},"api_tokens"," 表）、标签筛选（",[206,345,346],{},"files.tags","）、以及 ",[206,349,350],{},"private"," \u002F ",[206,353,354],{},"blocked"," 这一类可见性。上传接口现在要带登录 Cookie。PicGo 这类客户端如果带不了 Cookie，接不上。",[250,357,358],{"id":358},"怎么使用",[360,361,362],"h3",{"id":362},"本地跑起来",[187,364,365],{},"需要 Node.js 22 以上，包管理器用 pnpm 或 npm。",[367,368,373],"pre",{"className":369,"code":370,"language":371,"meta":372,"style":372},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm install\npnpm dev\n","bash","",[206,374,375,388],{"__ignoreMap":372},[376,377,380,384],"span",{"class":378,"line":379},"line",1,[376,381,383],{"class":382},"sBMFI","pnpm",[376,385,387],{"class":386},"sfazB"," install\n",[376,389,391,393],{"class":378,"line":390},2,[376,392,383],{"class":382},[376,394,395],{"class":386}," dev\n",[187,397,398,399,307],{},"健康检查是 ",[190,400,401],{"href":401,"rel":402},"http:\u002F\u002Flocalhost:3000\u002Fapi\u002Fhealth",[194],[187,404,405,406,409,410,413],{},"本地要通过 ",[206,407,408],{},"wrangler.toml"," 绑上 D1 和 R2。没绑定时，上传和后台接口会返回 503。",[206,411,412],{},".env"," 只给本地覆盖配置用，部署到 Cloudflare 时不读它。",[187,415,416,417,420,421,423,424,307],{},"直链如果 404，多半是文件写进了本地模拟桶，公网地址却指向云端的桶。需要连远程桶时，先 ",[206,418,419],{},"wrangler login","，再在 ",[206,422,408],{}," 里给 R2 打开 ",[206,425,426],{},"remote",[360,428,429],{"id":429},"部署",[187,431,432,433,436,437,440],{},"生产跑在 ",[201,434,435],{},"Workers + Assets"," 上，用 Nitro 的 ",[206,438,439],{},"cloudflare_module","。它不是 Cloudflare Pages。",[187,442,443,444,447,448,450,451,453],{},"上线前先在 Cloudflare 建好 D1 和 R2，给 R2 打开公网访问（",[206,445,446],{},"r2.dev"," 或自定义域名），把绑定和 ",[206,449,306],{}," 写进 ",[206,452,408],{}," 或控制台变量，再执行一次：",[367,455,457],{"className":369,"code":456,"language":371,"meta":372,"style":372},"pnpm db:migrate:remote\n",[206,458,459],{"__ignoreMap":372},[376,460,461,463],{"class":378,"line":379},[376,462,383],{"class":382},[376,464,465],{"class":386}," db:migrate:remote\n",[187,467,468,469,472],{},"我自己用的是 Cloudflare 控制台的 Workers Builds：仓库连上之后由它构建并发布。本地 ",[206,470,471],{},"pnpm deploy"," 只在排障时用。文档站走另一条 GitHub Actions，发到 GitHub Pages，和应用 Worker 无关。",[187,474,475,476,307],{},"完整对照和排错见 ",[190,477,479],{"href":246,"rel":478},[194],"部署指南",[360,481,482],{"id":482},"传一张图",[187,484,485],{},"打开站点会先到登录页。第一次没有用户，走「首次初始化」建管理员，之后用用户名和密码登录。",[187,487,488],{},"首页右上角选目录，或保持「自动归类」。把图片拖进去、粘贴，或点「选择文件夹上传」。成功后从复制菜单里取需要的格式。顶栏会显示当前版本号。",[360,490,491],{"id":491},"管理",[187,493,494],{},"管理员从后台进文件库、用户和状态。文件库里可以搜索、改名、挪目录、删除。用户页只能由管理员创建账号。",[496,497,498],"warning",{},"直链是公开的。发出去之后，拿到链接的人都能打开，不需要再登录。不要把不想公开的文件传上来。R2 公网域名和自定义域名都要自己保管好。",[250,500,501],{"id":501},"协议和反馈",[187,503,504,505,510],{},"项目以 ",[190,506,509],{"href":507,"rel":508},"https:\u002F\u002Fgithub.com\u002FArturoYi\u002FPicDepot\u002Fblob\u002Fmain\u002FLICENSE",[194],"MIT License"," 发布。你可以自由使用、复制、修改、合并、发布和再分发，包括用在闭源项目里，只要保留版权和许可声明。",[187,512,513],{},"软件按现状提供。我不保证它没有缺陷，也不对文件丢失、直链泄露或误删负责。Nuxt、Wrangler 以及 Cloudflare 各项服务各有自己的协议和配额。",[187,515,516,517,522],{},"用的过程中如果上传失败、直链打不开，或者列表和桶里的文件对不上，请到 ",[190,518,521],{"href":519,"rel":520},"https:\u002F\u002Fgithub.com\u002FArturoYi\u002FPicDepot\u002Fissues",[194],"Issues"," 开一条。这是个人项目，回复可能慢，也不是每条都能修。",[187,524,525],{},"写的时候请带上：",[216,527,528,535,538,541],{},[219,529,530,531,534],{},"部署方式：本地 ",[206,532,533],{},"pnpm dev","、Workers Builds，或本地 Wrangler",[219,536,537],{},"应用版本号，在顶栏",[219,539,540],{},"你做了什么、实际看到什么、你本来期望什么",[219,542,543],{},"接口返回的状态码；能稳定复现最好",[496,545,546],{},"不要在 Issue 里贴 Cloudflare API Token、管理员密码、会话 Cookie，或尚未公开的文件直链。用测试文件复现就够了。",[187,548,549],{},"功能建议也可以开 Issue。API Token 和私有读都在计划边上，不保证下一版就做。",[551,552,553],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":372,"searchDepth":390,"depth":390,"links":555},[556,557,558,565],{"id":252,"depth":390,"text":252},{"id":264,"depth":390,"text":264},{"id":358,"depth":390,"text":358,"children":559},[560,562,563,564],{"id":362,"depth":561,"text":362},3,{"id":429,"depth":561,"text":429},{"id":482,"depth":561,"text":482},{"id":491,"depth":561,"text":491},{"id":501,"depth":390,"text":501},"上传、文件库和公网直链都放在 Cloudflare 上。元数据进 D1，文件进 R2，看图不经过 Worker。","md",[569,573,576],{"label":570,"icon":571,"to":192,"target":572},"GitHub","i-simple-icons-github","_blank",{"label":574,"icon":575,"to":231,"target":572},"使用文档","i-lucide-book-open",{"label":577,"icon":578,"to":519,"target":572},"反馈问题","i-lucide-circle-alert",{"date":580,"tags":581},"2026-10-10",[582,583,195,584],"Nuxt","Cloudflare","图床",{"icon":26},{"title":587,"description":588},"PicDepot：Cloudflare 自建图床","PicDepot 用 Nuxt 4、Workers、D1 和 R2 搭一套自建图床。本文说明它为什么做、能做什么、怎么部署使用，以及协议和反馈方式。","YacKRTFejkSKlIHqXXQtYIpKGRpnIPKdjCjYgiXY4PQ",[591,593],{"title":18,"path":19,"stem":20,"description":592,"icon":21,"children":-1},"一款纯本地的密码保存应用。数据只写在当前设备上，不设账号、不上云、不收集使用数据。",{"title":28,"path":29,"stem":30,"description":594,"icon":33,"children":-1},"每年总结一下，为了放心去忘记。",1792494957074]