省市区县边界 GeoJSON 怎么自动更新?定时任务+增量比对+版本化发布实战
维护全国省市区县geojson数据下载的这几个月里,我最大的教训不是清洗算法写得不好,而是"更新流程全靠手动"。民政部每季度都可能调一次行政区划代码,天地图边界偶尔推新版本,我每次都手动重新下载、比对、重新上传,偶尔还忘了刷缓存——直到有次线上用了三个月前的旧边界,用户私信问"这个县的界是不是不对",我才下定决心把整套更新做成流水线。
起初我也天真地想过"就加个定时任务每天重跑一遍不就完了",结果第一版就翻车:每天全量重传几百兆文件,既浪费带宽又容易把没变的数据也卷进回源,改一次线上累半天。后来才想明白,自动化的核心不是"每天跑",而是"只在有变化时才动线上"。这篇就把这套自动化的做法和踩过的坑讲一遍,适合手里捧着一坨全国省市区县区划边界数据下载回来、又不想靠人肉刷新的人。
一、先搭一个"版本化 + 校验"的基准目录
在做任何自动化之前,先想清楚一个事:你总得有一个"最新真相源"。我把它落成一个目录,每次更新都从别的源往这里合并,而不是就地改:
data/admin-baseline/
├── _source/ # 各源原始下载(天地图/民政部代码/OSM)
├── _staging/ # 合并清洗后的中间结果
├── latest/ # 当前对外发布的权威版
└── archive/2026-08/ # 上个月快照,用于 diff
我需要强调一点:这个基准目录不是给脚本用的摆设,它决定了你整个流水线能不能"可复现"。有了 manifest.json 里的版本号和 SHA-256,任何一次更新都能回放——哪一版跑出来的、从哪个源拉的、校验和是多少,全都对得上。这在系列-34、系列-49 里分别讲过校验和与代码口径,但放到自动化场景里它们才会真正发力:没有版本号和校验和,diff 脚本根本不知道该跟哪个基线比,也就谈不上"只传有变化的文件"。
二、定时拉取 + 增量比对,才是更新流水线的核心
流水线我拆成四个 stage,每个 stage 一个独立脚本,串在一条命令里:
| stage | 做什么 | 主要输出 |
|---|---|---|
| fetch | 按清单拉各源最新包到 _source | 原始包 + 下载时间戳 |
| merge | 按 adcode 主键合并去重、坐标对齐 | _staging 合并包 |
| diff | 与 latest 对比,生成变更清单 | changes.json(新增/消失/更名/边界变化) |
| publish | 仅上传有变化的文件 + 刷精准缓存 | R2 新文件 + 待刷新 URL 清单 |
diff 这一步复用 series-31 讲的那套"用 adcode 作主键 + 面积比"的比对脚本,只是加了一个判断:没有变化就不进 publish。这一步极其关键,它让整套链路做到"有更新才动线上",这也是为什么自动化更新不会每天无谓地重传几百兆文件。我遇到过的最尴尬状况是上游源根本没更新,我却因为"怕漏"每天全量刷,最后数据没变、缓存倒是被反复刷新,反而把正常的边缘命中率冲低了——加了 diff 判断之后,一天不变化就一天不动源站,页面访问延迟也稳下来。
// diff.mjs —— 只看变化文件,降低重传与误刷风险
const hasChange = Object.keys(added).length || Object.keys(removed).length || boundaryDiff.length > 0;
if (!hasChange) {
console.log('[diff] 无变化,跳过 publish');
process.exit(0); // 关键:无变化直接停,不上传、不刷缓存
}
三、更新之后,别让旧缓存把成果咬回去
数据更新到位只是第一步,真正折磨人的是边缘缓存。我的站是 ESA + Cloudflare Worker 双层的,更新文件后如果不刷对应 URL,用户在 CDN 边缘拿到的还是旧 JSON。这里有个红线我从 series-40 之后就一直守着:只做 files 级精准刷,绝不整站/hostname 全站刷——全站刷不但慢,还可能把其他没更新的数据也卷进一次新的回源,白白多耗流量。
我处理的逻辑很简单:diff 出来哪几个文件变了,就把那几个 URL 的列表传给刷新脚本,一条一条 files 级 purge,CF 层刷对应页 + sitemap,ESA 层单独用 Type:file 刷那几处。刷完再 curl -I 拉一下响应头,确认 Last-Modified / 校验和是新值才算数。
# 只刷有变化的那几个文件,绝不用 purge everything
CF_HOST="<你的CF托管域>"
curl -X POST "$CF_API/purge_cache" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-d "{\"files\":[\"$CF_HOST/data/110105.geojson\"]}" # 单条,精准
四、这套流水线帮我把"更新"从半天缩到十分钟
现在每次数据更新,我只要跑一条命令:拉源、合并、比对、上传、精准刷缓存,全自动。更新完还能把 changes.json 打一份出来,哪几个村的边界动了、哪个街道更名了,一目了然,直接拿去做 changelog 甚至发一条更新说明,对做全国村级geojson数据下载的读者也友好——村级数据更新最频繁,最需要这种自动化。如果你维护的是一整套全国省市区县geojson数据下载,这套流水线还能顺带把所有省份的 manifest 汇总成一张表,哪个省几天没更新一查便知,比翻单个文件踏实得多。
踩坑小结按重要程度排一下:第一,diff 一定要跑在 publish 之前,没变化就别动线上;第二,缓存刷新永远精准 files 级,全站刷是坑;第三,manifest 的版本号与校验和别省,它是自动化比对的地基。预览免费、导出付费(按次 ¥1.99)的节奏不变,数据仍走静态分发,这套流水线只是把"人肉更新"替换成"机器定时跑",让全国省市区县乡镇街道村 geojson 数据下载的数据始终保持在最新版本。