banxiang/docs/技术方案-V1.2-进阶架构.md
2026-02-18 18:06:31 +00:00

537 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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