logo

Timeless

花开成景,花落成诗

躺了一年半,API 项目终于更新了

躺了一年半,API 项目终于更新了

十一月 10, 2026 3 2098 5 分钟

1.5.0 之后躺了一年半,这次一口气把 TypeScript、自动文档、二十万行数据都处理了,顺手补了 21 个接口,也跟 4 个老接口说了再见

开头先说两件事:

一是感谢。这次重构能啃下来,得多谢 GPT 大人——类型声明、迁移里那些重复劳动、生成器的主体,基本都是它陪着写的,我主要负责定规则、看结果。以前一个人磨一两个月的事,现在一个周末能推掉大半,时代确实变了~

二是道歉。1.6.0 砍掉了哔哩哔哩、QQ 音乐、番剧、碧蓝航线舰船这四个接口,还在用它们的朋友,对不住啦~ 数据源撑不住、维护成本又高,硬留着只会让文档和实现一起烂掉,与其半死不活地挂着,不如干脆说再见。如果确实还有使用场景,欢迎提个 issue 说说,说不定哪天换个数据源它又回来了。

说回正题。没想到这个项目还能再折腾一次。上一个版本 1.5.0 还是去年三月发的,之后基本就躺平了,偶尔想起来加两个接口,文档也懒得同步,慢慢就变成了"能跑就行"的状态~这次趁着有点动力,把一直想做的三件事一起做了:全量换 TypeScript、接口文档改成从代码生成、把仓库里那堆几百兆的二进制搬出去。顺带补了 21 个接口,也砍掉了 4 个。

为什么要改

  1. 改接口全靠记性:纯 JavaScript 写的,参数收什么、返回什么只能翻代码。加个字段,controller、logic、文档三处都要改,漏一处就是线上事故。
  2. 文档和代码是两张皮:以前文档在 Apifox 里手工维护,写完接口再去补一条。改代码忘了同步,文档就开始安静地骗人,而且骗的往往是我自己。
  3. 仓库越来越重:数据库备份、IP 库、Live2D 模型、宠物图片全在 Git 里,clone 一次要等半天,大部分部署其实根本用不上。

这三个都不致命,但每天都在磨人,攒到最后干脆一起解决。

TypeScript

项目用的是 ThinkJS,一个有点年头的框架,写法是 export = class extends Base,没有装饰器,路由靠目录约定。所以不能直接把 .js 改名成 .ts 就跑。做法是按"最小可运行"推进:先补 tsconfig.json 和第三方声明,再按 config → utils → model → logic → controller 的顺序一个个文件迁,每迁完一批就跑一遍 npm run typecheck。整个过程塞在一个 commit 里,97 个文件,+8566 / −478。

踩过的坑大概这几个:

  • lunar-javascript、ip2region 都没有类型声明,手写了两份 .d.ts 丢进 src/types/。老库的 API 面很宽,只声明用到的部分,够用就行。
  • ThinkJS 的 this.mongo()、this.get() 都不在类型里,给 think.Controller 打了份补丁声明,再把取参数包成 getApiQuery<T>(),控制器里就有明确类型了。
  • 开了 strict 之后,modelInstance 这种运行时注入的属性必须 declare,不然 tsc 会认为它和父类冲突。

开发体验上收益最大的是 tsc-watch:保存即编译,编译成功才重启服务,编译失败继续跑旧代码。比改出个语法错误整个进程就挂掉舒服太多了~

文档

这块是这次最有意思的。

以前是在 Apifox 里手写接口,写完还得再维护一遍。现在 scripts/generate-openapi.cjs 直接用 TypeScript Compiler API 解析源码,规则很朴素:

  • Controller 里每个 *Action 就是一个接口,路径按 ThinkJS 约定,router.ts 里写死的别名也能识别;
  • Action 上的 @summary、@description 就是接口标题和说明;
  • 参数名和说明来自 src/types/*.ts 里属性的 JSDoc,必填、范围、枚举来自同名 Logic 的 scope;
  • this.success(data) 的实参类型决定响应结构;
  • 标了 @internal 的(定时任务、资源更新)不进文档。

所以源码里写:

/**
 * @summary 月历摘要
 * @description 按日期升序返回指定月份的公历序号、农历月日、节日节气及简要黄历……
 */
calendarAction() {
  const query = this.getApiQuery<TimeCalendarQuery>();
  // ...
}

构建的时候就变成 OpenAPI 里的一个 path,带 summary、description 和两个可选参数。以后加接口只要写 Controller、写类型、写校验,文档自己就出来了,不用再打开任何文档工具。

