| slug |
| tech-v1-0-mvp-architecture |
「伴享」技术方案 V1.0 — MVP架构
版本: V1.0
对应PRD: PRD-V1.0-MVP核心功能
更新日期: 2026-02-16
1. 技术架构总览
1.1 系统架构图
┌────────────────────────────────────────────────────┐
│ 客户端层 │
│ ┌──────────────────┐ ┌───────────────────────┐ │
│ │ Flutter APP │ │ 子女端(V1.2预留) │ │
│ │ iOS + Android │ │ 微信小程序 │ │
│ └────────┬─────────┘ └───────────────────────┘ │
└───────────┼────────────────────────────────────────┘
│ HTTPS + WebSocket
┌───────────▼────────────────────────────────────────┐
│ API网关层(Nginx) │
│ SSL终止 │ 限流 │ 日志 │ 路由 │ CORS │ 压缩 │
└───────────┬────────────────────────────────────────┘
│
┌───────────▼────────────────────────────────────────┐
│ 业务服务层(Node.js + Express) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 用户服务 │ │ 活动服务 │ │ AI管家 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 医疗服务 │ │ 支付服务 │ │ 通知服务 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└───────────┬────────────────────────────────────────┘
│
┌───────────▼────────────────────────────────────────┐
│ 数据层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │PostgreSQL│ │ Redis │ │ 阿里云OSS │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────────────────────────────────────┘
1.2 技术选型
| 层级 |
技术 |
版本 |
选型理由 |
| 前端 |
Flutter |
3.x |
跨平台、性能好、适老化组件丰富 |
| 后端 |
Node.js + Express |
22 + 4.x |
生态成熟、AI集成方便 |
| 数据库 |
PostgreSQL |
15+ |
JSON支持好、地理空间查询 |
| 缓存 |
Redis |
7.x |
会话管理、限流、缓存 |
| 文件存储 |
阿里云OSS |
- |
图片/语音文件 |
| AI模型 |
通义千问 |
qwen-max |
国产、函数调用支持好 |
| 语音 |
讯飞ASR/TTS |
- |
中文识别准确率高 |
| 地图 |
高德地图 |
- |
国内POI数据全 |
| 支付 |
微信支付V3 |
- |
银发群体使用率高 |
| 推送 |
JPush |
- |
全平台推送 |
| 部署 |
Docker Compose |
- |
MVP阶段够用 |
2. 数据库设计
2.1 ER关系概览
users ──< activity_participants >── activities
users ──< ai_conversations
users ──< orders
users ──< appointments >── doctors >── departments >── hospitals
users ──< consultations >── doctors
users ──< family_links
users ──< notifications
users ── ai_user_preferences
2.2 完整DDL
-- ===========================
-- 伴享 V1.0 DDL
-- PostgreSQL 15+
-- ===========================
CREATE EXTENSION IF NOT EXISTS earthdistance CASCADE;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- 1. 用户表
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
phone VARCHAR(11) UNIQUE NOT NULL,
nickname VARCHAR(50),
avatar_url TEXT,
birth_year INTEGER,
birth_month INTEGER,
gender VARCHAR(10) CHECK (gender IN ('male', 'female', 'other')),
city VARCHAR(50) DEFAULT '成都',
district VARCHAR(50),
interests TEXT[] DEFAULT '{}',
real_name VARCHAR(50),
id_card_hash VARCHAR(64),
id_card_encrypted TEXT,
verified BOOLEAN DEFAULT FALSE,
emergency_contact_name VARCHAR(50),
emergency_contact_phone VARCHAR(11),
font_size VARCHAR(10) DEFAULT 'medium',
speech_speed VARCHAR(10) DEFAULT 'normal',
auto_voice BOOLEAN DEFAULT TRUE,
last_login_at TIMESTAMP,
login_count INTEGER DEFAULT 0,
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_users_phone ON users(phone);
CREATE INDEX idx_users_city ON users(city);
CREATE INDEX idx_users_interests ON users USING GIN(interests);
-- 2. 短信验证码
CREATE TABLE sms_codes (
id BIGSERIAL PRIMARY KEY,
phone VARCHAR(11) NOT NULL,
code VARCHAR(6) NOT NULL,
purpose VARCHAR(20) DEFAULT 'login',
used BOOLEAN DEFAULT FALSE,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- 3. 家庭关联
CREATE TABLE family_links (
id BIGSERIAL PRIMARY KEY,
parent_user_id BIGINT REFERENCES users(id),
child_user_id BIGINT,
invite_code VARCHAR(6),
status VARCHAR(20) DEFAULT 'active',
linked_at TIMESTAMP DEFAULT NOW(),
UNIQUE(parent_user_id, child_user_id)
);
CREATE TABLE invite_codes (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
code VARCHAR(6) UNIQUE NOT NULL,
used BOOLEAN DEFAULT FALSE,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- 4. 活动表
CREATE TABLE activities (
id BIGSERIAL PRIMARY KEY,
creator_id BIGINT REFERENCES users(id) NOT NULL,
title VARCHAR(100) NOT NULL,
category VARCHAR(50) NOT NULL,
description TEXT,
cover_image_url TEXT,
location_lat DECIMAL(10,7) NOT NULL,
location_lng DECIMAL(10,7) NOT NULL,
location_address TEXT NOT NULL,
location_name VARCHAR(100),
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP,
max_participants INTEGER DEFAULT 10 CHECK (max_participants BETWEEN 5 AND 15),
current_participants INTEGER DEFAULT 0,
status VARCHAR(20) DEFAULT 'upcoming',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_activities_status_time ON activities(status, start_time);
CREATE INDEX idx_activities_category ON activities(category);
-- 5. 活动报名
CREATE TABLE activity_participants (
id BIGSERIAL PRIMARY KEY,
activity_id BIGINT REFERENCES activities(id),
user_id BIGINT REFERENCES users(id),
status VARCHAR(20) DEFAULT 'registered',
signed_in_at TIMESTAMP,
signed_in_lat DECIMAL(10,7),
signed_in_lng DECIMAL(10,7),
rating INTEGER CHECK (rating BETWEEN 1 AND 5),
review TEXT,
joined_at TIMESTAMP DEFAULT NOW(),
cancelled_at TIMESTAMP,
UNIQUE(activity_id, user_id)
);
-- 6. AI对话
CREATE TABLE ai_conversations (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
session_id VARCHAR(36) NOT NULL,
role VARCHAR(20) NOT NULL,
content TEXT NOT NULL,
content_type VARCHAR(20) DEFAULT 'text',
voice_url TEXT,
intent VARCHAR(50),
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_ai_conv_user ON ai_conversations(user_id, session_id);
CREATE TABLE ai_user_preferences (
user_id BIGINT PRIMARY KEY REFERENCES users(id),
preferred_hospital VARCHAR(100),
preferred_doctor VARCHAR(100),
home_address TEXT,
common_destinations JSONB,
dietary_restrictions TEXT[],
updated_at TIMESTAMP DEFAULT NOW()
);
-- 7. 医院/科室/医生/排班
CREATE TABLE hospitals (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
address TEXT,
lat DECIMAL(10,7),
lng DECIMAL(10,7),
phone VARCHAR(20),
logo_url TEXT,
level VARCHAR(20),
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE departments (
id BIGSERIAL PRIMARY KEY,
hospital_id BIGINT REFERENCES hospitals(id),
name VARCHAR(50) NOT NULL,
description TEXT,
sort_order INTEGER DEFAULT 0
);
CREATE TABLE doctors (
id BIGSERIAL PRIMARY KEY,
hospital_id BIGINT REFERENCES hospitals(id),
department_id BIGINT REFERENCES departments(id),
name VARCHAR(50) NOT NULL,
title VARCHAR(50),
avatar_url TEXT,
specialty TEXT,
rating DECIMAL(2,1) DEFAULT 5.0,
rating_count INTEGER DEFAULT 0,
status VARCHAR(20) DEFAULT 'active'
);
CREATE TABLE doctor_schedules (
id BIGSERIAL PRIMARY KEY,
doctor_id BIGINT REFERENCES doctors(id),
schedule_date DATE NOT NULL,
start_time TIME NOT NULL,
end_time TIME NOT NULL,
max_patients INTEGER DEFAULT 30,
current_patients INTEGER DEFAULT 0,
fee DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'available'
);
-- 8. 预约/问诊
CREATE TABLE appointments (
id BIGSERIAL PRIMARY KEY,
order_no VARCHAR(20) UNIQUE NOT NULL,
user_id BIGINT REFERENCES users(id),
hospital_id BIGINT REFERENCES hospitals(id),
department_id BIGINT REFERENCES departments(id),
doctor_id BIGINT REFERENCES doctors(id),
schedule_id BIGINT REFERENCES doctor_schedules(id),
patient_name VARCHAR(50),
patient_phone VARCHAR(11),
appointment_date DATE,
appointment_time TIME,
fee DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'pending',
paid_at TIMESTAMP,
cancelled_at TIMESTAMP,
cancel_reason TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE consultations (
id BIGSERIAL PRIMARY KEY,
order_no VARCHAR(20) UNIQUE NOT NULL,
user_id BIGINT REFERENCES users(id),
doctor_id BIGINT REFERENCES doctors(id),
symptom_description TEXT,
images TEXT[],
doctor_reply TEXT,
fee DECIMAL(10,2) DEFAULT 19.90,
status VARCHAR(20) DEFAULT 'pending',
replied_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
-- 9. 订单
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
order_no VARCHAR(20) UNIQUE NOT NULL,
user_id BIGINT REFERENCES users(id),
order_type VARCHAR(50) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
detail JSONB,
wx_transaction_id VARCHAR(50),
paid_at TIMESTAMP,
completed_at TIMESTAMP,
cancelled_at TIMESTAMP,
refund_amount DECIMAL(10,2),
refund_at TIMESTAMP,
refund_reason TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_orders_user ON orders(user_id, created_at DESC);
-- 10. 通知
CREATE TABLE notifications (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
type VARCHAR(50) NOT NULL,
title VARCHAR(100),
content TEXT,
data JSONB,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_notif_user ON notifications(user_id, is_read, created_at DESC);
-- 11. 审计日志
CREATE TABLE audit_logs (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT,
action VARCHAR(50),
resource_type VARCHAR(50),
resource_id BIGINT,
detail JSONB,
ip_address INET,
created_at TIMESTAMP DEFAULT NOW()
);
3. API文档
3.1 规范
- Base URL:
https://api.banxiang.com/api/v1
- 认证:
Authorization: Bearer <JWT>
- 响应格式:
{ "success": true, "data": {...} } / { "success": false, "error": {"code":"...", "message":"..."} }
3.2 完整接口清单
| 模块 |
方法 |
路径 |
描述 |
| 认证 |
POST |
/auth/send-code |
发送验证码 |
| 认证 |
POST |
/auth/login |
登录/注册 |
| 认证 |
POST |
/auth/refresh |
刷新Token |
| 用户 |
GET |
/users/me |
获取当前用户 |
| 用户 |
PUT |
/users/profile |
更新资料 |
| 用户 |
POST |
/users/verify |
实名认证 |
| 用户 |
PUT |
/users/emergency-contact |
紧急联系人 |
| 用户 |
PUT |
/users/settings |
更新设置 |
| 家庭 |
POST |
/family/generate-code |
生成邀请码 |
| 家庭 |
POST |
/family/link |
绑定子女 |
| 家庭 |
GET |
/family/members |
家人列表 |
| 活动 |
GET |
/activities |
活动列表 |
| 活动 |
GET |
/activities/:id |
活动详情 |
| 活动 |
POST |
/activities |
创建活动 |
| 活动 |
PUT |
/activities/:id |
修改活动 |
| 活动 |
DELETE |
/activities/:id |
取消活动 |
| 活动 |
POST |
/activities/:id/join |
报名 |
| 活动 |
POST |
/activities/:id/cancel |
取消报名 |
| 活动 |
POST |
/activities/:id/sign-in |
签到 |
| 活动 |
POST |
/activities/:id/review |
评价 |
| 活动 |
GET |
/users/me/activities |
我的活动 |
| AI |
POST |
/ai/chat |
文字对话(SSE) |
| AI |
POST |
/ai/voice |
语音对话 |
| AI |
GET |
/ai/history |
对话历史 |
| 医疗 |
GET |
/medical/hospitals |
医院列表 |
| 医疗 |
GET |
/medical/hospitals/:id/departments |
科室 |
| 医疗 |
GET |
/medical/doctors |
医生列表 |
| 医疗 |
GET |
/medical/doctors/:id/schedules |
排班 |
| 医疗 |
POST |
/medical/appointments |
创建预约 |
| 医疗 |
POST |
/medical/appointments/:id/cancel |
取消 |
| 医疗 |
POST |
/medical/consultations |
创建问诊 |
| 订单 |
POST |
/orders/create |
创建订单 |
| 订单 |
GET |
/orders |
订单列表 |
| 订单 |
GET |
/orders/:orderNo |
订单详情 |
| 订单 |
POST |
/orders/:orderNo/refund |
退款 |
| 通知 |
GET |
/notifications |
通知列表 |
| 通知 |
PUT |
/notifications/:id/read |
标记已读 |
| 上传 |
POST |
/upload/image |
上传图片 |
| 上传 |
POST |
/upload/voice |
上传语音 |
4. Flutter组件架构
4.1 项目结构
banxiang_app/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── config/
│ │ ├── theme.dart # 适老化主题
│ │ ├── routes.dart
│ │ └── constants.dart
│ ├── core/
│ │ ├── api/
│ │ │ ├── api_client.dart # Dio封装
│ │ │ └── endpoints.dart
│ │ ├── models/ # 数据模型
│ │ ├── providers/ # Riverpod状态管理
│ │ ├── services/ # 业务服务
│ │ └── utils/
│ ├── features/
│ │ ├── auth/
│ │ ├── home/
│ │ ├── activity/
│ │ ├── ai_butler/
│ │ ├── medical/
│ │ ├── profile/
│ │ └── notification/
│ └── shared/
│ ├── widgets/ # BX适老化组件库
│ └── layouts/
├── assets/
│ ├── images/
│ ├── fonts/
│ └── animations/
├── test/
└── pubspec.yaml
4.2 适老化组件库(BX Design System)
/// BX适老化按钮
class BxButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final BxButtonStyle style; // primary / secondary / danger
final bool loading;
// 最小高度48dp,字号18sp,圆角12dp
// 点击区域≥48x48dp
// loading状态显示转圈+文字
}
/// BX适老化输入框
class BxInput extends StatelessWidget {
final String label;
final String hint;
final TextInputType keyboardType;
final bool voiceEnabled; // 右侧语音输入按钮
// 高度56dp,字号18sp
// label在输入框上方
// 错误提示在下方,红色16sp
}
/// BX确认弹窗(防误操作)
class BxConfirmDialog extends StatelessWidget {
final String title;
final String message;
final String confirmText;
final String cancelText;
// 标题22sp,正文18sp
// 按钮大且间隔远
// 危险操作confirmText用红色
}
/// BX活动卡片
class BxActivityCard extends StatelessWidget {
final Activity activity;
final double distance;
// 大标题18sp,时间地点16sp
// 报名按钮在右侧
// 已满员显示灰色
}
4.3 核心代码示例
API客户端
// core/api/api_client.dart
class ApiClient {
late final Dio _dio;
ApiClient() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.banxiang.com/api/v1',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
));
_dio.interceptors.addAll([
AuthInterceptor(), // 自动附加JWT
LogInterceptor(),
ErrorInterceptor(), // 统一错误处理
]);
}
Future<T> get<T>(String path, {Map<String, dynamic>? params}) async {
final response = await _dio.get(path, queryParameters: params);
return response.data['data'] as T;
}
Future<T> post<T>(String path, {dynamic data}) async {
final response = await _dio.post(path, data: data);
return response.data['data'] as T;
}
}
AI管家对话页面
// features/ai_butler/chat_page.dart
class AiChatPage extends ConsumerStatefulWidget {
@override
ConsumerState<AiChatPage> createState() => _AiChatPageState();
}
class _AiChatPageState extends ConsumerState<AiChatPage> {
final _controller = TextEditingController();
bool _isRecording = false;
@override
Widget build(BuildContext context) {
final messages = ref.watch(aiChatProvider);
return Scaffold(
appBar: AppBar(title: Text('🤖 小伴 · AI管家')),
body: Column(
children: [
// 消息列表
Expanded(
child: ListView.builder(
reverse: true,
itemCount: messages.length,
itemBuilder: (ctx, i) => ChatBubble(
message: messages[i],
fontSize: ref.watch(fontSizeProvider),
),
),
),
// 快捷指令栏
QuickActions(
actions: ['附近活动', '挂号', '买菜', '天气'],
onTap: (action) => _sendMessage(action),
),
// 输入栏
ChatInputBar(
controller: _controller,
onSend: () => _sendMessage(_controller.text),
onVoiceStart: _startRecording,
onVoiceEnd: _stopRecording,
isRecording: _isRecording,
),
],
),
);
}
void _sendMessage(String text) {
ref.read(aiChatProvider.notifier).sendMessage(text);
_controller.clear();
}
}
活动推荐算法
// core/services/recommendation_service.dart
double calculateRecommendScore(User user, Activity activity, double distanceKm) {
// 兴趣匹配度 (0-1)
final commonInterests = user.interests
.where((i) => i == activity.category)
.length;
final interestScore = commonInterests > 0 ? 1.0 : 0.0;
// 距离分 (0-1), 10km内线性衰减
final distanceScore = 1 - (distanceKm / 10).clamp(0, 1);
// 时间分 (0-1), 168小时(一周)内线性衰减
final hoursDiff = activity.startTime.difference(DateTime.now()).inHours;
final timeScore = 1 - (hoursDiff / 168).clamp(0, 1);
// 热度分 (0-1)
final hotScore = activity.currentParticipants / activity.maxParticipants;
return interestScore * 0.4 + distanceScore * 0.3 + timeScore * 0.2 + hotScore * 0.1;
}
5. AI管家技术方案
5.1 架构
用户输入(文字/语音)
↓
┌─────────────┐
│ 语音处理 │ ← 讯飞ASR(语音→文字)
└──────┬──────┘
↓
┌─────────────┐
│ 意图识别 │ ← 通义千问 Function Calling
└──────┬──────┘
↓
┌─────────────────────────────┐
│ 任务路由 │
│ ├── 活动查询 → 调用活动API │
│ ├── 挂号预约 → 调用医疗API │
│ ├── 生鲜购买 → 调用商品API │
│ └── 闲聊/咨询 → 直接对话 │
└──────┬──────────────────────┘
↓
┌─────────────┐
│ 响应生成 │ ← 通义千问生成回复
└──────┬──────┘
↓
┌─────────────┐
│ 语音合成 │ ← 讯飞TTS(文字→语音)
└──────┬──────┘
↓
返回用户(文字+语音+操作按钮)
5.2 Function Calling定义
// AI工具定义
const tools = [
{
type: 'function',
function: {
name: 'search_activities',
description: '搜索附近的活动',
parameters: {
type: 'object',
properties: {
category: { type: 'string', description: '活动类型' },
timeRange: { type: 'string', enum: ['today', 'tomorrow', 'week'] },
lat: { type: 'number' },
lng: { type: 'number' }
}
}
}
},
{
type: 'function',
function: {
name: 'book_appointment',
description: '预约挂号',
parameters: {
type: 'object',
properties: {
hospitalName: { type: 'string' },
departmentName: { type: 'string' },
doctorName: { type: 'string' },
date: { type: 'string' },
time: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'order_grocery',
description: '生鲜配送下单',
parameters: {
type: 'object',
properties: {
items: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
quantity: { type: 'number' }
}
}
},
deliveryAddress: { type: 'string' }
}
}
}
}
];
5.3 System Prompt
你是「小伴」,「伴享」APP的AI智能管家。你服务的是50-70岁的银发群体。
规则:
1. 说话简洁、亲切、温暖,像家人一样
2. 每次回复不超过100字
3. 称呼用户为"X阿姨"或"X叔叔"(根据性别和姓氏)
4. 不使用网络用语、不说英文
5. 涉及医疗健康的建议必须加"仅供参考,建议咨询医生"
6. 涉及支付前必须明确告知金额并确认
7. 不确定的信息不要编造,说"我帮您查查"
8. 提供操作选项时,用简短的按钮文字
用户信息:
- 姓名:{userName}
- 城市:{city}
- 兴趣:{interests}
- 常去医院:{preferredHospital}
- 家庭住址:{homeAddress}
6. 部署方案
6.1 服务器配置(MVP阶段)
| 服务 |
配置 |
月费用 |
| 应用服务器 |
阿里云ECS 2核4G |
¥200/月 |
| 数据库 |
阿里云RDS PostgreSQL 2核4G |
¥300/月 |
| Redis |
阿里云Redis 1G |
¥100/月 |
| OSS |
按量计费 |
¥50/月 |
| CDN |
按量计费 |
¥50/月 |
| 合计 |
|
¥700/月 |
6.2 Docker Compose
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://banxiang:xxx@db:5432/banxiang
- REDIS_URL=redis://redis:6379
- QWEN_API_KEY=${QWEN_API_KEY}
- XUNFEI_APP_ID=${XUNFEI_APP_ID}
- WX_PAY_MCH_ID=${WX_PAY_MCH_ID}
depends_on:
- db
- redis
restart: always
db:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
- POSTGRES_DB=banxiang
- POSTGRES_USER=banxiang
- POSTGRES_PASSWORD=${DB_PASSWORD}
restart: always
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
restart: always
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: always
volumes:
pgdata:
redisdata:
6.3 CI/CD流程
代码推送 → GitHub Actions
├── 运行测试
├── 构建Docker镜像
├── 推送到阿里云容器镜像
└── SSH到服务器执行 docker compose pull && docker compose up -d
6.4 监控方案
| 维度 |
工具 |
说明 |
| 应用监控 |
PM2 + 阿里云ARMS |
错误率、响应时间 |
| 服务器监控 |
阿里云云监控 |
CPU/内存/磁盘 |
| 日志 |
Winston + 阿里云SLS |
结构化日志 |
| 报警 |
钉钉/飞书Webhook |
异常自动通知 |
7. 安全方案
7.1 数据安全
| 层面 |
措施 |
| 传输 |
HTTPS + TLS 1.3 |
| 存储 |
敏感字段AES-256加密 |
| 身份证 |
哈希索引 + 加密存储,不存明文 |
| JWT |
HS256签名,7天有效期 |
| 密钥管理 |
环境变量,不入代码库 |
7.2 接口安全
| 措施 |
配置 |
| 全局限流 |
100次/分钟/IP |
| 登录限流 |
5次/分钟/手机号 |
| 验证码限流 |
3次/5分钟/手机号 |
| SQL注入防护 |
参数化查询 |
| XSS防护 |
输出转义 + CSP头 |
| CORS |
白名单域名 |
文档结束