Skip to content

技术架构

「奶茶仙人」基于多端分离架构设计,前端应用均通过 pnpm workspaces 统一管理,并抽离了通用的类型定义与核心算法。


技术选型

模块技术选型说明与职责
微信小程序TypeScript + 原生小程序框架 + Vant当前核心交付端,负责用户日常的热量打卡、预算看板展示。
H5 Web 端Vue 3 + Vite + Vant管理端与预览端,负责复杂的数据统计与管理功能。
服务端 APIFastAPI + SQLAlchemy + SQLitev0.5.0 后端标准接口,采用 SQLite 进行饮品及用户账户的轻量化存储。
共享逻辑库TypeScript + Vitest提取评级算法、统计方法为独立共享包,保证多端逻辑一致。

项目组织结构 (Monorepo)

通过模块化 Monorepo 结构,小程序和 Web 端能无缝共用底层的数据结构和测试过的工具类:

text
drink-budget/
├── apps/
│   ├── miniapp/                # 微信小程序(Vant UI + TS 开发)
│   ├── web/                    # H5 网页(Vue 3 + Vite 驱动)
│   └── api/                    # 后端 API 服务(FastAPI 提供 REST 接口)
├── packages/
│   ├── shared/                 # 跨端共享业务逻辑包(编译后分发)
│   │   ├── src/
│   │   │   ├── index.ts        # 营养评级算法、日历统计、单位换算
│   │   │   └── index.test.ts   # Vitest 测试套件
│   │   └── package.json
│   └── types/                  # 跨端强类型契约
│       ├── src/
│       │   └── index.ts        # 饮品、历史记录及配置的 TypeScript 类型
│       └── package.json
├── docs/                       # 本地详细设计文档
└── package.json                # 工作区配置

核心数据契约

我们在 packages/types 中定义了强类型,这是前端、小程序以及后端 API 进行网络请求与持久化存储时的核心数据契约。

1. 奶茶饮品 (MilkTeaProduct)

typescript
export interface MilkTeaProduct {
  id: string;
  name: string;
  brand: string;
  category: string;
  size: number;              // 容量,单位 ml
  calories: number;          // 热量,单位 kcal
  sugar: number;             // 糖分,单位 g
  fat: number;               // 脂肪,单位 g
  protein: number;           // 蛋白质,单位 g
  fiber: number;             // 膳食纤维,单位 g
  sodium: number;            // 钠,单位 mg
  grade: 'A' | 'B' | 'C' | 'D'; // 新加坡营养评级标签
  isActive: boolean;         // 上架或在售状态
}

2. 消费打卡记录 (ConsumptionRecord)

typescript
export interface ConsumptionRecord {
  id: string;
  productId: string;
  // 核心饮品快照:防止由于后台商品配方、热量更改,导致历史摄入数据发生漂移
  product: Pick<MilkTeaProduct, 'id' | 'name' | 'brand' | 'calories' | 'grade' | 'size'>;
  consumedAt: number;        // 打卡时间戳
  quantity: number;          // 饮用杯数
}

3. 热量预算 (CalorieBudget)

typescript
export interface CalorieBudget {
  daily: number;             // 每日最大热量预算 (kcal)
  weekly: number;            // 每周最大热量预算 (kcal)
  unit: 'kcal' | 'kJ';       // 热量单位
  updatedAt: number;         // 配置更新时间戳
}

评级引擎与逻辑共享

1. 评级判定逻辑

评级计算器被编写在 packages/shared/src/index.ts 中,通过以下条件计算得出:

typescript
export type Grade = 'A' | 'B' | 'C' | 'D';

export function calculateGrade(calories: number, sugar: number, fat: number): Grade {
  // A级:低卡无糖
  if (calories < 150 && sugar < 5 && fat < 3) return 'A';
  // B级:中低卡微糖
  if (calories < 250 && sugar < 10 && fat < 5) return 'B';
  // C级:中热量半糖
  if (calories < 350 && sugar < 20 && fat < 8) return 'C';
  // D级:高卡重糖重奶
  return 'D';
}

2. 多端数据缓存与同步策略

由于小程序需要支持离线使用,数据存储机制如下:

  • 本地 Storage 缓存:饮品数据与用户记录首选缓存在设备本地,提供毫秒级无网渲染体验。
  • 服务端兜底同步:在网络通畅时,本地缓存与 SQLite 服务端进行异步数据对齐。
  • 测试保障packages/shared 下的逻辑在发布前会通过 index.test.ts 中针对边界条件的单元测试进行保障:
    typescript
    test('calculateGrade should follow bottle-neck effect', () => {
      // 即使热量极低,但脂肪超标,也必须落入 D 级
      expect(calculateGrade(50, 2, 9)).toBe('D');
    });