从一个小程序到一座工具箱:uni-app 分包与 Flask 微服务实践

当一个小程序从单一的金价查询逐渐加入计分板、倒数日、汇率、番茄钟和健康打卡时,真正困难的不是再写一个页面,而是让第十七个工具仍像第一个一样容易接入、独立运行和安全发布。

一、问题不是"页面变多了"

「零碎百宝箱」最初是一个金价排行榜,后来逐步容纳了 17 个工具。它们的运行形态并不一致:

  • 瞬间听力、聚会决定器、单位换算等工具可以纯前端运行;
  • 台球、倒数日等工具要求离线可用,同时希望换机后恢复历史;
  • 金价、油价和汇率依赖定时更新的服务端数据;
  • 麻将计分以联机房间为主,服务端才是事实来源;
  • 喝水提醒还需要处理微信一次性订阅消息和后台调度。

如果所有功能都堆在一个目录、共享任意状态并请求任意接口,新功能越多,主包体积、命名冲突和回归范围都会同步增长。这个项目的解法是先规定边界,再允许工具在边界内自由选择实现。

二、总体结构:一个壳,多种工具形态

flowchart LR subgraph MP[微信小程序] Home[主包:首页] Registry[工具注册表] Core[core 基础设施] Design[design 设计系统] P1[行情类分包] P2[计分类分包] P3[纯前端分包] P4[记录类分包] Home --> Registry P1 --> Core P2 --> Core P3 --> Design P4 --> Core end Core --> Gateway[Nginx 路径网关] Gateway --> Auth[统一认证服务] Gateway --> Dedicated[专属 Flask 服务] Gateway --> Records[通用 records 服务实例] Dedicated --> DB[(MySQL)] Records --> DB

客户端主包只保留首页和公共能力。每个工具位于 packages/<tool-slug>/,并在 pages.json 中注册为分包。首页不硬编码卡片,而由 config/tools.js 的注册表驱动:名称、分类、图标、主题、入口和启用状态都在一处声明。

这种结构带来两个直接收益:

  1. 用户只在进入某个工具时下载对应分包,主包不会随着工具数量线性膨胀。
  2. 新工具的接入点是显式的:建分包、注册页面、登记工具;不会偷偷依赖另一个工具的内部代码。

项目进一步规定,分包之间禁止互相 import,只能依赖主包的 core/design/。这条规则看似严格,却能防止"倒数日复用了健康打卡的一段代码,后来两个分包必须同时发布"的隐形耦合。

三、用工厂函数统一网络边界

每个服务在网关下拥有独立前缀。分包只需要声明自己的服务名:

js 复制代码
import { createRequest } from '../../core/request';

const request = createRequest('gold-price');
const res = await request({ url: 'brands', method: 'GET' });

公共请求层完成三件事:

  • 将相对路径拼为 /<service>/api/v1/<path>
  • 从本地读取 JWT 并注入 Authorization: Bearer ...
  • 收到 401 后静默登录,并将原请求重放一次。

微信登录本身也做了"单飞"控制。多个并发请求同时发现 token 过期时,共享同一个 loginPromise,而不是同时调用多次 wx.login

js 复制代码
let loginPromise = null;

export function wxLogin() {
  if (loginPromise) return loginPromise;
  loginPromise = doLogin().finally(() => {
    loginPromise = null;
  });
  return loginPromise;
}

服务端由认证服务用微信临时 code 换取 openid,再签发只包含 openid 和过期时间的 HS256 JWT。所有业务服务共享签名密钥,因此可以本地验签,不必让每个请求再次穿过认证服务。

这是一种适合小型微服务系统的取舍:认证入口集中,鉴权执行分散。它减少了一次内部网络调用,但也意味着密钥轮换必须让所有服务同步更新。

四、本地优先不是"有缓存就行"

计分、倒数日和健康记录发生在球桌、牌桌或日常打卡中。网络短暂不可用不应该阻塞一次记分,所以项目把本地存储定义为主存储,云端负责异步备份。

所有分包通过命名空间存储避免 key 冲突:

js 复制代码
const storage = createStorage('billiards');
storage.set('settings', settings);
// 实际写入 key:billiards:settings

更通用的记录同步由 createRecordSync(service) 提供。一次同步分为三个阶段:

sequenceDiagram participant L as 本地记录 participant Q as 待删队列 participant S as records 服务 L->>S: 1. 补交历史删除 S-->>Q: 仅保留失败项 L->>S: 2. 拉取云端记录 S-->>L: 按 clientKey 合并,updatedAt 新者胜 L->>S: 3. 分批推送未同步记录 S-->>L: 返回幂等保存结果

这里最容易漏掉的是"删除"。如果用户离线删除一条记录,只从本地数组移除,下一次拉取时云端旧数据会再次出现。因此删除动作会先写入 pendingDeletes,云端确认删除后才移出队列。若用户用同一个 clientKey 重建记录,则撤销对应删除意图。

