537 lines
18 KiB
Markdown
537 lines
18 KiB
Markdown
---
|
||
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
|
||
- **数据类型**: 步数、心率、睡眠
|
||
- **注意**: 小米手环不支持血压直接测量,需外接血压计
|
||
|
||
---
|
||
|
||
**文档结束**
|