「伴享」技术方案 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
-- ===========================
-- 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
// 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 预警检测引擎
// 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 微信登录流程
// 子女端登录
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 新增服务
# 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 华为健康
6.2 小米运动
文档结束