--- slug: tech-v1-2-advanced --- # 「伴享」技术方案 V1.2 — 进阶架构 **版本**: V1.2 **对应PRD**: PRD-V1.2-功能丰富版 **更新日期**: 2026-02-16 --- ## 1. 架构变更概述 V1.2引入两个重大变更:**健康数据架构**(时序数据+预警引擎)和**子女端小程序**(微信小程序+独立API层)。 ### 1.1 变更影响矩阵 | 层级 | 变更 | 说明 | |------|------|------| | 数据库 | 新增8张表 | 健康设备/记录/预警/报告+子女用户+关联+社区内容 | | API | 新增3组端点 | /health, /child, /posts | | 新增服务 | 健康数据同步Worker | 定时拉取手环数据 | | 新增服务 | 预警检测引擎 | 实时异常检测 | | 新增客户端 | 微信小程序(子女端) | Taro + React | | 新增服务产品 | 候鸟/文教/设备 | 复用service_products表 | ### 1.2 架构图更新 ``` ┌───────────────────────────────────────────────────────┐ │ 客户端层 │ │ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ │ │ Flutter APP │ │ 子女端小程序 │ │ 代付H5 │ │ │ │ (父母端) │ │ (Taro+React) │ │ (Vue3) │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬─────┘ │ └─────────┼─────────────────┼─────────────────┼─────────┘ │ │ │ ┌─────────▼─────────────────▼─────────────────▼─────────┐ │ API网关 (Nginx) │ │ /api/v1/* → 主APP API │ │ /api/child/* → 子女端API │ │ /api/h5/* → H5页面API │ └─────────┬─────────────────────────────────────────────┘ │ ┌─────────▼─────────────────────────────────────────────┐ │ Node.js 业务服务层 │ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │用户 │ │活动 │ │AI │ │医疗 │ │支付 │ │ │ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │队长 │ │会员 │ │健康⭐│ │子女⭐│ │社区⭐│ │ │ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │ └─────────┬─────────────────────────────────────────────┘ │ ┌─────────▼─────────────────────────────────────────────┐ │ 后台Worker层(新增) │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ 健康数据同步Worker│ │ 预警检测引擎 │ │ │ │ (每30分钟/每小时) │ │ (实时处理) │ │ │ └─────────────────┘ └─────────────────┘ │ └───────────────────────────────────────────────────────┘ ``` --- ## 2. 健康数据架构 ### 2.1 数据流 ``` 华为健康API / 小米运动API ↓ (OAuth2 + 定时拉取) ┌─────────────────────┐ │ 健康数据同步Worker │ ← Bull队列 + Redis │ 每30分钟执行一次 │ └──────────┬──────────┘ ↓ 写入 ┌─────────────────────┐ │ health_records表 │ ← 时序数据 └──────────┬──────────┘ ↓ 触发 ┌─────────────────────┐ │ 预警检测引擎 │ │ 检查阈值 → │ │ ├── 正常 → 跳过 │ │ └── 异常 → │ │ ├── 写入alerts │ │ ├── Push用户 │ │ └── Push子女端 │ └─────────────────────┘ ``` ### 2.2 增量DDL ```sql -- =========================== -- V1.2 增量DDL -- =========================== -- 1. 健康设备绑定 CREATE TABLE health_devices ( id BIGSERIAL PRIMARY KEY, user_id BIGINT REFERENCES users(id), brand VARCHAR(20) NOT NULL, device_model VARCHAR(50), device_id VARCHAR(100), auth_token_encrypted TEXT, refresh_token_encrypted TEXT, sync_interval INTEGER DEFAULT 60, last_sync_at TIMESTAMP, status VARCHAR(20) DEFAULT 'active', created_at TIMESTAMP DEFAULT NOW() ); -- 2. 健康记录(时序数据,按月分区) CREATE TABLE health_records ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, record_type VARCHAR(20) NOT NULL, value JSONB NOT NULL, recorded_at TIMESTAMP NOT NULL, source VARCHAR(20) DEFAULT 'device', created_at TIMESTAMP DEFAULT NOW() ) PARTITION BY RANGE (recorded_at); -- 按月创建分区(示例) CREATE TABLE health_records_2026_04 PARTITION OF health_records FOR VALUES FROM ('2026-04-01') TO ('2026-05-01'); CREATE TABLE health_records_2026_05 PARTITION OF health_records FOR VALUES FROM ('2026-05-01') TO ('2026-06-01'); CREATE INDEX idx_health_user_type_time ON health_records(user_id, record_type, recorded_at DESC); -- 3. 健康预警 CREATE TABLE health_alerts ( id BIGSERIAL PRIMARY KEY, user_id BIGINT REFERENCES users(id), alert_type VARCHAR(50), record_id BIGINT, value JSONB, threshold JSONB, severity VARCHAR(20) DEFAULT 'warning', notified_user BOOLEAN DEFAULT FALSE, notified_family BOOLEAN DEFAULT FALSE, acknowledged BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT NOW() ); -- 4. 健康周报 CREATE TABLE health_reports ( id BIGSERIAL PRIMARY KEY, user_id BIGINT REFERENCES users(id), report_type VARCHAR(20) DEFAULT 'weekly', period_start DATE, period_end DATE, content JSONB, created_at TIMESTAMP DEFAULT NOW() ); -- 5. 子女用户 CREATE TABLE child_users ( id BIGSERIAL PRIMARY KEY, wx_openid VARCHAR(50) UNIQUE NOT NULL, wx_unionid VARCHAR(50), nickname VARCHAR(50), avatar_url TEXT, phone VARCHAR(11), created_at TIMESTAMP DEFAULT NOW() ); -- 6. 子女-父母关联 CREATE TABLE child_parent_links ( id BIGSERIAL PRIMARY KEY, child_id BIGINT REFERENCES child_users(id), parent_user_id BIGINT REFERENCES users(id), relationship VARCHAR(20) DEFAULT 'child', notification_enabled BOOLEAN DEFAULT TRUE, alert_phone VARCHAR(11), linked_at TIMESTAMP DEFAULT NOW(), UNIQUE(child_id, parent_user_id) ); -- 7. 社区动态 CREATE TABLE posts ( id BIGSERIAL PRIMARY KEY, user_id BIGINT REFERENCES users(id), content TEXT, images TEXT[], topic_id BIGINT, location VARCHAR(100), like_count INTEGER DEFAULT 0, comment_count INTEGER DEFAULT 0, status VARCHAR(20) DEFAULT 'active', created_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE post_likes ( post_id BIGINT REFERENCES posts(id), user_id BIGINT REFERENCES users(id), created_at TIMESTAMP DEFAULT NOW(), PRIMARY KEY(post_id, user_id) ); CREATE TABLE post_comments ( id BIGSERIAL PRIMARY KEY, post_id BIGINT REFERENCES posts(id), user_id BIGINT REFERENCES users(id), content TEXT, reply_to_id BIGINT, status VARCHAR(20) DEFAULT 'active', created_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE topics ( id BIGSERIAL PRIMARY KEY, name VARCHAR(50) UNIQUE, description TEXT, post_count INTEGER DEFAULT 0, is_official BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT NOW() ); -- 8. 通用服务产品表(候鸟/文教/设备) CREATE TABLE service_products ( id BIGSERIAL PRIMARY KEY, provider_id BIGINT REFERENCES service_providers(id), category VARCHAR(50), subcategory VARCHAR(50), title VARCHAR(200), description TEXT, images TEXT[], price DECIMAL(10,2), price_unit VARCHAR(20), commission_rate DECIMAL(4,2), location VARCHAR(100), available_from DATE, available_to DATE, stock INTEGER, sales_count INTEGER DEFAULT 0, rating DECIMAL(2,1) DEFAULT 5.0, status VARCHAR(20) DEFAULT 'active', created_at TIMESTAMP DEFAULT NOW() ); ``` ### 2.3 健康数据同步Worker ```javascript // workers/health-sync.js const Queue = require('bull'); const healthSyncQueue = new Queue('health-sync', process.env.REDIS_URL); // 每30分钟同步一次 healthSyncQueue.process(async (job) => { const { userId, brand, authToken } = job.data; let data; if (brand === 'huawei') { data = await fetchHuaweiHealthData(authToken); } else if (brand === 'xiaomi') { data = await fetchXiaomiHealthData(authToken); } // 写入数据库 for (const record of data.records) { await db.query( `INSERT INTO health_records (user_id, record_type, value, recorded_at, source) VALUES ($1, $2, $3, $4, 'device') ON CONFLICT DO NOTHING`, [userId, record.type, record.value, record.timestamp] ); } // 触发预警检测 await alertEngine.check(userId, data.records); }); // 调度:为每个绑定设备的用户创建定时任务 async function scheduleHealthSync() { const devices = await db.query( 'SELECT * FROM health_devices WHERE status = $1', ['active'] ); for (const device of devices.rows) { healthSyncQueue.add( { userId: device.user_id, brand: device.brand, authToken: device.auth_token_encrypted }, { repeat: { every: device.sync_interval * 60 * 1000 } } ); } } ``` ### 2.4 预警检测引擎 ```javascript // services/alert-engine.js const THRESHOLDS = { blood_pressure: { systolic: { min: 85, max: 150 }, diastolic: { min: 55, max: 100 } }, heart_rate: { min: 50, max: 120 }, blood_sugar: { min: 3.5, max: 7.0 } }; async function checkAlerts(userId, records) { for (const record of records) { const threshold = THRESHOLDS[record.type]; if (!threshold) continue; let isAlert = false; let alertValue = {}; if (record.type === 'blood_pressure') { const { systolic, diastolic } = record.value; if (systolic > threshold.systolic.max || systolic < threshold.systolic.min || diastolic > threshold.diastolic.max || diastolic < threshold.diastolic.min) { isAlert = true; alertValue = { systolic, diastolic }; } } else if (record.type === 'heart_rate') { const { bpm } = record.value; if (bpm > threshold.max || bpm < threshold.min) { isAlert = true; alertValue = { bpm }; } } if (isAlert) { // 写入预警记录 const alert = await db.query( `INSERT INTO health_alerts (user_id, alert_type, value, threshold, severity) VALUES ($1, $2, $3, $4, $5) RETURNING id`, [userId, record.type, alertValue, threshold, determineSeverity(record.type, alertValue, threshold)] ); // 通知用户 await pushNotification(userId, { type: 'HEALTH_ALERT', title: '健康提醒', message: formatAlertMessage(record.type, alertValue), data: { alertId: alert.rows[0].id } }); // 通知子女 const childLinks = await db.query( 'SELECT child_id FROM child_parent_links WHERE parent_user_id = $1 AND notification_enabled = true', [userId] ); for (const link of childLinks.rows) { await pushChildNotification(link.child_id, { type: 'PARENT_HEALTH_ALERT', parentUserId: userId, alertType: record.type, value: alertValue }); } } } } ``` --- ## 3. 子女端小程序架构 ### 3.1 技术选型 | 技术 | 选择 | 理由 | |------|------|------| | 框架 | Taro 3.x + React | 跨平台小程序框架,后续可扩支付宝 | | 状态管理 | Zustand | 轻量、简单 | | UI库 | Taro UI | 适配微信小程序 | | 请求 | Taro.request封装 | 小程序网络请求 | ### 3.2 项目结构 ``` banxiang-child-miniapp/ ├── src/ │ ├── app.tsx │ ├── app.config.ts │ ├── pages/ │ │ ├── index/ # 首页(父母状态概览) │ │ ├── health/ # 健康数据 │ │ ├── activities/ # 活动记录 │ │ ├── proxy/ # 代办服务 │ │ └── profile/ # 我的 │ ├── components/ │ │ ├── HealthCard.tsx │ │ ├── ActivityCard.tsx │ │ └── AlertBanner.tsx │ ├── services/ │ │ ├── api.ts │ │ └── auth.ts │ └── stores/ │ ├── authStore.ts │ └── parentStore.ts ├── config/ └── package.json ``` ### 3.3 子女端API端点 | 方法 | 路径 | 描述 | |------|------|------| | POST | /child/auth/wx-login | 微信登录 | | POST | /child/bind | 绑定父母 | | GET | /child/parents | 已绑定父母列表 | | GET | /child/parents/:id/overview | 父母状态概览 | | GET | /child/parents/:id/health | 健康数据 | | GET | /child/parents/:id/health/trends | 趋势数据 | | GET | /child/parents/:id/health/reports | 周报列表 | | GET | /child/parents/:id/activities | 活动记录 | | POST | /child/parents/:id/proxy-order | 代办下单 | | POST | /child/parents/:id/membership | 代付会员 | | GET | /child/alerts | 预警通知列表 | | PUT | /child/alerts/:id/ack | 确认预警 | ### 3.4 微信登录流程 ```javascript // 子女端登录 router.post('/child/auth/wx-login', async (req, res) => { const { code } = req.body; // 换取openid const wxRes = await axios.get( `https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${SECRET}&js_code=${code}&grant_type=authorization_code` ); const { openid, unionid } = wxRes.data; // 查找或创建用户 let user = await db.query('SELECT * FROM child_users WHERE wx_openid = $1', [openid]); if (!user.rows.length) { user = await db.query( 'INSERT INTO child_users (wx_openid, wx_unionid) VALUES ($1, $2) RETURNING *', [openid, unionid] ); } const token = jwt.sign({ childId: user.rows[0].id, type: 'child' }, JWT_SECRET); res.json({ success: true, data: { token, user: user.rows[0] } }); }); ``` --- ## 4. 内容审核方案 ### 4.1 双重审核机制 ``` 用户发布内容 ↓ ┌─────────────────┐ │ 机器审核(实时)│ │ ├── 文字:敏感词库 + 通义千问审核 │ └── 图片:阿里云内容安全API └──────┬──────────┘ ↓ ├── 通过 → 直接发布 ├── 疑似 → 标记待人工审核,临时发布 └── 违规 → 拦截,通知用户 ↓ ┌─────────────────┐ │ 人工审核(异步)│ ← 疑似内容队列 │ 管理后台审核 │ └─────────────────┘ ``` ### 4.2 敏感词库 - 基础库:政治/色情/暴力/赌博 - 医疗库:虚假医疗广告、伪科学养生 - 诈骗库:常见诈骗话术、钓鱼链接 - 自定义库:平台运营过程中积累 --- ## 5. 部署变更 ### 5.1 新增服务 ```yaml # docker-compose.yml 新增 services: # 健康数据同步Worker health-worker: build: . command: node workers/health-sync.js environment: - REDIS_URL=redis://redis:6379 - DATABASE_URL=postgres://... depends_on: - db - redis restart: always # 预警检测(与主服务共用,通过Bull队列触发) # 无需独立服务,health-worker内集成 ``` ### 5.2 服务器升级 | 服务 | V1.0配置 | V1.2配置 | 原因 | |------|----------|----------|------| | ECS | 2核4G | 4核8G | Worker增加 | | RDS | 2核4G | 2核4G | 暂不变,健康数据用分区 | | Redis | 1G | 2G | Bull队列+更多缓存 | | **月费** | ¥700 | **¥1000** | | --- ## 6. 华为/小米健康API接入 ### 6.1 华为健康 - **OAuth2授权**: 用户在APP内跳转华为健康授权页 - **API文档**: https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/open-platform-oauth-0000001050123437 - **数据类型**: 步数、心率、血压、血糖、睡眠 - **频率限制**: 1000次/天/用户 ### 6.2 小米运动 - **OAuth2授权**: 小米开放平台授权 - **API文档**: https://dev.mi.com/docs - **数据类型**: 步数、心率、睡眠 - **注意**: 小米手环不支持血压直接测量,需外接血压计 --- **文档结束**