Blog

  • 一个甩不掉的红叉:深挖 Dropbox 文件夹图标上的”幽灵”同步错误

    一个甩不掉的红叉:深挖 Dropbox 文件夹图标上的”幽灵”同步错误

    一次完整的取证式排查记录:从”重启大法”一路打到 Dropbox 的内部 SQLite 数据库,推翻三个看似正确的结论,最后用一条官方排障命令收尾。

    症状

    Windows 11 的文件资源管理器左侧导航栏里,Dropbox 文件夹图标上挂着一个红圈白叉(Dropbox 的”同步错误 / 无法同步”徽标)。但诡异的是:

    • 右下角系统托盘里的 Dropbox 图标完全正常;
    • Dropbox 自带面板底部明确写着 “✓ Your files are up to date”(文件均为最新);
    • 这个红叉已经持续很多天,期间重启过电脑很多次,纹丝不动。

    一句话:Dropbox 自己说一切正常,但文件夹图标却在报错。 到底谁在撒谎?

    一个关键观察(也是整场排查的起点):同一个 Dropbox 文件夹,在两个不同的资源管理器窗口里,一个有红叉、一个没有。同一对象在不同窗口显示不一致,直觉上指向”缓存 / 刷新问题”而非”实时真实状态”。后面会看到,这个直觉对了一半,又错了一半。


    背景知识:文件夹图标上的”徽标”是怎么来的

    Windows 资源管理器允许第三方通过 Shell Icon Overlay Handler(外壳覆盖图标处理器) 在文件/文件夹图标上叠加一个小角标。Dropbox、OneDrive、Google Drive、TortoiseGit/SVN、各种网盘都靠它来画”已同步 / 同步中 / 出错”的小图标。

    但这里有一个臭名昭著的坑:Windows 最多只认 15 个覆盖图标处理器。

    一个容易混淆的点:这台机器上其实有两套图标系统并存。 Dropbox 开了 Files On-Demand(基于 Windows 的 Cloud Files / 云文件 API),文件级的”绿勾(已同步)/ 白云(仅在线)”是 Cloud Files 的 hydration 状态图标画的,不受这 15 个槽位限制;而文件夹级的”同步中 / 出错”角标(至少在本机这套环境里)走的仍是上面这套老式 Shell 覆盖图标处理器(DropboxExt)。本文要追的那个红叉属于后者,所以下文只沿覆盖图标处理器这条线走。

    这个限制来自底层 image list 最多只能有 15 个 overlay mask,从 Windows XP 时代硬编码至今,Raymond Chen 在《The Old New Thing》里专门解释过它的来龙去脉(devblogs.microsoft.com)。注册表位置在:

    HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers
    HKLM\SOFTWARE\WOW6432Node\...\ShellIconOverlayIdentifiers

    普遍观察到的行为是:Windows 按键名的字母序枚举、只保留前 15 个,后面的静默丢弃。(15 这个上限有 Raymond Chen 的文章背书;而”按键名排序加载”更多是业界长期观察到的实现行为,并非官方逐字承诺的契约——下文我会用实测而非这条规则来下判断。)于是各家应用为了抢进前 15,纷纷在自己的键名前面加前导空格(空格的 ASCII 比字母小,排序靠前)——这是一场公开的”军备竞赛”,ownCloud 的开发者甚至专门提了 issue 吐槽(owncloud/client #3141),也有不少博客教你手动加空格给自己常用的应用提权(garethjmsaunders.co.ukitwriting.com)。

    记住这个机制,因为它马上要把我带进第一个(错误的)结论。


    第一回合:当成图标缓存花屏 —— 失败

    最常见的”图标错乱”原因是 Windows 的图标缓存损坏。标准疗法是重启资源管理器、重建图标缓存数据库:

    # 重启 explorer
    Stop-Process -Name explorer -Force; Start-Process explorer
    
    # 彻底重建图标缓存
    Stop-Process -Name explorer -Force
    Remove-Item "$env:LOCALAPPDATA\IconCache.db" -Force
    Remove-Item "$env:LOCALAPPDATA\Microsoft\Windows\Explorer\iconcache_*.db" -Force
    Start-Process explorer

    删掉了 16 个 iconcache_*.db,重启。红叉纹丝不动。

    结论:它不是 Windows 图标缓存花屏。 一个能扛过图标缓存彻底重建的角标,要么是实时画上去的,要么藏在别处。


    第二回合:15 个覆盖图标名额被吃满 —— 看似铁证,其实站不住

    我把 ShellIconOverlayIdentifiers 下的所有处理器列出来并按加载顺序排序,再逐个解析到它们背后的 DLL:

    槽位 键名 归属
    1–7 OneDrive17 微软 OneDrive(FileSyncShell64.dll)
    8–14 .WorkspaceExt06 百度网盘(YunShellExtV164.dll,用”前导空格 + 点”伪装,抢占最高优先级)
    15 AccExtIco1 Adobe CoreSync
    16–27 DropboxExt0110 ❌ Dropbox,全部排在 15 名之外

    OneDrive 占 7 个 + 百度网盘占 7 个 + Adobe 占 1 个,正好吃满 15 个名额,Dropbox 的 10 个处理器一个都没排上。看起来完美解释了一切:Dropbox 画不出正确徽标,旧红叉也清不掉。

    实际把注册表项按键名排序后的样子(为抢前 15,各家都在键名前堆前导空格 / 加点号):

    [排进]  1.'       OneDrive1'  … 7.'       OneDrive7'        (FileSyncShell64.dll)
    [排进]  8.'      .WorkspaceExt0' …14.'      .WorkspaceExt6'  (百度网盘 YunShellExtV164.dll)
    [排进] 15.'   AccExtIco1'                                    (Adobe CoreSync)
    [出局] 16.'   DropboxExt01' …25.'   DropboxExt10'           (Dropbox ← 全部落在 15 名之外)
    [出局] 26.'  115ErrorOverlayIcon' …                         (115 网盘)

    这正是网上铺天盖地的那个解释——”装太多网盘把 Dropbox 挤掉了”(itwriting.comCentreStackWikipedia: List of shell icon overlay identifiers)。

    但我没有就此收手。 因为有个细节对不上:如果 Dropbox 的处理器没被加载,它连红叉都画不出来才对。于是我去查 explorer.exe 当前到底加载了哪些 DLL:

    Get-Process explorer | ForEach-Object { $_.Modules } |
      Where-Object { $_.FileName -match 'DropboxExt|FileSyncShell|YunShellExt|CoreSync' } |
      Select-Object -Expand FileName -Unique

    实测输出(只列命中的模块):

    Dropbox  : 已加载 -> C:\Program Files (x86)\Dropbox\Client\DropboxExt64.95.0.dll
    OneDrive : 已加载 -> C:\Program Files\Microsoft OneDrive\...\FileSyncShell64.dll
    Baidu    : 未加载
    Adobe    : 已加载 -> C:\Program Files (x86)\Common Files\Adobe\...\CoreSync_x64.dll

    结果与”名额被吃满”的预期正好相反:

    • DropboxExt64.95.0.dll —— ✅ 已加载、活动中
    • 百度网盘 YunShellExtV164.dll —— ❌ 没加载(因为百度网盘进程当时没运行!)

    反转: 百度网盘虽然霸占着注册表里的高优先级槽位,但它没运行,所以它的处理器根本没被加载(也就谈不上占用 overlay 槽)。”15 名额被吃满”这个流传最广的解释,在这台机器上根本不成立

    教训一:注册表里”占着名额”不等于”运行时真加载了”。至少在这台机器上,排在 16 名之外的 DropboxExt 照样被加载进了 explorer 并在画角标——”名额被吃满”的解释在此不成立。(严格说,单凭”DLL 已加载”还不足以断言 Windows 的 15 计数规则,也不能证明别的 handler”让出了名额”;但第四回合会证明红叉确实是 Dropbox 实时画的,从而坐实它的覆盖处理器在正常工作。)要快速验证,直接看 explorer.exe 的已加载模块即可。

    既然 DropboxExt 是活的,那红叉就是 Dropbox 自己实时画的。问题升级为:Dropbox 为什么坚持认为根目录有错?

    本回合小结 —— 观察到:DropboxExt 已加载、红叉仍在;排除了:”被挤出 15 名额导致画不出图标”这个解释(在本机);还不能证明:Dropbox 的”错误”判断到底源于真实同步问题,还是陈旧状态。


    第三回合:desktop.ini 红鲱鱼

    文件夹图标可以被自身的 desktop.ini 静态指定。如果 Dropbox 在出错时往 desktop.ini 写了个”错误版”图标资源、修好后没改回来,那就能完美解释”持久且扛重启”。一查:

    [.ShellClassInfo]
    IconResource=C:\Program Files (x86)\Dropbox\Client\Dropbox.exe,-13001

    确实指定了自定义图标 -13001。我用 PrivateExtractIcons(负数索引按资源 ID 提取)把它抠出来看:

    # PrivateExtractIcons 传负的 nIconIndex 时,按资源 ID 的绝对值提取
    [IconRes]::Save("...\Dropbox.exe", 13001, "res_13001.png", 256)

    -13001 是一个干净的蓝色 Dropbox 文件夹图标,没有任何红叉。所以 desktop.ini 是清白的,文件夹的基础图标正常,红叉确实是叠加在上面的动态覆盖,不是静态图标。

    教训二:desktop.iniIconResource=...,-N 里的 N资源 ID(负号表示”这是 ID,不是序号”),Windows 的图标提取语义里负值通常被解释为资源 ID(而非从 0 起的序号)——这一点在 ExtractIconEx/SHDefExtractIcon 的官方文档里有明确说明。我用 PrivateExtractIcons 传负值做了验证性提取,取到的正是资源 ID 13001 对应的图标。


    第四回合:注册表命名空间节点 + 一个幽灵副本

    导航栏里的 Dropbox 项不是普通的”固定文件夹”,而是一个 命名空间外壳扩展(shell namespace extension),跟 OneDrive 一样,通过 HKCU\Software\Classes\CLSID\{GUID} + ...\Desktop\NameSpace\{GUID} 注册。一查,发现注册了两个都叫 “Dropbox”、都指向 C:\Users\sudos\Dropbox 的 CLSID:

    • {9E639962-...} —— 当前生效(在 Desktop\NameSpace 里)
    • {E31EA727-...} —— 幽灵副本(不在 NameSpace 里,疑似重装/迁移残留)

    为了判断红叉是”卡在这个外壳节点上”还是”Dropbox 实时喂的”,我做了一个删除—恢复对照实验(全程只改 HKCU、先 reg export 备份、可逆):

    1. 删掉幽灵副本 {E31EA727} + 把活动项 {9E639962} 连定义带挂载点整个删除;
    2. 重启 explorer → 侧边栏 Dropbox 项连同红叉一起消失;
    3. 从备份原样导回活动项;
    4. 重启 explorer → 红叉在节点一出现的瞬间又回来了。

    这一步是决定性的:红叉不在注册表 / 外壳缓存里。 删除节点只是把”显示红叉的载体”拿掉了;载体一回来,Dropbox 立刻又把红叉喂上去。

    教训三:想区分”显示层缓存卡死”还是”数据源实时输出”,做一次”删除载体—恢复载体”对照实验最干脆。


    第五回合:翻 Dropbox 自己的数据库

    既然是 Dropbox 实时输出”错误”,那它的依据在哪?我开始只读地翻 Dropbox 的本地状态(都不碰云端、不触发下载):

    ...\Dropbox\instance1\icon.db(81 MB) —— 名字很像”图标状态库”,但一看 schema 就排除了:键名是 tpng<路径>_256、值是 Python pickle,这其实是缩略图预览缓存,与徽标无关。

    sync_history.db —— 近期 99 条事件全是正常的 file/add|edit|delete · upload|download,零错误、零冲突,最近一次同步就在当天下午。

    sync_and_storage.db(127 MB) —— 只有一张 storage_files 表(Files-On-Demand 占位索引),根本没有”状态 / 错误”字段

    到这里,Dropbox 所有可读的本地证据都在说”我很健康”。


    第六回合:”仅在线”状态反推出真相

    storage_files425,472 条记录,本地实际 406,481 个文件,其中 406,076 个是”仅在线”(online-only)占位符

    “仅在线 vs 本地有内容”是靠 Windows Cloud Files 给占位符打的文件属性位数出来的(只读元数据,不触发下载):

    # RECALL_ON_OPEN(0x40000) / RECALL_ON_DATA_ACCESS(0x400000) / OFFLINE(0x1000)
    # 任一置位 = 该文件是"仅在线"占位符(内容在云端,本地已脱水)
    attrs = entry.stat(follow_symlinks=False).st_file_attributes
    online_only = bool(attrs & (0x40000 | 0x400000 | 0x1000))
    # 结果:406,481 个文件里 406,076 个 online_only,仅 390 个本地有内容

    关键推理:

    一个文件能被脱水成”仅在线”占位符,前提是它已经成功上传到云端了。所以所有”仅在线”文件,定义上都是同步成功的。

    那么”上传失败”的文件,本地必然是”有内容”状态(不会被脱水)。而本地”有内容”的文件总共只有 390 个——若元凶是某个”上传失败”的文件,它必然落在这 390 个里。我把这 390 个全列出来,逐类排查:最近编辑过的正常文档、Git 仓库、GIS 数据、.DS_Store、会被 Dropbox 默认忽略的 ~$ Office 锁文件、几个自动化脚本留下的 .tmp……

    同时对全盘做了一次命名合规扫描(对照 Dropbox 官方的命名规则:不能含 < > : " | ? *、不能以空格/句点结尾、路径 < 260 等,见 Dropbox Help: Naming files and folders):

    • 非法字符:0
    • 尾部空格/句点:0
    • Windows 保留名:0
    • 大小写冲突:0
    • 以空格开头:10 个,但它们全是”仅在线”状态 = 早已同步成功
    • 超长路径(>250):769 个(多为 node_modules 深层嵌套),同样全是”仅在线” = 已同步

    换句话说,那些”看起来可疑”的文件,Dropbox 其实都已经顺利同步了。 这一步把嫌疑范围从 40 万骤降到 390 个本地文件——而这 390 个里也没发现任何卡住失败的项

    本回合小结 —— 观察到:可疑命名 / 超长路径的文件其实都是”仅在线”= 已同步,本地有内容的仅 390 个且无异常;排除了:存在大批因命名 / 路径而同步失败的文件;还不能证明:Dropbox 内部绝对没有任何挂起项——只能说在可读的本地证据里没发现。


    结论:一个陈旧的”幽灵徽标”

    把所有证据拼起来:

    检查项 结果
    Dropbox 面板 / 托盘 ✓ 一切正常,文件均为最新
    sync_history 0 错误,当天仍在正常同步
    主同步库 无错误字段;40.6 万文件已同步为”仅在线”
    本地未上传文件 仅 390 个,无任何卡住失败项
    命名合规 0 个硬性不兼容文件
    Windows 图标缓存 已重建,无效
    desktop.ini 指向干净图标,无辜
    注册表节点 红叉随节点删/恢复同步消失/重现 → 由 Dropbox 实时输出

    基于现有本地证据,没找到任何真正无法同步的文件。那个红叉更像是 Dropbox 文件资源管理器集成为根目录留下的一个”陈旧徽标”——它和 Dropbox 自己的引擎状态相矛盾,是显示层的 bug。很可能驻留在 Dropbox 自身的状态 / 缓存里(从”重启系统、重启 Dropbox、清 Windows 图标缓存、重置注册表节点全都无效”这一点反推),又由 Dropbox 实时喂给外壳扩展(所以注册表删了再恢复会立刻重现)。

    这类”引擎说 up to date、角标却卡在错误”的情况,在各家网盘里都不罕见(参见 Dropbox Help: 检查同步状态 以及社区里大量同类反馈)。


    收尾:一条命令搞定

    既然是 Dropbox 自己的状态缓存陈旧,正确做法不是再去碰 Windows,而是让 Dropbox 重算并重新推送徽标。Dropbox 官方排障步骤里就有”清空缓存文件夹”这一招,把这一招用上:.dropbox.cache 只是上传/下载用的临时暂存数据,删除不会动你的 Dropbox 正式文件,Dropbox 会按需自动重建它(Dropbox Help: Clear the cache folder)。下面的命令针对本机这种标准布局(个人账户、根目录在 %USERPROFILE%\Dropbox);团队账户或 File Provider 布局下缓存路径不同,需相应调整:

    # 1) 退出 Dropbox(官方做法是托盘菜单「退出」;强杀只是脚本化的便捷替代)
    Stop-Process -Name Dropbox -Force
    
    # 2) 清空内部缓存(不是你的文件;Dropbox 会自动重建)
    Get-ChildItem "$env:USERPROFILE\Dropbox\.dropbox.cache" -Force |
      Remove-Item -Recurse -Force
    
    # 3) 重启 Dropbox
    Start-Process "C:\Program Files (x86)\Dropbox\Client\Dropbox.exe"

    重启后 Dropbox 重新评估了根目录状态,红叉消失,图标恢复成干净的蓝色盒子。 收工。

    注:托盘菜单里的「暂停同步」→「恢复同步」对本案无效——这台机器此前已多次暂停/恢复,红叉依旧;只有清掉 .dropbox.cache、让 Dropbox 重建状态才奏效。可见这个广为流传的建议并非万能。


    几条可复用的经验

    1. 别被”显而易见的答案”骗了。 “网盘装太多,15 个覆盖图标名额被吃满”是本案最诱人的解释,而且数据初看完全吻合——但实测 explorer 已加载模块直接推翻了它。注册表占名额 ≠ 运行时真加载。
    2. 区分”显示层缓存”与”数据源实时输出”: 做”删除载体—恢复载体”对照实验。本案中红叉随注册表节点删/复而灭/现,直接证明它是 Dropbox 实时喂的。
    3. 善用”仅在线”占位符状态做反向推理: 能脱水 = 已上传成功。这一步把”40 万嫌疑文件”瞬间缩小到”390 个本地文件”,而这些里也查不出真正卡住的项。
    4. 修复要打在正确的层: 症状显示在 Windows 资源管理器,病根却在 Dropbox 的内部状态。对着 Windows 一通重启、清缓存、改注册表,都是在错误的层上使劲。
    5. 用户的直觉常常是对的: 他从一开始就说”Dropbox 其实没问题”。最终所有取证都指向同一结论——未发现任何实际同步失败的证据,红叉更像显示层的 bug,不影响文件同步。

    参考资料

    • Raymond Chen, *Why is there a limit of 15 shell icon overlays?* — The Old New Thing, Microsoft DevBlogs. [oldnewthing]
    • Tim Anderson, *The battle to own Windows Explorer shell overlay icons, or why your OneDrive green ticks have stopped working.* [itwriting]
    • Wikipedia, *List of shell icon overlay identifiers.* [wikipedia]
    • Gareth J. M. Saunders, *Managing overlay icons for Dropbox and TortoiseSVN and TortoiseGit.* [gareth]
    • ownCloud client, Issue #3141: *[Windows] Shell icon overlays take unwarranted precedence in registry list.* [owncloud]
    • CentreStack / Gladinet, *Why can’t I see any overlay icon on the files in the cloud drive?* [gladinet]
    • Dropbox Help, *How to clear the Dropbox cache folder.* [dropbox-cache]
    • Dropbox Help, *How to check if your files and folders are syncing.* [dropbox-sync-check]
    • Dropbox Help, *Naming Dropbox files and folders.* [dropbox-naming]

    *排查环境:Windows 11 Home (22631) · Dropbox 95.x · 同步约 40 万文件(Files On-Demand)· 同机并存 OneDrive / 百度网盘 / Adobe CoreSync。*


    致谢与署名

    本文由 Claude Code 实地探索并撰写初稿:从复现症状、逐层取证(重建图标缓存、解析覆盖图标注册表、检查 explorer 已加载模块、提取图标资源、读取 Dropbox 内部 SQLite 库、全盘只读元数据扫描),到最终定位与修复,全程留痕。网站管理员 oopus 在过程中做了若干关键的问题决断与验证(包括坚持”Dropbox 实际无故障”的判断、对动注册表/扫描等操作的把关与授权、以及”暂停/恢复同步无效”等一手事实的纠正)。最终的修订与审校由 oopuscodex(OpenAI Codex)共同完成——codex 对技术准确性、逻辑严谨度与引用规范做了一轮严格 review,促成了多处”把’很像’收紧为’已证实’”的改写。

  • QGIS 导出 mbtiles 作为离线地图,并在 Laravel 中使用的实践

    QGIS 导出 mbtiles 作为离线地图,并在 Laravel 中使用的实践

    将 QGIS 可见图层导出为离线地图、并与 Laravel 这样的 Web 后端结合使用,MBTiles 格式配合前端的 Leaflet.js 或 OpenLayers 是目前比较常见的一套组合。

    如果地图包含栅格数据(DEM/地形晕渲)和矢量数据(河流),直接导出为 Web 切片(瓦片)是个可行的方向——QGIS 的切片工具只会渲染当前处于勾选(可见)状态的图层,省去了手动筛选的麻烦。

    以下是完整的工作流记录:


    第一阶段:在 QGIS 中导出数据 (MBTiles)

    为什么选 MBTiles?
    相比于导出成千上万个碎小的 .png 图片文件夹(XYZ 目录结构),MBTiles 本质上是一个单文件的 SQLite 数据库,里面存储了所有层级的瓦片图片。它极大地简化了文件的传输、存储,并且 Laravel(PHP)原生支持极速读取 SQLite。

    具体步骤:

    1. 检查图层与投影: 确保左侧图层树中,只有你需要导出的图层被勾选。建议将 QGIS 右下角的项目坐标系(CRS)设置为 Web 地图通用的 EPSG:3857 (WGS 84 / Pseudo-Mercator)。在 Web 离线地图开发中,使用非 EPSG:3857 作为切片坐标系,容易打破 Web 地图的”默认规则”,引发一系列兼容性问题。条件允许的话,尽量统一采用 EPSG:3857。
    2. 打开切片工具: 在顶部菜单栏选择 处理 (Processing) -> 工具箱 (Toolbox)
    3. 搜索工具: 在右侧弹出的工具箱中搜索 XYZ,找到 栅格工具 (Raster tools) 下的 生成 XYZ 瓦片 (MBTiles) / Generate XYZ tiles (MBTiles)
    4. 配置参数:
      • 范围 (Extent): 点击右侧的 ...,选择”使用当前画布范围 (Use Map Canvas Extent)”,或者选择某个图层的范围。
      • 最小缩放级别 (Minimum zoom): 通常设为 03
      • 最大缩放级别 (Maximum zoom): ⚠️ 注意: 如果地图范围较大(比如覆盖整个东亚这样的区域),级别设得太高会导致生成文件呈指数级暴增且耗时极长。建议先从较低的缩放级别(如 78)开始测试,确认文件大小和生成时间可接受后,再酌情提高到 10 左右。
      • 输出文件: 选择一个保存路径,命名为 offline_map.mbtiles
    5. 点击 运行 (Run),等待生成完毕。

    第二阶段:在 Laravel 中提供服务

    现在你有了一个 offline_map.mbtiles(SQLite 数据库文件)。你需要让 Laravel 读取它并输出图片流。

    1. 放置文件: 将生成的 offline_map.mbtiles 放入 Laravel 项目中,例如 storage/app/maps/offline_map.mbtiles
    2. 配置数据库连接:config/database.php 中添加一个新的 SQLite 连接:

      'connections' => [
          // ... 其他连接
          'mbtiles' => [
              'driver'   => 'sqlite',
              'database' => storage_path('app/maps/offline_map.mbtiles'),
              'prefix'   => '',
          ],
      ],
    3. 创建路由与控制器: MBTiles 数据库里通常有一张名为 tiles 的表,包含 zoom_leveltile_columntile_rowtile_data(BLOB 格式的图片)。在 routes/web.php 中添加一个路由:

      use Illuminate\Support\Facades\Route;
      use Illuminate\Support\Facades\DB;
      use Illuminate\Http\Response;
      
      Route::get('/map-tiles/{z}/{x}/{y}', function ($z, $x, $y) {
          // MBTiles 使用的是 TMS 坐标系,Y 轴与标准的 XYZ 相反,需要转换
          $tms_y = (pow(2, $z) - 1) - $y;
      
          $tile = DB::connection('mbtiles')
              ->table('tiles')
              ->where('zoom_level', $z)
              ->where('tile_column', $x)
              ->where('tile_row', $tms_y)
              ->first();
      
          if (!$tile) {
              abort(404); // 或者返回一张透明的占位图片
          }
      
          // 假设导出的格式是 PNG
          return response($tile->tile_data)->header('Content-Type', 'image/png');
      });

    第三阶段:前端渲染 (Leaflet.js)

    在你的 Laravel Blade 视图(或 Vue/React 组件)中,使用常用的开源地图库 Leaflet.js 来加载这些离线瓦片。

    1. 引入 Leaflet 的 CSS 和 JS:

      <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
      <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
      <style>
          #map { height: 600px; width: 100%; }
      </style>
    2. 初始化地图并接入 Laravel 接口:

      <div id="map"></div>
      
      <script>
          // 根据实际地图区域调整中心点坐标和初始缩放级别
          var map = L.map('map').setView([35.0, 105.0], 4);
      
          // 添加离线瓦片图层,指向 Laravel 的路由
          L.tileLayer('/map-tiles/{z}/{x}/{y}', {
              minZoom: 3,
              maxZoom: 8, // 必须与你在 QGIS 中导出的层级匹配
              attribution: 'Offline Map Data'
          }).addTo(map);
      </script>

    替代方案(最简单,但文件多):导出目录结构 (Directory)

    如果你的服务器对 SQLite 拓展支持有问题,或者你想直接用 Nginx 代理静态文件来追求极致性能:

    • 在 QGIS 中选择 生成 XYZ 瓦片 (目录)
    • 将生成的包含层级数字(如 012…)的文件夹整个复制到 Laravel 的 public/tiles/ 目录下。
    • 前端直接写静态路径:L.tileLayer('/tiles/{z}/{x}/{y}.png', {...})
    • 缺点: 高层级地图会产生几十万个碎片文件,非常难移动和删除。
  • 清理 VS Code「最近打开」列表中的无效记录

    清理 VS Code「最近打开」列表中的无效记录

    用 VS Code 时间久了,「文件 > 最近打开」列表会积累大量已删除或移动过的项目路径,点进去全是报错。网上流传的清理方法大多已过时——本文记录了在 VS Code 1.122+ 上找到正确存储位置的完整过程,以及最终可用的清理脚本。


    传统方法失效

    常见教程的做法是直接修改 SQLite 数据库:

    %APPDATA%\Code\User\globalStorage\state.vscdb

    查询 key:

    SELECT value FROM ItemTable WHERE key = 'history.recentlyOpenedPathsList'

    但在 VS Code 1.122 上执行后返回 None——这个 key 在这个路径下根本不存在。


    逐步排查过程

    1. 确认 User globalStorage 中没有该 key

    列出 state.vscdb 中所有 key,没有任何 recent 相关条目。

    2. 搜索所有 .vscdb 和 LevelDB 文件

    workspaceStorage 目录下有数百个工作区数据库,Local Storage LevelDB 也无相关 key。

    3. 在 storage.json 中找到文件夹历史

    %APPDATA%\Code\User\globalStorage\storage.json 中的 profileAssociations.workspaces 存储了所有打开过的文件夹路径与 profile 的对应关系,可以从这里删除不存在的文件夹记录。

    4. 在 workspaceStorage 中找到文件历史

    每个工作区的 state.vscdbhistory.entries 记录了该工作区内曾打开过的文件路径,可以从这里批量清理。

    5. 最终发现:真正的「最近打开」列表

    上面两步清理后,独立文件(直接用 File > Open File 打开、不属于任何工作区的文件)仍然出现在列表里。通过分析 VS Code 1.122 的主进程日志,找到了最终答案:

    [shared storage] Creating shared storage database at
    '~/.vscode-shared/sharedStorage/state.vscdb'

    真正的存储位置是 ~/.vscode-shared/sharedStorage/state.vscdb,这是 VS Code 的跨 profile 共享存储,包含完整的「文件夹 + 独立文件」全局最近打开列表,key 仍然是 history.recentlyOpenedPathsList,只是搬到了这个新路径。


    VS Code 存储结构(1.122+)

    内容 文件 Key
    全局最近打开列表(文件夹 + 文件) ~/.vscode-shared/sharedStorage/state.vscdb history.recentlyOpenedPathsList
    文件夹与 profile 关联 %APPDATA%/Code/User/globalStorage/storage.json profileAssociations.workspaces
    工作区内文件编辑历史 %APPDATA%/Code/User/workspaceStorage/*/state.vscdb history.entries

    版本变迁

    VS Code 版本 存储位置 Key
    ~1.74 及以前 %APPDATA%/Code/User/globalStorage/storage.json(JSON) history.recentlyOpenedPathsList
    ~1.75 ~ 1.121 %APPDATA%/Code/User/globalStorage/state.vscdb(SQLite) history.recentlyOpenedPathsList
    1.122+ ~/.vscode-shared/sharedStorage/state.vscdb(SQLite) history.recentlyOpenedPathsList

    版本节点为推测值,以实际行为为准。


    清理脚本

    完整脚本已开源,一键清理上述三个位置:

    GitHub:sudoghut/vscode-clean-recent

    运行方法

    git clone https://github.com/sudoghut/vscode-clean-recent.git
    cd vscode-clean-recent
    python clean_vscode_recent.py

    或在 Windows 下避免编码问题:

    cmd.exe /c "chcp 65001 >nul && set PYTHONIOENCODING=utf-8 && python clean_vscode_recent.py"

    注意事项

    • 先关闭 VS Code,否则 VS Code 退出时会覆盖修改
    • 脚本会自动备份所有修改的文件
    • Remote SSH / WSL 路径不会被删除

    结果

    脚本在测试机上:

    • 全局最近打开列表:806 条 → 删除 427 条无效,保留 379 条
    • 文件夹 profile 关联:338 条 → 删除 190 条无效,保留 148 条
    • 工作区文件历史:扫描 477 个数据库 → 删除 1325 条无效记录,保留 2002 条

    重启 VS Code 后「最近打开」列表彻底干净。

  • Dropbox 的 Smart Sync 触发自动故障导致磁盘空间被迅速消耗完的问题

    Dropbox 的 Smart Sync 触发自动故障导致磁盘空间被迅速消耗完的问题

    Dropbox 的 Smart Sync 功能允许用户将文件设为”仅在线”以节省本地磁盘空间。然而,Dropbox 客户端在重新启动时(例如系统从睡眠唤醒、客户端更新或手动重启),有时会将大量文件夹的 Smart Sync
    策略从”仅在线”批量重置为”下载到本地”,触发数万个文件的大量下载,迅速耗尽磁盘空间。这一行为并非用户误操作,而是 Dropbox 客户端的已知问题。

    当这种情况发生时,在 Dropbox 正在执行批量下载的过程中,直接通过右键菜单将文件夹设为”Make available online only”往往无效,因为父级文件夹的策略优先级更高,且 Dropbox 的下载队列在重启后仍会持续执行。

    有效的解决方法是:完全退出 Dropbox 客户端,然后用 SQLite 工具打开本地数据库文件 smarter_smart_sync.dbx(位于 AppData\Local\Dropbox\instance1\),执行 UPDATE scan_history SET folder_policy=NULL WHERE folder_policy=1
    将所有被重置的下载策略清除。操作前建议先备份该文件。清除后重新启动 Dropbox,立刻暂停同步,再对 Dropbox 根目录执行一次”Make available online only”,然后恢复同步,Dropbox 便会开始删除本地副本、释放磁盘空间,而不再重新下载。

    为避免此问题,建议对于不需要本地存储的超大文件夹改用 Selective Sync(选择性同步)而非 Smart Sync,因为 Selective Sync 将文件夹完全排除在本地之外,不受策略重置的影响,稳定性更好。此外,也建议在磁盘上保留足够的余量,以便在策略意外重置时有足够时间发现并处理。

  • 圣诞假补完链锯人蕾塞篇

    圣诞假补完链锯人蕾塞篇

    前段,男频 yy 恋爱剧嘛,有什么好看的。中段,哇,分镜!音乐!节奏!适合圣诞的打斗爆米花番! 过度到结尾之前:有些事既正确又恶心,那么就愉快地不要恶心自己!结尾:价值观冲突时,「子弹的批判」总是适合悲剧。权力/暴力决定结局,也算是当下文化的写实。豆瓣 9.1, 真不错。
    原作藤本树:「原作中让帕瓦最后登场是为了过渡到下一章,但拍成电影我也想过,结束时是否有必要让她出场。不过多亏她最后阳光的声音,能让自己心情愉悦地走出电影院。」你也知道吖!

  • VSCode 中文折行不完全

    在 VSCode 中安裝了主題和字體之後,開啟折行時,有時會出現折行後仍然有一部分內容需要透過水平滾動條顯示的狀況。

    這時候可以進入 VSCode 的設置,檢索”Wrapping Strategy”, 把它設置為 advanced 即可。

  • Trending stories 轉型方案

    Trending stories 轉型方案

    Trending stories 網站在設計的時候,有兩個技術點,首先是用打開 internet access 的 gemini api 根據 Google trends 生成新聞。這本身也是我自己想要的產品,經常看到 Google trends 一頭霧水,希望能快速知道這些搜索熱詞背後到底是什麼,而不是被新聞站或者 twitter 這種基於推送的算法引導。第二個是用 flux .1 模型為每一條新聞配圖。如:

    https://trending.oopus.info/date/20251101

    在推廣失利以及和 LLM 深度探討之後,對 Trending news 有進一步的思考:新聞站有大量競品,甚至是惡意新聞農場。而關於新聞報道,Google 本身也在做 news 和其他產品來解釋新聞。基於 Google trends 做新聞解釋網站等於與自己的完整產業鏈供貨者競爭下游利益,更難成功。

    考慮到當前網站的維護由於圖片生成質量不穩定,需要我自己檢查和重新生成部分圖片,所以沒辦法全自動運行。那麼當前的轉變是:

    1. 把基於 AI 的圖片生成轉為簡單的詞雲。如 https://trending.oopus.info/date/20251104 頁面的後二十張圖。這樣雖然更加不好看,但更容易讓人一眼就能看到今天的新聞熱點,以及當前所瀏覽新聞的大致熱度。比如 20251104 的熱點是選舉。
    2. 生成 trending news 的文本需要一個模板(還沒做,這幾天調試 prompt)。比如每一篇新聞都由總結,為什麼會變成今日熱點,以及何時、何地、何人、何事。
    3. 把新聞生成程序部署到雲端,全自動生圖生文,卡住早、晚報的時間(還沒做,這幾天做)。讓偶爾能看到這個網站的人,確實可以看到今日新聞。網站不會因為我某天沒來得及手動做圖片質檢而導致新聞推遲發佈。
    4. 【真正的轉變】基於當前的每日新聞撰寫深度報道和分析文章。這些文章可以用 AI 生圖, AI 組織,AI 分析,但都要經過自己來最終成文。這些深度報道和分析也許可以發佈在另一個新的網站上。它們是作為對 trending news 相關新聞的「透視」。建立 trending news 的目標本來就是希望研究「新聞」和「歷史」的關係。之前想看看 trending news 作為日報,本身是否能成為獨立產品。現在看來不太行,那麼就回到最初設計。並且這也是「須有我在」。之前單純是基於 U.S. Google trends 面向北美的產品,並沒有「我在」。而現在把自己對新聞的興趣,自己設計的相似性算法,自己的新聞分析,以及可視化加進來。

  • Troubleshooting Localhost Access Issues: When Your Terminal Can’t Reach Local Services

    Have you ever encountered a frustrating situation where your browser can access a local service, but your terminal and IDE cannot? Recently, I faced this exact issue while trying to access a ComfyUI API running on 127.0.0.1:8188.

    The Problem

    • What worked: Browser with proxy settings could access the local service
    • What didn’t work: VSCode, terminal, and curl commands couldn’t reach 127.0.0.1:8188
    • What I tried: Cleared system and IDE proxy settings, removed HTTP_PROXY and HTTPS_PROXY environment variables

    Despite confirming that echo %HTTP_PROXY% and echo %HTTPS_PROXY% returned empty values, the issue persisted.

    The Solution: Let Your Tools Tell You What They See

    Sometimes the best approach is to let your applications reveal their own proxy configuration. Here’s a Python script that helped me diagnose the issue:

    import os
    import urllib.request
    
    print("--- Checking Environment Variables from Python's perspective ---")
    # os.environ is how Python sees all environment variables
    print(f"HTTP_PROXY: {os.environ.get('HTTP_PROXY')}")
    print(f"HTTPS_PROXY: {os.environ.get('HTTPS_PROXY')}")
    
    print("\n--- Checking what proxies urllib automatically detects ---")
    # getproxies() shows what urllib will use by default
    print(urllib.request.getproxies())

    What This Reveals

    The urllib.request.getproxies() function shows you exactly what proxy settings your Python environment detects, even when environment variables appear clean. This often reveals:

    • Registry-based proxy settings (Windows)
    • System-wide proxy configurations
    • Application-specific proxy settings that override environment variables

    The Fix: Explicitly Disable Proxies

    If you find unwanted proxy settings, you can force urllib (and applications that use it) to bypass proxies entirely:

    # Create a proxy handler with no proxies
    proxy_handler = urllib.request.ProxyHandler({})
    opener = urllib.request.build_opener(proxy_handler)
    urllib.request.install_opener(opener)

    Key Takeaway

    When troubleshooting network connectivity issues, don’t just rely on environment variables and system settings. Use your application’s own introspection capabilities to understand what it actually sees. This approach often reveals hidden proxy configurations that standard troubleshooting methods miss.

    Pro tip: Always check what your specific tools and libraries detect, not just what the system claims to be configured.

  • VSCode Python Run Button Not Working? Check Your Extension Version

    If you’re experiencing issues with VSCode where clicking the “Run” button in the top-right corner of a Python file doesn’t work properly, you might encounter these problems:

    1. No new terminal window is activated (the new windows are created in the background)
    2. The command “python xxx.py” doesn’t execute automatically

    Here’s a quick fix that often resolves this issue:

    Solution:

    Go to your VSCode Extensions panel and locate the Python extension. Check if you’re currently using the pre-release version of the Python extension. If you see “Pre-Release” indicated next to the Python extension, that’s likely the culprit.

    Simply switch from the pre-release version back to the stable release version. You can do this by clicking on the extension and selecting the option to install the release version instead.

    After switching to the release version, restart VSCode and try running your Python file again. The run button should now properly activate a new terminal and execute your Python script as expected.

  • 十個月兩個項目的低谷

    epr (番劇推薦)半年籌備獲得慘淡的點擊率之後,奮起兩個月,把 trending 做出來。這個禮拜 trending(熱門關鍵詞解說) 在 hackernews 和 reddit 收穫雙份失敗,這個禮拜就特別的 down 無比的 down. 加上老爸和老媽的矛盾導致老爸被 120 送到醫院,就更覺得世界的荒謬。到週日,實在就動不了。和 gemini 好好聊了一次,收穫許多 pua. 也是令人溫馨治愈, 感覺被 pua 到了(不要代入啊,喂!)!也獲得了實質性可以試驗的建議