一、技术栈选型与架构总览
1.1 技术选型原则
高可用性:99.99%可用性目标
可扩展性:支持千万级日活用户
安全性:医疗健康数据最高安全标准
开发效率:统一技术栈,降低维护成本
用户体验:秒级响应,流畅交互
1.2 整体架构
┌─────────────────────────────────────────────┐
│ 用户端 │
│ ┌───────────┐ ┌───────────┐ │
│ │ 微信小程序 │ │ 企业微信 │ │
│ └───────────┘ └───────────┘ │
│ │ │ │
└─────────┼─────────────┼───────────────────┘
│ │
┌─────────┼─────────────┼───────────────────┐
│ 负载均衡集群(Nginx + Keepalived) │
└─────────┼─────────────┼───────────────────┘
│ │
┌─────────▼─────────────▼───────────────────┐
│ API网关层(Spring Cloud Gateway) │
│ ┌─────────────────────────────────────┐ │
│ │ • 路由分发 │ │
│ │ • 限流熔断 │ │
│ │ • 权限校验 │ │
│ │ • 日志审计 │ │
│ └─────────────────────────────────────┘ │
└─────────┬─────────────────────────────────┘
│
┌─────────▼─────────────────────────────────┐
│ 业务微服务集群 │
│ ┌───────────┐ ┌───────────┐ ┌─────────┐│
│ │用户服务 │ │健康服务 │ │订单服务 ││
│ ├───────────┤ ├───────────┤ ├─────────┤│
│ │• 会员管理 │ │• 健康档案 │ │• 服务单 ││
│ │• 认证授权 │ │• 计划管理 │ │• 支付 ││
│ │• 社交关系 │ │• 数据采集 │ │• 评价 ││
│ └───────────┘ └───────────┘ └─────────┘│
│ ┌───────────┐ ┌───────────┐ ┌─────────┐│
│ │医疗资源 │ │内容服务 │ │消息服务 ││
│ ├───────────┤ ├───────────┤ ├─────────┤│
│ │• 医院对接 │ │• 知识库 │ │• 即时通讯││
│ │• 医生管理 │ │• 内容管理 │ │• 推送 ││
│ │• 预约调度 │ │• AI解读 │ │• 通知 ││
│ └───────────┘ └───────────┘ └─────────┘│
└─────────┬─────────────────────────────────┘
│
┌─────────▼─────────────────────────────────┐
│ 数据服务层 │
│ ┌─────────────────────────────────────┐ │
│ │ 缓存层(Redis Cluster) │ │
│ │ • 会话管理 │ │
│ │ • 热点数据 │ │
│ │ • 分布式锁 │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ 消息队列(RocketMQ) │ │
│ │ • 异步解耦 │ │
│ │ • 削峰填谷 │ │
│ │ • 顺序消息 │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ 数据库层 │ │
│ │ • MySQL集群(主从+分库分表) │ │
│ │ • MongoDB(文档型数据) │ │
│ │ • 时序数据库(健康指标) │ │
│ └─────────────────────────────────────┘ │
└───────────────────────────────────────────┘二、小程序端技术架构
2.1 前端框架选型
// package.json 核心依赖
{
"dependencies": {
// 基础框架
"@tarojs/taro": "^3.6.0", // 多端统一框架
"@tarojs/runtime": "^3.6.0", // 运行时
"@tarojs/react": "^3.6.0", // React支持
// UI组件库
"@nutui/nutui-taro": "^2.0.0", // 轻量级组件库
"taro-ui": "^3.0.0-alpha.3", // Taro官方UI
// 状态管理
"mobx": "^6.3.0", // 状态管理
"mobx-react-lite": "^3.2.0", // Mobx React绑定
// 网络请求
"luch-request": "^3.0.0", // 增强型请求库
// 工具库
"dayjs": "^1.11.0", // 日期处理
"lodash-es": "^4.17.21", // 工具函数
"crypto-js": "^4.1.1", // 加密解密
// 图表
"@antv/f2": "^4.0.0", // 移动端图表
"ec-canvas": "^1.0.0", // ECharts小程序版
// 地图
"taro-map": "^1.0.0", // 地图组件
}
}2.2 项目目录结构
src/
├── app.config.ts # 小程序全局配置
├── app.tsx # 小程序入口
├── app.scss # 全局样式
├── config/ # 配置文件
│ ├── index.ts # 主配置
│ ├── env.ts # 环境配置
│ ├── api.ts # API接口配置
│ └── constant.ts # 常量定义
├── assets/ # 静态资源
│ ├── images/ # 图片资源
│ ├── icons/ # 图标资源
│ └── fonts/ # 字体文件
├── components/ # 公共组件
│ ├── common/ # 通用组件
│ │ ├── Loading/
│ │ ├── Empty/
│ │ ├── ErrorBoundary/
│ │ └── Modal/
│ ├── business/ # 业务组件
│ │ ├── HealthCard/
│ │ ├── AppointmentCard/
│ │ ├── DoctorCard/
│ │ └── ServiceCard/
│ └── layout/ # 布局组件
│ ├── Header/
│ ├── TabBar/
│ └── SideMenu/
├── pages/ # 页面目录
│ ├── index/ # 首页
│ ├── health/ # 健康管理
│ │ ├── dashboard/ # 健康仪表盘
│ │ ├── records/ # 健康记录
│ │ ├── plans/ # 健康计划
│ │ └── trends/ # 健康趋势
│ ├── service/ # 服务
│ │ ├── appointment/ # 预约服务
│ │ ├── companion/ # 陪诊服务
│ │ └── consultation/ # 咨询服务
│ ├── hospital/ # 医院服务
│ ├── mine/ # 个人中心
│ └── messages/ # 消息中心
├── services/ # 服务层
│ ├── api/ # API服务
│ │ ├── user.api.ts
│ │ ├── health.api.ts
│ │ └── hospital.api.ts
│ ├── storage/ # 存储服务
│ ├── request/ # 请求封装
│ └── websocket/ # WebSocket服务
├── stores/ # 状态管理
│ ├── user.store.ts # 用户状态
│ ├── health.store.ts # 健康状态
│ ├── app.store.ts # 应用状态
│ └── index.ts # Store入口
├── utils/ # 工具函数
│ ├── auth.ts # 认证工具
│ ├── validator.ts # 验证工具
│ ├── formatter.ts # 格式化工具
│ ├── encryption.ts # 加密工具
│ └── performance.ts # 性能监控
├── hooks/ # 自定义Hooks
│ ├── useAuth.ts # 认证Hook
│ ├── useRequest.ts # 请求Hook
│ └── useHealthData.ts # 健康数据Hook
├── types/ # TypeScript类型定义
│ ├── global.d.ts # 全局类型
│ ├── api.d.ts # API类型
│ └── model.d.ts # 数据模型
└── styles/ # 样式文件
├── variables.scss # 样式变量
├── mixins.scss # Mixin函数
└── common.scss # 公共样式2.3 核心组件设计
2.3.1 健康数据卡片组件
// components/business/HealthDataCard/index.tsx
import React, { memo } from 'react';
import { View, Text } from '@tarojs/components';
import { LineChart } from '@antv/f2';
import './index.scss';
interface HealthDataCardProps {
title: string;
value: number | string;
unit: string;
trend: 'up' | 'down' | 'stable';
trendValue: number;
chartData: Array<{ date: string; value: number }>;
onClick?: () => void;
}
const HealthDataCard: React.FC<HealthDataCardProps> = memo(({
title,
value,
unit,
trend,
trendValue,
chartData,
onClick
}) => {
const getTrendIcon = () => {
switch(trend) {
case 'up': return '↑';
case 'down': return '↓';
default: return '→';
}
};
const getTrendColor = () => {
switch(trend) {
case 'up': return '#ff6b6b';
case 'down': return '#52c41a';
default: return '#999';
}
};
return (
<View className="health-data-card" onClick={onClick}>
<View className="card-header">
<Text className="title">{title}</Text>
<View className="trend">
<Text style={{ color: getTrendColor() }}>
{getTrendIcon()} {Math.abs(trendValue)}
</Text>
</View>
</View>
<View className="card-body">
<View className="current-value">
<Text className="value">{value}</Text>
<Text className="unit">{unit}</Text>
</View>
{chartData.length > 0 && (
<View className="chart-container">
<LineChart
data={chartData}
xField="date"
yField="value"
width={300}
height={80}
/>
</View>
)}
</View>
<View className="card-footer">
<Text className="time">最近更新: 今天 09:30</Text>
<Text className="detail">查看详情 ></Text>
</View>
</View>
);
});
export default HealthDataCard;2.3.2 服务预约流程组件
// components/business/ServiceFlow/index.tsx
import React, { useState } from 'react';
import { View, Text, Button } from '@tarojs/components';
import { Steps, Step } from '@nutui/nutui-taro';
import ServiceType from './ServiceType';
import TimeSelection from './TimeSelection';
import InfoConfirm from './InfoConfirm';
import './index.scss';
const steps = [
{ title: '选择服务', description: '选择需要的服务类型' },
{ title: '选择时间', description: '预约合适的时间' },
{ title: '确认信息', description: '确认预约信息' },
];
const ServiceFlow: React.FC = () => {
const [currentStep, setCurrentStep] = useState(0);
const [formData, setFormData] = useState({
serviceType: '',
appointmentTime: '',
patientInfo: {},
});
const handleNext = () => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1);
} else {
handleSubmit();
}
};
const handleBack = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
const handleSubmit = async () => {
// 提交预约逻辑
console.log('提交预约:', formData);
};
const renderStepContent = () => {
switch(currentStep) {
case 0:
return (
<ServiceType
value={formData.serviceType}
onChange={(type) => setFormData({...formData, serviceType: type})}
/>
);
case 1:
return (
<TimeSelection
value={formData.appointmentTime}
onChange={(time) => setFormData({...formData, appointmentTime: time})}
/>
);
case 2:
return (
<InfoConfirm
data={formData}
onChange={(info) => setFormData({...formData, patientInfo: info})}
/>
);
default:
return null;
}
};
return (
<View className="service-flow">
<Steps current={currentStep}>
{steps.map((step, index) => (
<Step
key={index}
title={step.title}
description={step.description}
active={index === currentStep}
complete={index < currentStep}
/>
))}
</Steps>
<View className="step-content">
{renderStepContent()}
</View>
<View className="action-buttons">
{currentStep > 0 && (
<Button className="back-btn" onClick={handleBack}>
上一步
</Button>
)}
<Button
className="next-btn"
type="primary"
onClick={handleNext}
>
{currentStep === steps.length - 1 ? '确认预约' : '下一步'}
</Button>
</View>
</View>
);
};2.4 状态管理设计
// stores/user.store.ts
import { makeAutoObservable, runInAction } from 'mobx';
import { userApi } from '../services/api/user.api';
import { StorageService } from '../services/storage';
export interface UserProfile {
id: string;
name: string;
avatar: string;
phone: string;
membershipLevel: 'basic' | 'professional' | 'premium';
healthTags: string[];
}
class UserStore {
// 状态
userInfo: UserProfile | null = null;
token: string | null = null;
isAuthenticated: boolean = false;
loading: boolean = false;
error: string | null = null;
constructor() {
makeAutoObservable(this);
this.initialize();
}
// 初始化
async initialize() {
const token = await StorageService.getToken();
if (token) {
this.token = token;
await this.fetchUserProfile();
}
}
// Action: 登录
async login(credentials: { phone: string; code: string }) {
this.loading = true;
this.error = null;
try {
const response = await userApi.login(credentials);
runInAction(() => {
this.token = response.token;
this.userInfo = response.user;
this.isAuthenticated = true;
});
// 持久化存储
await StorageService.setToken(response.token);
await StorageService.setUser(response.user);
} catch (error) {
runInAction(() => {
this.error = error.message;
this.isAuthenticated = false;
});
} finally {
runInAction(() => {
this.loading = false;
});
}
}
// Action: 获取用户资料
async fetchUserProfile() {
if (!this.token) return;
try {
const user = await userApi.getProfile();
runInAction(() => {
this.userInfo = user;
this.isAuthenticated = true;
});
} catch (error) {
runInAction(() => {
this.isAuthenticated = false;
});
}
}
// Action: 更新用户信息
async updateProfile(profile: Partial<UserProfile>) {
try {
const updated = await userApi.updateProfile(profile);
runInAction(() => {
this.userInfo = { ...this.userInfo!, ...updated };
});
return updated;
} catch (error) {
throw error;
}
}
// Action: 登出
async logout() {
await userApi.logout();
await StorageService.clear();
runInAction(() => {
this.userInfo = null;
this.token = null;
this.isAuthenticated = false;
});
}
// Computed: 是否是高级会员
get isPremiumMember() {
return this.userInfo?.membershipLevel === 'premium';
}
// Computed: 用户标签字符串
get healthTagsString() {
return this.userInfo?.healthTags?.join('、') || '无';
}
}
export default new UserStore();三、后端微服务架构设计
3.1 用户服务 (User Service)
// UserService 核心接口设计
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
/**
* 用户注册
*/
@PostMapping("/register")
public ApiResponse<UserVO> register(@Valid @RequestBody RegisterDTO dto) {
UserVO user = userService.register(dto);
return ApiResponse.success(user);
}
/**
* 用户登录
*/
@PostMapping("/login")
public ApiResponse<LoginResult> login(@Valid @RequestBody LoginDTO dto) {
LoginResult result = userService.login(dto);
return ApiResponse.success(result);
}
/**
* 获取用户健康档案
*/
@GetMapping("/{userId}/health-profile")
@PreAuthorize("hasRole('USER')")
public ApiResponse<HealthProfileVO> getHealthProfile(@PathVariable String userId) {
HealthProfileVO profile = userService.getHealthProfile(userId);
return ApiResponse.success(profile);
}
/**
* 更新健康标签
*/
@PutMapping("/{userId}/health-tags")
@PreAuthorize("hasRole('USER')")
public ApiResponse<Void> updateHealthTags(
@PathVariable String userId,
@RequestBody UpdateTagsDTO dto) {
userService.updateHealthTags(userId, dto.getTags());
return ApiResponse.success();
}
}
// 用户领域模型
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@Column(unique = true, nullable = false)
private String phone;
private String name;
private String avatar;
@Enumerated(EnumType.STRING)
private UserStatus status = UserStatus.ACTIVE;
@Enumerated(EnumType.STRING)
private MembershipLevel membershipLevel = MembershipLevel.BASIC;
@ElementCollection
@CollectionTable(name = "user_health_tags")
private Set<String> healthTags = new HashSet<>();
@Embedded
private HealthProfile healthProfile;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
// 健康档案值对象
@Embeddable
@Data
public class HealthProfile {
private String bloodType;
private Double height;
private Double weight;
private Boolean hasAllergy;
private String allergyInfo;
private String chronicDiseases;
private String familyHistory;
@Embedded
private EmergencyContact emergencyContact;
}3.2 健康服务 (Health Service)
// 健康数据服务
@Service
@Slf4j
public class HealthDataServiceImpl implements HealthDataService {
@Autowired
private HealthDataRepository healthDataRepo;
@Autowired
private HealthIndicatorCalculator indicatorCalculator;
@Autowired
private RiskAssessmentEngine riskEngine;
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
/**
* 记录健康数据
*/
@Override
@Transactional
public HealthRecordVO recordHealthData(String userId, RecordHealthDataDTO dto) {
// 1. 保存原始数据
HealthData data = HealthData.builder()
.userId(userId)
.type(dto.getType())
.value(dto.getValue())
.unit(dto.getUnit())
.source(dto.getSource())
.recordedAt(dto.getRecordedAt())
.metadata(dto.getMetadata())
.build();
HealthData saved = healthDataRepo.save(data);
// 2. 计算衍生指标
Map<String, Object> indicators = indicatorCalculator.calculate(
userId, dto.getType(), dto.getValue());
// 3. 风险评估
RiskAssessment risk = riskEngine.assess(userId, dto.getType(), dto.getValue());
// 4. 发送事件消息
HealthDataRecordedEvent event = HealthDataRecordedEvent.builder()
.userId(userId)
.dataType(dto.getType())
.recordedAt(dto.getRecordedAt())
.indicators(indicators)
.riskLevel(risk.getLevel())
.build();
kafkaTemplate.send("health-data-events", event);
// 5. 返回结果
return HealthRecordVO.from(saved, indicators, risk);
}
/**
* 获取健康趋势
*/
@Override
public HealthTrendVO getHealthTrend(String userId, HealthTrendQuery query) {
LocalDateTime end = query.getEndDate() != null ?
query.getEndDate().atTime(23, 59, 59) : LocalDateTime.now();
LocalDateTime start = end.minus(query.getPeriod());
List<HealthData> data = healthDataRepo.findByUserIdAndTypeAndTimeRange(
userId, query.getDataType(), start, end);
// 数据聚合与统计
HealthStatistics stats = calculateStatistics(data);
// 趋势分析
TrendAnalysis trend = analyzeTrend(data, query.getPeriod());
// 生成建议
List<HealthAdvice> advice = generateAdvice(stats, trend, query.getDataType());
return HealthTrendVO.builder()
.statistics(stats)
.trend(trend)
.advice(advice)
.dataPoints(data.stream()
.map(HealthDataPoint::from)
.collect(Collectors.toList()))
.build();
}
/**
* 生成健康报告
*/
@Override
public HealthReportVO generateReport(String userId, ReportPeriod period) {
// 获取报告期数据
ReportData data = collectReportData(userId, period);
// 计算各项指标
Map<String, Object> metrics = calculateMetrics(data);
// 风险评估
List<RiskItem> risks = assessRisks(data);
// 生成建议
List<Recommendation> recommendations = generateRecommendations(metrics, risks);
// 生成可视化数据
Map<String, Object> visualizations = generateVisualizations(data);
return HealthReportVO.builder()
.period(period)
.metrics(metrics)
.risks(risks)
.recommendations(recommendations)
.visualizations(visualizations)
.generatedAt(LocalDateTime.now())
.build();
}
}3.3 订单服务 (Order Service)
// 订单状态机
@Component
public class OrderStateMachine {
private final StateMachine<OrderStatus, OrderEvent> stateMachine;
public OrderStateMachine() {
StateMachineBuilder<OrderStatus, OrderEvent> builder =
StateMachineBuilderFactory.create();
// 配置状态转移
builder.configureStates()
.withStates()
.initial(OrderStatus.PENDING)
.state(OrderStatus.PAID)
.state(OrderStatus.CONFIRMED)
.state(OrderStatus.IN_PROGRESS)
.state(OrderStatus.COMPLETED)
.state(OrderStatus.CANCELLED)
.state(OrderStatus.REFUNDED);
builder.configureTransitions()
.withExternal()
.source(OrderStatus.PENDING).target(OrderStatus.PAID)
.event(OrderEvent.PAY_SUCCESS)
.and()
.withExternal()
.source(OrderStatus.PAID).target(OrderStatus.CONFIRMED)
.event(OrderEvent.CONFIRM)
.and()
.withExternal()
.source(OrderStatus.CONFIRMED).target(OrderStatus.IN_PROGRESS)
.event(OrderEvent.START_SERVICE)
.and()
.withExternal()
.source(OrderStatus.IN_PROGRESS).target(OrderStatus.COMPLETED)
.event(OrderEvent.COMPLETE_SERVICE)
.and()
.withExternal()
.source(OrderStatus.PAID).target(OrderStatus.CANCELLED)
.event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderStatus.COMPLETED).target(OrderStatus.REFUNDED)
.event(OrderEvent.REFUND);
}
public OrderStatus processEvent(Order order, OrderEvent event) {
stateMachine.start();
stateMachine.sendEvent(event);
return stateMachine.getState().getId();
}
}
// 分布式事务处理
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserServiceClient userServiceClient;
@Autowired
private InventoryServiceClient inventoryServiceClient;
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 创建订单(分布式事务)
*/
@Transactional
public Order createOrder(CreateOrderDTO dto) {
// 1. 验证用户
UserVO user = userServiceClient.getUser(dto.getUserId());
if (user == null) {
throw new BusinessException("用户不存在");
}
// 2. 锁定库存(Saga模式)
try {
inventoryServiceClient.lockInventory(dto.getServiceId(), dto.getQuantity());
} catch (Exception e) {
throw new BusinessException("库存锁定失败");
}
// 3. 创建订单
Order order = Order.builder()
.orderNo(generateOrderNo())
.userId(dto.getUserId())
.serviceId(dto.getServiceId())
.amount(dto.getAmount())
.status(OrderStatus.PENDING)
.build();
Order savedOrder = orderRepository.save(order);
// 4. 发送创建订单事件
eventPublisher.publishEvent(new OrderCreatedEvent(savedOrder));
return savedOrder;
}
/**
* 支付回调处理
*/
@Transactional
public void handlePaymentCallback(PaymentCallbackDTO callback) {
Order order = orderRepository.findByOrderNo(callback.getOrderNo())
.orElseThrow(() -> new BusinessException("订单不存在"));
if (callback.isSuccess()) {
// 支付成功,更新订单状态
order.setStatus(OrderStatus.PAID);
order.setPaidAt(LocalDateTime.now());
order.setPaymentNo(callback.getPaymentNo());
orderRepository.save(order);
// 发送支付成功事件
eventPublisher.publishEvent(new OrderPaidEvent(order));
} else {
// 支付失败,释放库存
inventoryServiceClient.releaseInventory(
order.getServiceId(), order.getQuantity());
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
}
}
}四、数据库设计
4.1 核心表结构设计
用户相关表
-- 用户表
CREATE TABLE users (
id VARCHAR(36) PRIMARY KEY,
phone VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(50),
avatar VARCHAR(500),
membership_level ENUM('basic', 'professional', 'premium') DEFAULT 'basic',
status ENUM('active', 'inactive', 'suspended') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_phone (phone),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户健康档案表
CREATE TABLE user_health_profiles (
user_id VARCHAR(36) PRIMARY KEY,
blood_type ENUM('A', 'B', 'AB', 'O'),
height DECIMAL(5,2) COMMENT '身高(cm)',
weight DECIMAL(5,2) COMMENT '体重(kg)',
bmi DECIMAL(4,2) COMMENT '身体质量指数',
has_allergy BOOLEAN DEFAULT FALSE,
allergy_info TEXT,
chronic_diseases JSON COMMENT '慢性病信息',
family_history JSON COMMENT '家族病史',
emergency_contact JSON COMMENT '紧急联系人',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 健康数据记录表
CREATE TABLE health_data_records (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
data_type VARCHAR(50) NOT NULL COMMENT '数据类型: blood_pressure, blood_glucose, etc.',
value DECIMAL(10,2) NOT NULL,
unit VARCHAR(20) NOT NULL,
source VARCHAR(50) COMMENT '数据来源: manual, device, hospital',
recorded_at DATETIME NOT NULL,
notes TEXT,
metadata JSON COMMENT '扩展数据',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_type_time (user_id, data_type, recorded_at),
INDEX idx_recorded_at (recorded_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (YEAR(recorded_at))
SUBPARTITION BY KEY (user_id)
SUBPARTITIONS 10 (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026)
);服务相关表
-- 服务项目表
CREATE TABLE services (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(50) NOT NULL COMMENT 'category: companion, consultation, etc.',
description TEXT,
price DECIMAL(10,2) NOT NULL,
duration_minutes INT COMMENT '服务时长(分钟)',
is_active BOOLEAN DEFAULT TRUE,
sort_order INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_category (category),
INDEX idx_active (is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 订单表
CREATE TABLE orders (
id VARCHAR(36) PRIMARY KEY,
order_no VARCHAR(32) UNIQUE NOT NULL,
user_id VARCHAR(36) NOT NULL,
service_id VARCHAR(36) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status VARCHAR(20) NOT NULL,
payment_method VARCHAR(20),
payment_no VARCHAR(64),
paid_at DATETIME,
completed_at DATETIME,
cancel_reason TEXT,
contact_info JSON NOT NULL COMMENT '联系人信息',
service_info JSON NOT NULL COMMENT '服务信息',
metadata JSON COMMENT '扩展信息',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_order_no (order_no),
INDEX idx_status_created (status, created_at),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (service_id) REFERENCES services(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 预约表
CREATE TABLE appointments (
id VARCHAR(36) PRIMARY KEY,
order_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
service_id VARCHAR(36) NOT NULL,
scheduled_time DATETIME NOT NULL,
duration_minutes INT NOT NULL,
status VARCHAR(20) NOT NULL COMMENT 'scheduled, confirmed, in_progress, completed, cancelled',
staff_id VARCHAR(36) COMMENT '服务人员ID',
location_info JSON COMMENT '位置信息',
notes TEXT,
reminder_sent BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_time (user_id, scheduled_time),
INDEX idx_staff_time (staff_id, scheduled_time),
INDEX idx_status_time (status, scheduled_time),
UNIQUE KEY uk_order (order_id),
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (service_id) REFERENCES services(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;内容相关表
-- 健康知识库
CREATE TABLE health_knowledge (
id VARCHAR(36) PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content LONGTEXT NOT NULL,
category VARCHAR(50) NOT NULL,
tags JSON COMMENT '标签数组',
source VARCHAR(100) COMMENT '来源',
author VARCHAR(100),
is_recommended BOOLEAN DEFAULT FALSE,
view_count INT DEFAULT 0,
like_count INT DEFAULT 0,
is_published BOOLEAN DEFAULT TRUE,
published_at DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FULLTEXT idx_title_content (title, content),
INDEX idx_category (category),
INDEX idx_published (is_published, published_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- AI解读记录
CREATE TABLE ai_interpretations (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
report_type VARCHAR(50) NOT NULL,
original_data JSON NOT NULL COMMENT '原始数据',
interpretation TEXT NOT NULL COMMENT 'AI解读结果',
confidence_score DECIMAL(5,4) COMMENT '置信度',
suggestions JSON COMMENT '建议',
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_type (user_id, report_type),
INDEX idx_created (created_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;五、API接口设计规范
5.1 接口设计原则
RESTful风格:使用标准HTTP方法,资源化URL设计
版本控制:API版本在URL中体现(/api/v1/)
统一响应格式:标准化响应结构
错误处理:标准化的错误码和错误信息
分页与过滤:支持标准化的分页、排序、过滤
限流与认证:统一的认证和限流机制
5.2 统一响应格式
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
// 业务数据
},
"timestamp": 1633046400000,
"requestId": "req_1234567890"
}5.3 核心API接口
5.3.1 用户认证相关
# 发送验证码
POST /api/v1/auth/sms-code
Request:
{
"phone": "13800138000",
"scene": "login|register|reset"
}
Response:
{
"success": true,
"data": {
"expiresIn": 300
}
}
# 登录/注册
POST /api/v1/auth/login
Request:
{
"phone": "13800138000",
"code": "123456"
}
Response:
{
"success": true,
"data": {
"token": "jwt_token_string",
"user": {
"id": "user_id",
"name": "张三",
"phone": "13800138000",
"avatar": "https://xxx.com/avatar.jpg"
}
}
}5.3.2 健康数据相关
# 记录健康数据
POST /api/v1/health/records
Headers:
Authorization: Bearer {token}
Request:
{
"type": "blood_pressure",
"value": 120,
"unit": "mmHg",
"recordedAt": "2023-10-01T08:00:00Z",
"metadata": {
"systolic": 120,
"diastolic": 80,
"heartRate": 75
}
}
Response:
{
"success": true,
"data": {
"id": "record_id",
"type": "blood_pressure",
"value": 120,
"unit": "mmHg",
"recordedAt": "2023-10-01T08:00:00Z",
"indicators": {
"level": "normal",
"description": "血压正常"
}
}
}
# 查询健康趋势
GET /api/v1/health/trends
Headers:
Authorization: Bearer {token}
Query Parameters:
type=blood_pressure&period=7d&start=2023-10-01&end=2023-10-07
Response:
{
"success": true,
"data": {
"statistics": {
"average": 118.5,
"max": 130,
"min": 110,
"count": 21
},
"trend": "stable",
"dataPoints": [
{
"date": "2023-10-01",
"value": 120,
"time": "08:00"
}
],
"advice": [
{
"level": "info",
"content": "血压控制良好,请继续保持"
}
]
}
}5.3.3 服务预约相关
# 获取可预约时间
GET /api/v1/appointments/available-slots
Headers:
Authorization: Bearer {token}
Query Parameters:
serviceId=service_123&date=2023-10-10
Response:
{
"success": true,
"data": {
"date": "2023-10-10",
"slots": [
{
"time": "09:00",
"available": true
},
{
"time": "10:00",
"available": false,
"reason": "已约满"
}
]
}
}
# 创建预约
POST /api/v1/appointments
Headers:
Authorization: Bearer {token}
Request:
{
"serviceId": "service_123",
"scheduledTime": "2023-10-10T09:00:00Z",
"contactPerson": "张三",
"contactPhone": "13800138000",
"notes": "需要轮椅辅助"
}
Response:
{
"success": true,
"data": {
"appointmentId": "app_123456",
"orderNo": "ORDER20231010001",
"status": "pending",
"scheduledTime": "2023-10-10T09:00:00Z",
"amount": 299.00
}
}六、性能优化策略
6.1 前端性能优化
// 1. 图片优化
// 使用WebP格式,懒加载,CDN加速
const ImageOptimizer = {
getOptimizedUrl: (url, options = {}) => {
const params = new URLSearchParams();
if (options.width) params.append('w', options.width);
if (options.quality) params.append('q', options.quality);
if (options.format) params.append('fm', options.format);
return `${url}?${params.toString()}`;
}
};
// 2. 请求优化
// 防抖、节流、请求合并
class RequestOptimizer {
constructor() {
this.pendingRequests = new Map();
}
// 请求去重
async deduplicatedRequest(key, requestFn) {
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}
const promise = requestFn();
this.pendingRequests.set(key, promise);
try {
const result = await promise;
return result;
} finally {
this.pendingRequests.delete(key);
}
}
// 请求合并
async batchRequest(requests, batchSize = 5) {
const results = [];
for (let i = 0; i < requests.length; i += batchSize) {
const batch = requests.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(req => req())
);
results.push(...batchResults);
}
return results;
}
}
// 3. 缓存策略
class CacheManager {
constructor() {
this.memoryCache = new Map();
this.storage = Taro.getStorageSync;
}
async getWithCache(key, fetchFn, ttl = 5 * 60 * 1000) {
// 内存缓存
const memoryItem = this.memoryCache.get(key);
if (memoryItem && Date.now() - memoryItem.timestamp < ttl) {
return memoryItem.data;
}
// 本地存储缓存
try {
const storageItem = this.storage(key);
if (storageItem && Date.now() - storageItem.timestamp < ttl) {
// 更新内存缓存
this.memoryCache.set(key, storageItem);
return storageItem.data;
}
} catch (error) {
console.warn('Storage read error:', error);
}
// 获取新数据
const data = await fetchFn();
const cacheItem = {
data,
timestamp: Date.now()
};
// 更新缓存
this.memoryCache.set(key, cacheItem);
try {
this.storage.setItem(key, cacheItem);
} catch (error) {
console.warn('Storage write error:', error);
}
return data;
}
}6.2 后端性能优化
// 1. 多级缓存策略
@Service
@Slf4j
public class HealthDataCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private CaffeineCacheManager caffeineCacheManager;
private static final String HEALTH_DATA_PREFIX = "health:data:";
private static final long REDIS_TTL = 3600; // 1小时
private static final long LOCAL_TTL = 300; // 5分钟
/**
* 获取健康数据(多级缓存)
*/
public HealthDataVO getHealthDataWithCache(String userId, String dataType,
LocalDate date) {
String cacheKey = buildCacheKey(userId, dataType, date);
// 1. 本地缓存
Cache localCache = caffeineCacheManager.getCache("healthData");
HealthDataVO cached = localCache.get(cacheKey, HealthDataVO.class);
if (cached != null) {
return cached;
}
// 2. Redis缓存
try {
cached = (HealthDataVO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
// 回填本地缓存
localCache.put(cacheKey, cached);
return cached;
}
} catch (Exception e) {
log.warn("Redis cache error, falling back to DB", e);
}
// 3. 数据库查询
cached = healthDataRepository.findByUserIdAndTypeAndDate(userId, dataType, date);
if (cached != null) {
// 异步更新缓存
CompletableFuture.runAsync(() -> {
try {
// 更新本地缓存
localCache.put(cacheKey, cached);
// 更新Redis缓存
redisTemplate.opsForValue()
.set(cacheKey, cached, REDIS_TTL, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("Failed to update cache", e);
}
});
}
return cached;
}
private String buildCacheKey(String userId, String dataType, LocalDate date) {
return String.format("%s%s:%s:%s",
HEALTH_DATA_PREFIX, userId, dataType, date.toString());
}
}
// 2. 数据库查询优化
@Repository
public interface HealthDataRepository extends JpaRepository<HealthData, Long> {
// 使用覆盖索引
@Query(value = "SELECT recorded_at, value FROM health_data_records " +
"WHERE user_id = :userId AND data_type = :dataType " +
"AND recorded_at BETWEEN :start AND :end " +
"ORDER BY recorded_at",
nativeQuery = true)
List<Object[]> findTrendData(@Param("userId") String userId,
@Param("dataType") String dataType,
@Param("start") LocalDateTime start,
@Param("end") LocalDateTime end);
// 分页查询优化
@Query("SELECT h FROM HealthData h WHERE h.userId = :userId " +
"AND h.recordedAt >= :startDate ORDER BY h.recordedAt DESC")
Page<HealthData> findByUserAndDate(@Param("userId") String userId,
@Param("startDate") LocalDateTime startDate,
Pageable pageable);
}
// 3. 连接池配置
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
config.setConnectionTestQuery("SELECT 1");
config.setPoolName("HealthDBPool");
return new HikariDataSource(config);
}
}七、安全与监控
7.1 安全防护
// 1. 数据脱敏
@Component
public class DataMaskingUtil {
public static String maskPhone(String phone) {
if (StringUtils.isBlank(phone) || phone.length() < 7) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(7);
}
public static String maskIdCard(String idCard) {
if (StringUtils.isBlank(idCard) || idCard.length() < 15) {
return idCard;
}
return idCard.substring(0, 6) + "********" + idCard.substring(14);
}
public static String maskName(String name) {
if (StringUtils.isBlank(name)) {
return name;
}
if (name.length() == 1) {
return "*";
} else if (name.length() == 2) {
return name.charAt(0) + "*";
} else {
return name.charAt(0) + "*" + name.charAt(name.length() - 1);
}
}
}
// 2. SQL注入防护
@Aspect
@Component
@Slf4j
public class SqlInjectionAspect {
@Around("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object checkSqlInjection(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof String) {
String str = (String) arg;
if (containsSqlInjection(str)) {
log.warn("Potential SQL injection detected: {}", str);
throw new SecurityException("Invalid input detected");
}
} else if (arg instanceof Map) {
Map<?, ?> map = (Map<?, ?>) arg;
for (Object value : map.values()) {
if (value instanceof String &&
containsSqlInjection((String) value)) {
throw new SecurityException("Invalid input detected");
}
}
}
}
return joinPoint.proceed();
}
private boolean containsSqlInjection(String input) {
if (StringUtils.isBlank(input)) {
return false;
}
String upperInput = input.toUpperCase();
String[] sqlKeywords = {"SELECT", "INSERT", "UPDATE", "DELETE",
"DROP", "UNION", "OR", "AND", "'", "\"",
"--", ";", "/*", "*/"};
for (String keyword : sqlKeywords) {
if (upperInput.contains(keyword)) {
// 进一步检查是否是正常的业务输入
if (!isSafeKeywordUsage(upperInput, keyword)) {
return true;
}
}
}
return false;
}
}7.2 监控与告警
# Prometheus监控配置
spring:
application:
name: leiming-health-service
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
distribution:
percentiles-histogram:
http.server.requests: true
endpoint:
health:
show-details: always
# 自定义监控指标
@Component
public class BusinessMetrics {
private final MeterRegistry meterRegistry;
// 业务指标
private final Counter userRegisterCounter;
private final Counter healthDataRecordCounter;
private final Counter orderCreateCounter;
private final Timer apiResponseTimer;
public BusinessMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.userRegisterCounter = Counter.builder("business.user.register")
.description("用户注册数量")
.tag("application", "leiming-health")
.register(meterRegistry);
this.healthDataRecordCounter = Counter.builder("business.health.data.record")
.description("健康数据记录数量")
.tag("application", "leiming-health")
.register(meterRegistry);
this.orderCreateCounter = Counter.builder("business.order.create")
.description("订单创建数量")
.tag("application", "leiming-health")
.register(meterRegistry);
this.apiResponseTimer = Timer.builder("http.server.requests")
.description("API响应时间")
.publishPercentiles(0.5, 0.95, 0.99)
.register(meterRegistry);
}
public void recordUserRegister() {
userRegisterCounter.increment();
}
public void recordHealthData(String type) {
healthDataRecordCounter.increment();
meterRegistry.counter("business.health.data.record.type", "type", type)
.increment();
}
public Timer.Sample startApiTimer() {
return Timer.start(meterRegistry);
}
public void stopApiTimer(Timer.Sample sample, String uri, String method, int status) {
sample.stop(Timer.builder("http.server.requests")
.tags("uri", uri, "method", method, "status", String.valueOf(status))
.register(meterRegistry));
}
}八、部署与运维
8.1 Docker部署配置
# 后端服务Dockerfile
FROM openjdk:11-jre-slim
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 创建应用目录
RUN mkdir -p /app
WORKDIR /app
# 复制应用
COPY target/leiming-service.jar /app/app.jar
# 创建非root用户
RUN groupadd -r leiming && useradd -r -g leiming leiming
USER leiming
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]
EXPOSE 8080# docker-compose.yml
version: '3.8'
services:
# MySQL数据库
mysql:
image: mysql:8.0
container_name: leiming-mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: leiming
MYSQL_USER: leiming
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
- ./config/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
ports:
- "3306:3306"
networks:
- leiming-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
# Redis缓存
redis:
image: redis:7-alpine
container_name: leiming-redis
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
ports:
- "6379:6379"
networks:
- leiming-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# 应用服务
api-service:
build:
context: ./backend
dockerfile: Dockerfile
container_name: leiming-api
environment:
SPRING_PROFILES_ACTIVE: ${PROFILE:-prod}
MYSQL_HOST: mysql
REDIS_HOST: redis
ports:
- "8080:8080"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- leiming-network
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Nginx网关
nginx:
image: nginx:alpine
container_name: leiming-nginx
volumes:
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./config/nginx/conf.d:/etc/nginx/conf.d
ports:
- "80:80"
- "443:443"
depends_on:
- api-service
networks:
- leiming-network
networks:
leiming-network:
driver: bridge
volumes:
mysql_data:
redis_data:8.2 CI/CD流程
# GitHub Actions workflow
name: Build and Deploy
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Run unit tests
run: mvn test
- name: Run integration tests
run: mvn verify -Pintegration-test
- name: Code coverage
uses: codecov/codecov-action@v3
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Build with Maven
run: mvn clean package -DskipTests
- name: Build Docker image
run: |
docker build -t leiming-api:${{ github.sha }} .
docker tag leiming-api:${{ github.sha }} ${{ secrets.DOCKER_USERNAME }}/leiming-api:${{ github.sha }}
- name: Push to Docker Hub
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
docker push ${{ secrets.DOCKER_USERNAME }}/leiming-api:${{ github.sha }}
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: |
echo "Deploying to production..."
# SSH to server and deploy
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "
docker pull ${{ secrets.DOCKER_USERNAME }}/leiming-api:${{ github.sha }} &&
docker service update --image ${{ secrets.DOCKER_USERNAME }}/leiming-api:${{ github.sha }} leiming_api-service
"九、总结
济南雷鸣网络小程序的技术架构设计体现了以下核心原则:
高可用性:通过微服务架构、负载均衡、集群部署确保系统稳定
可扩展性:采用云原生架构,支持水平扩展
安全性:多重安全防护,符合医疗健康数据安全标准
高性能:多级缓存、数据库优化、异步处理
可维护性:清晰的代码结构、完整的监控体系、自动化部署
此架构能够支撑雷鸣网络的业务发展,为用户提供稳定、安全、高效的健康管理服务。随着业务发展,架构将持续演进,引入更多先进的技术和最佳实践。
业务专线:15966650167(同微信)
联系人:王经理
咨询请联系王经理 15966650167(微信同号),地址:济南市历下区解放路112号历东商务大厦。
【性能优化服务】
优化承诺:首屏加载时间<1.5秒,API响应P95<200ms
优化案例:已为超过200家客户提供性能优化服务
监测工具:自研全链路性能监控平台,7×24小时监控
性能专家:王经理 15966650167(微信同号,可提供免费性能评估报告)
公司地址:济南市历下区解放路112号历东商务大厦




