订阅这件事的麻烦不在于「订」,在于「之后」。一个 Spotify 家庭版、一个 VPS、两个域名、一个设计工具、三个 AI 服务——每一样单独看都不贵,加起来的数字往往比你以为的高出一截,而且每一笔的扣费日都散落在不同的日子里。
这类小工具最近冒出来不少。SubManager 是其中一个比较完整的:自托管的订阅记账本,前端 React、后端 Express、数据落在本地一个 SQLite 文件里,一条 Docker 命令起来。项目是 huhusmang/Subscription-Management,MIT 协议,902 星、99 fork。
一、仪表盘:先把「每个月花多少」这个数字摆出来
首页做得很克制,就四块:本月支出、本年支出、活跃订阅数,以及「最近付款」和「即将续费」两个列表,右边一条按分类的年度占比。
值得说的是数字的呈现方式:金额是双币的。截图里 YouTube 那行写着「¥57.23($7.99)」——原币是多少、折算成基币是多少,两个都在。这个细节很关键,因为订阅服务的定价币种五花八门,只记一个数字,你永远不知道自己实际花了多少人民币。
右边那条分类占比用的是水平条形图而不是饼图,扫一眼就知道哪一类在吃钱。截图里音乐流媒体占了 40%,这个比例对同时订了几个音乐服务的人来说应该不陌生。
二、订阅列表:每张卡就是一笔账
列表页是卡片式布局,一张卡四行信息:金额(原币 + 基币)、计费周期(月付/年付)、下次扣费日、扣款方式。标签颜色也做了区分——月付是黄色,年付是绿色,快到期的天数用红底标出来。
注意「扣款方式」这一栏:截图里出现了 Apple Pay、Google Play、微信支付、支付宝、信用卡。这说明它记的不只是「花了多少钱」,还有「从哪扣的」。对账的时候这个字段比金额更有用——信用卡泄漏要换卡,你得知道哪些订阅绑在那张卡上。
页面顶部的图标暗示了三件事:新增、导出、导入。README 里写明支持 CSV 和 JSON 两种格式的导入导出,这意味着你可以从别的记账工具迁过来,也能定期导出一份做备份——数据在 SQLite 里,但不必只能靠 SQLite 才能取出来。
三、真正的技术核心:一天里的两次心跳
订阅管理工具的功能表都差不多,差别藏在「什么时候做什么」里。翻了这个项目的调度器源码之后,我觉得这一块最值得单独讲——因为它把「改数据」和「发消息」拆成了两个独立任务,而且两个任务的时区来源还不一样。
凌晨两点那一次是维护任务。它做两件事:对到期且勾了「自动续费」的订阅,把下次扣费日往后推一个周期,并补一条支付记录;对到期但没勾自动续费的,把状态改成「已取消」。这一步会真的改数据。
上午九点那一次是提醒任务。它筛出「下次扣费日落在明天到 N 天后之间」的订阅(N 默认 7),去掉 N 天内已经提醒过的,然后往 Telegram 或邮件推送。这一步只读数据、只发消息。
拆开的好处很直接:自动续费的订阅不会在提醒里再催一遍,因为凌晨那次已经把它推到下一期了,九点再筛时它已经不在「未来七天内到期」的范围里。换句话说,提醒列表里出现的,都是需要你手动处理的那些。
这里有两个实现细节,部署前值得知道:
- 凌晨那次的时区是写死的东八区。代码里直接把调度时区设成了 Asia/Shanghai,注释还留了一句「可以根据需要调整」。也就是说,除非改源码,否则账期推进永远按北京时间算。
- 九点那次的时区来自设置页。而环境变量示例里给这个值填的是 UTC。两处默认值不一致,第一次部署最好自己去设置页确认一眼——否则「早上九点的提醒」可能拖到下午五点才到。
另外,筛选条件是「明天到 N 天后」,不含今天。今天到期的已经被凌晨那次处理掉了,逻辑上是自洽的,但也意味着:如果你今天新加了一条今天就到期的订阅,它不会在当天提醒你。
四、报表:月、季、年三层摊开
报表页把同一份数据切成三层:月、季、年。每一层给出总额、日均、笔数三个指标——「日均」这个算法挺有意思,它让「一个月 228 块」这种数字变得可比:日均 7 块多,约等于一杯咖啡。
底部是年度趋势柱图和分类占比饼图。柱图按年份分组、按分类上色,能看出某类支出是哪一年突然长出来的。
从工程角度看,这一页其实暴露了这个项目的定位:它是记账工具,不是预算工具。有汇总、有趋势、有占比,但没有预算上限、没有超支预警、没有「预测下个月会花多少」。想看趋势够用,想控预算得自己算。
五、多币种是怎么处理的
支持九种货币(美元、欧元、英镑、加元、澳元、日元、人民币、土耳其里拉、港币),基币可选。汇率每天自动更新,用的是第三方汇率接口,需要一个 API key 填进环境变量。
这意味着两件事。第一,不填 key 也能用,只是汇率要自己维护或者固定一个值。第二,汇率是「当天值」而不是「扣款当天值」:你的历史支出金额取决于你当时记账时的汇率,如果这家服务一年涨了价,报表上体现出来的差额里会混着汇率波动。对个人记账来说无所谓,但别拿这个数字去报销。
六、部署与安全:几处必须自己处理的
README 给的路线是 Docker Compose,镜像在 GitHub 容器仓库里,端口默认 3001,数据落在一个具名卷上。开箱即用是真的,但有几个地方得自己动手:
- 会话密钥必须自己生成。这个值用来签名登录 cookie,留空或者用示例值等于把门虚掩。README 建议用随机长字符串生成。
- 管理员密码要先变成哈希。首次启动时程序会把明文密码派生成一个哈希并打印出来,之后应该把哈希写进配置、把明文那行删掉。这一步别跳过。
- 前面挡一层网关时要打开一个开关。项目里有专门的转发信任开关,不开的话服务端拿到的客户端地址全是网关的,会话 cookie 的安全属性判断也会跟着错。
- 所有接口都要登录。这一条写在 README 的认证说明里——不是「部分接口需要登录」,是全部。这对个人自用是好事,但也意味着没法做只读分享。
技术上它用的是基于会话的登录(密码用 bcrypt 哈希,成本因子建议 12 以上)、HTTP-only cookie、Express 5 加 better-sqlite3。前端是 React 18 + TypeScript + Vite,样式用 Tailwind 配 shadcn/ui,图表用 Recharts,状态用 Zustand。这套组合是目前自托管项目里最主流的一档,没有惊喜,也不会踩坑。
七、它明确不做什么
把边界说清楚,比罗列功能更有用:
- 它不替你扣款,也不替你取消。「自动续费」这个开关记录的是这笔订阅在对方那边是不是自动扣款,程序据此推进你的账期。真要退订,还得你自己去对方网站操作。
- 它不会自动导入账单。没有对接邮箱、没有解析信用卡流水、没有读支付宝微信账单。数据要么手输,要么 CSV 导入。
- 它不管优惠券和试用期。首月半价、年付折扣、学生价这些,只能靠你在备注里写清楚,报表不会自动摊薄。
- 通知只有两条路。Telegram 和邮件。没有 Webhook、没有企业微信、没有服务号推送。
- 它是单用户的。有登录、有会话,但没有多用户、没有共享账本、没有权限区分。一个人一个实例。
八、适合谁
适合:订阅数量超过十个、每月的扣费日已经记不清的人;在意数据不出本机、不想把账单交给第三方 SaaS 的人;愿意花十分钟起一个容器、之后基本不用管的人。
不适合:想要「到期自动帮我退订」的人(没有这种工具能真做到);需要多成员共享家庭账本的人;想靠它做预算管控的人;以及完全不想碰命令行和环境变量的人——虽然它界面友好,但首次部署还是得改几个配置。
整体印象:这是一个边界很清楚的小工具。它的功能表不花哨,代码结构也规规矩矩(调度、服务、控制器分层,前端按页面拆),亮点全在「把一件小事做对」上——双币记账、扣款方式字段、两个调度器分工、导入导出兜底。对自托管爱好者来说,这类项目比功能大而全的更耐用,因为它不试图替你解决所有问题。
本文根据项目公开仓库 huhusmang/Subscription-Management 的 README、环境变量说明与调度器源码(续费维护、通知调度)整理撰写。前三张界面截图为仓库 docs/images 目录下的官方截图,第四张调度分工图为依据源码逻辑自绘。文中星标数、许可协议、默认端口与时区设定均为撰写时仓库所载,项目仍在活跃更新,请以仓库当前状态为准。本文不含外链入口与推广内容。
本文整理自公开资料并做重写,原始出处:https://github.com/huhusmang/Subscription-Management。文中配图已去除站点叠加水印与推广信息;版权归原作者所有。