三个刻意的设计:

  • 不做运行时探测。生成器不会去请求线上接口猜返回结构,猜出来的文档比没有文档更糟。
  • 生成失败就构建失败。生成前先跑一遍 tsc 诊断,类型有问题直接退出,文档不会带病发布。
  • 不追求 100%。请求体、动态路由、helper 的副作用目前都不解析,遇到就显式报错,让人去补类型,而不是偷偷输出一个错的 schema。

顺带把 Swagger UI 也搬进了仓库,www/swagger/ 下就是 swagger-ui-dist 的 JS 和 CSS,首页直接用本地文件渲染,不依赖任何 CDN。

这里澄清一下:Apifox 没有弃用,我还是天天在用的,调试和分享接口它最顺手。变的只是文档的源头,从"手工维护的 Apifox 项目"变成了代码。生成的 /swagger/openapi.json 还能直接导回 Apifox,两边不会再漂移~

数据搬家

1.6.0 的 diff 是 +22,031 / −206,117,删掉的那些不是代码,是数据。

数据库 BSON 备份、ip2region 的 xdb、Live2D 模型、宠物图片,全部挪到 GitHub Releases,用 assets-YYYY.MM.DD 单独打标签,跟应用版本解耦。源码仓库只留代码、配置、小 JSON、Swagger 和资源清单。

配套写了两个脚本:

  • npm run assets:pack:复制现成的数据库备份,打包三类静态资源,生成 manifest.json 和 SHA256SUMS.txt,再自己校验一遍解压后的每个文件;
  • npm run assets:install:校验 SHA256、拒绝路径越界/符号链接/清单外文件,通过了才装进 www/。支持 --only 只装一个、--overwrite 覆盖(覆盖前把旧目录备份到 deploy/resource-backups/)。

数据库始终不自动还原,导入导出还是交给部署的人用 mongorestore 手动来。工具只负责把文件搬到位,不碰线上数据,这条线我觉得得划清楚。

接口

加了 21 个,比较有意思的几个:

  • 月历摘要(/time/calendar):一次返回整月的公历、农历、节日、节气、宜忌、工作日标记。麻烦的不是算,是"哪一年有调休数据",库没收录当年安排的时候 isWorkday 得返回 null,不能瞎猜 true 或 false。
  • 八字排盘(/time/bazi):四柱、藏干、十神、纳音都是确定性的历法计算。旺衰、喜用神、运势这类主观内容一律不做,文档里也写清楚"民俗娱乐参考"。
  • 历史上的今天(/time/history):公元前用负数、没有公元 0 年、2 月 29 日要能查,这几个边界讨论的时间比写代码还长。
  • 词典四件套(/dictionary/word|riddle|xiehouyu|couplet):汉字、谜语、歇后语、对联,统一分页,保留数据源里的多条释义和原始标点。

砍掉的也有四个:

  • 哔哩哔哩(/bilibili):上游数据不稳定,经常整段拉不到。
  • QQ 音乐(/music/tencent):依赖第三方音乐接口,维护成本太高。
  • 番剧(/bangumi):番剧数据源停更了,留着也是死数据。
  • 碧蓝航线舰船(/azurlane/ship):舰船数据体量大、更新又勤,整体下线。

原因各不相同,本质一样:公共服务的成本是持续的,上游一变、数据一停,接口就从资产变成负债。Spine 模型留着了,但资源改成从上游项目自己下,版权和数据都不该由这个仓库来背。

总结

重构这事,如果目标只是"把 JS 改成 TS",我大概改到一半就没劲了。这次是"TS + 文档生成 + 数据分离"一起做,每一步都能立刻看到回报:类型让重构有安全感,文档生成让类型有了用处,资源分离让仓库终于能正常 clone。成就感大概就是这么一点点攒出来的~

还有个感受是,删功能比加功能更需要说明,应该提前发公告逐条写了原因和替代方案,并在文档标注将废弃以及废弃时间。毕竟这是公开给大伙用的,下次一定注意。(还有下次?)

项目地址:https://github.com/Spacesless/api,文档:https://api.timelessq.com

恰饭区

广告位招租

评论

Power by ThinkJS + Nuxt.js,已经稳稳地存活了 8年202天 十年之约虫洞

Copyright © 2018 - 2026 Timeless. All rights reserved.桂ICP备18003656号-1公网安备桂公网安备45092102000147号