服务端按 (openid, client_key) 幂等 upsert。客户端主导身份,服务端负责备份与跨设备恢复,双方用 updated_at 做简单的最后写入者胜出。它并不试图解决协作文档级别的合并冲突,但足以覆盖个人记录工具的真实场景。

五、后端为什么同时存在专属服务和通用服务

给每个工具复制一套 Flask CRUD 很快会产生大量相似代码;反过来,把所有业务塞进一个服务,又会让金价爬虫、麻将房间和喝水提醒互相影响。

项目采用两种后端形态:

  • 专属服务:金价、油价、汇率、台球、麻将等具有独特数据模型或调度逻辑的工具;
  • 通用 records 服务:球拍计分、棋牌计分、倒数日、健康记录和自由记分复用同一份代码,但以不同容器、数据库名和服务名运行。

通用服务复用实现,不共享数据。专属服务保留业务表达力,不被"万能记录表"限制。这比"一工具一套复制代码"或"所有工具共用一张表"都更平衡。

Docker Compose 负责启动 MySQL 和各业务容器,Nginx 按路径转发:

text 复制代码
/auth/          -> auth:5000
/gold-price/    -> gold-price:5000
/mahjong/       -> mahjong:5000
/health-log/    -> health-log:5000

数据库容器还针对小内存服务器关闭了 performance schema,并缩小 InnoDB buffer pool。架构设计不仅要画出服务边界,也要尊重实际机器的资源上限。

六、设计系统只统一外壳

工具箱容易走向两个极端:要么每个页面完全不同,像跳进了另一个应用;要么为了统一而抹掉工具个性。

项目把颜色、间距、圆角等基础变量集中在 design/tokens.scss,通用页面头、返回按钮、图标和工具卡使用 tb- 前缀组件。工具内部仍可以保留自己的交互语言,例如金价品牌卡的行情感、听力测试的游戏感、横屏计分板的大字号操作感。

统一的是导航、反馈和基础尺度,不是所有页面必须长成同一张卡片。

七、新工具如何选择落点

接入前先判断数据归属,而不是先复制最近的目录:

工具特征 推荐形态 参考实现
无持久数据、无需联网 纯前端分包 瞬间听力、单位换算
个人记录、离线必须可用 createRecordSync + records 服务 倒数日、健康记录
业务模型复杂、仍以本地为主 专属 store + 专属备份 API 台球计分
多人共享实时状态 服务端主存 + 版本控制 麻将计分
周期采集公共行情 专属服务 + 调度器 + 缓存展示 金价、油价、汇率

这个判断比技术选型本身更重要。离线个人记录和多人联机房间虽然都叫"同步",一致性目标完全不同,不应被同一套抽象强行覆盖。

八、这套架构的边界

当前方案适合中小规模工具箱,但仍有明确的演进点:

  • JWT 使用共享对称密钥,服务数量继续增长时可考虑非对称签名和密钥轮换;
  • 最后写入者胜出适合个人记录,不适合多人同时编辑;
  • APScheduler 随应用进程运行便于部署,多进程或多副本后应拆为独立 worker;
  • 各服务独立容器提高隔离性,也增加部署和可观测性成本,需要统一日志与指标。

好的架构不是一开始就上最复杂的组件,而是让每种复杂度只出现在真正需要它的工具里。这个项目最值得复用的并非某个框架,而是"主包提供稳定能力,分包表达业务差异;简单工具保持简单,复杂工具拥有自己的边界"这一原则。

相关推荐
鲁Q同志3 小时前
微信小程序【uni-file-picker文件预览】
微信小程序·小程序
他叫自己MR张3 小时前
uni-app 应用启动报uni-ad业务状态异常(-9002)【已解决】
uni-app
2601_963869953 小时前
【计算机毕业设计】基于微信小程序的拼车服务系统设计与实现
微信小程序·小程序·课程设计
2501_915909064 小时前
iOS应用性能监控 Instruments工具与崩溃日志分析完整指南
android·ios·小程序·https·uni-app·iphone·webview
2501_9159184120 小时前
详解iOS App上架至App Store的全流程步骤与注意事项
android·macos·ios·小程序·uni-app·cocoa·iphone
anyup21 小时前
终于在今天入选了 Gitee GVP,这真值得庆祝~
前端·uni-app·开源
虚惊一场1 天前
撤销为什么只需要 pop:用事件重放实现离线台球计分
微信小程序·uni-app
beichenxingzhui1 天前
茶饮品牌“新品口感盲测问答”:适合微信小程序与互动页的工具 Top 3
微信小程序·小程序
Cry丶1 天前
【已解决】adb devices 能识别手机,但 HBuilderX 搜索不到设备的避坑指南
android·adb·uni-app·hbuilderx·真机调试