From ad56253552ea0d5506a5f5f957874655b2a05802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Sun, 8 Mar 2026 21:41:16 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=88=9B=E5=BB=BA=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/design/technical/DB-数据库设计.md | 505 +++++++++++++++++++++++++ 1 file changed, 505 insertions(+) create mode 100644 docs/design/technical/DB-数据库设计.md diff --git a/docs/design/technical/DB-数据库设计.md b/docs/design/technical/DB-数据库设计.md new file mode 100644 index 0000000..9f0e871 --- /dev/null +++ b/docs/design/technical/DB-数据库设计.md @@ -0,0 +1,505 @@ +# 健身房管理系统数据库设计文档 + +> 文档编号:GYM-DB-DESIGN-001 +> 版本:v1.0 +> 创建日期:2026-03-08 +> 最后更新日期:2026-03-08 +> 作者:张翔 +> 状态:正式发布 + +## 文档修订历史 + +| 版本 | 日期 | 作者 | 修订内容 | +| ---- | ---------- | ---- | -------- | +| v1.0 | 2026-03-08 | 张翔 | 创建数据库设计文档 | + +## 参考文档 + +- 《健身房管理系统基础版技术实现详细设计文档》 GYM-T-ILD-BASIC-001 +- 《健身房管理系统付费订阅版技术实现详细设计文档》 GYM-T-ILD-SUBSCRIPTION-001 +- PostgreSQL 官方文档 +- R2DBC 规范文档 + +--- + +## 一、数据库架构设计 + +### 1.1 多租户架构设计 + +本系统采用**共享数据库、共享 Schema、租户 ID 隔离**的多租户架构: + +``` +租户层级: +租户 (Tenant) → 门店 (Store) → 业务数据 +``` + +**租户隔离策略**: +- 所有业务表包含 `tenant_id` 字段 +- 查询时强制添加 `tenant_id` 过滤条件 +- 通过数据库视图实现租户级数据隔离 + +**门店隔离策略**: +- 门店级业务表包含 `store_id` 字段 +- 支持跨店约课的多门店数据关联 +- 通过配置继承实现门店级个性化 + +### 1.2 分库分表策略 + +**分库策略**(未来扩展): +- 按租户分库:大型租户(月交易额>100 万)独立数据库 +- 按业务分库:交易库、日志库、分析库分离 + +**分表策略**: +- 按时间分表:签到记录、预约记录按月分表 +- 按租户分表:大型租户数据独立表空间 + +### 1.3 数据库选型 + +**核心数据库**:PostgreSQL 15+ +- 完全支持 R2DBC 响应式驱动 +- JSONB 支持灵活的配置管理 +- 全文搜索支持 +- ACID 事务保证 + +**缓存数据库**:Redis 7+ +- 响应式缓存支持 +- 分布式锁 +- 过期策略 + +**搜索引擎**:Elasticsearch 8+(可选) +- 全文搜索 +- 复杂查询 +- 数据分析 + +--- + +## 二、核心表结构设计 + +### 2.1 会员域 + +#### 2.1.1 会员基础信息表(member) + +```sql +CREATE TABLE member ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + phone VARCHAR(11) NOT NULL, + name VARCHAR(50) NOT NULL, + gender SMALLINT NOT NULL, + birthday DATE, + height DECIMAL(5,2), + weight DECIMAL(5,2), + fitness_goal VARCHAR(100), + avatar_url VARCHAR(500), + wechat_openid VARCHAR(100), + status SMALLINT DEFAULT 1, + level SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_member_phone UNIQUE (tenant_id, phone), + CONSTRAINT fk_member_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_member_store FOREIGN KEY (store_id) REFERENCES store(id) +); + +-- 索引设计 +CREATE INDEX idx_member_tenant_store ON member(tenant_id, store_id); +CREATE INDEX idx_member_phone ON member(phone); +CREATE INDEX idx_member_status ON member(status); +CREATE INDEX idx_member_level ON member(level); +``` + +**字段说明**: +- `tenant_id`: 租户 ID,多租户隔离 +- `store_id`: 门店 ID,单店运营 +- `phone`: 手机号,唯一索引 +- `status`: 1-正常,2-沉默,3-流失预警,4-流失 +- `level`: 会员等级,1-普通,2-VIP,3-SVIP + +#### 2.1.2 会员卡表(member_card) + +```sql +CREATE TABLE member_card ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + card_type SMALLINT NOT NULL, + card_name VARCHAR(100) NOT NULL, + price DECIMAL(10,2) NOT NULL, + duration_days INT, + duration_times INT, + balance DECIMAL(10,2) DEFAULT 0.00, + start_date DATE NOT NULL, + end_date DATE NOT NULL, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_card_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_card_member FOREIGN KEY (member_id) REFERENCES member(id) +); + +-- 索引设计 +CREATE INDEX idx_card_member ON member_card(member_id); +CREATE INDEX idx_card_status ON member_card(status); +CREATE INDEX idx_card_end_date ON member_card(end_date); +``` + +**字段说明**: +- `card_type`: 1-时长卡,2-次卡,3-储值卡,4-组合卡 +- `duration_days`: 时长卡天数 +- `duration_times`: 次卡次数 +- `balance`: 储值卡余额 +- `status`: 1-有效,2-已过期,3-已退款 + +#### 2.1.3 会员权益表(member_benefit) + +```sql +CREATE TABLE member_benefit ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + card_id BIGINT NOT NULL, + benefit_type SMALLINT NOT NULL, + benefit_value DECIMAL(10,2) NOT NULL, + used_value DECIMAL(10,2) DEFAULT 0.00, + remaining_value DECIMAL(10,2) NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + + CONSTRAINT fk_benefit_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_benefit_member FOREIGN KEY (member_id) REFERENCES member(id), + CONSTRAINT fk_benefit_card FOREIGN KEY (card_id) REFERENCES member_card(id) +); + +-- 索引设计 +CREATE INDEX idx_benefit_member ON member_benefit(member_id); +CREATE INDEX idx_benefit_type ON member_benefit(benefit_type); +``` + +**字段说明**: +- `benefit_type`: 1-时长权益,2-次数权益,3-储值权益,4-等级权益 +- `benefit_value`: 权益总值 +- `used_value`: 已使用值 +- `remaining_value`: 剩余值 + +#### 2.1.4 会员生命周期表(member_lifecycle) + +```sql +CREATE TABLE member_lifecycle ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + stage SMALLINT NOT NULL, + enter_time TIMESTAMP NOT NULL, + exit_time TIMESTAMP, + exit_reason VARCHAR(200), + intervention VARCHAR(500), + intervention_result SMALLINT, + created_at TIMESTAMP DEFAULT NOW(), + + CONSTRAINT fk_lifecycle_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_lifecycle_member FOREIGN KEY (member_id) REFERENCES member(id) +); + +-- 索引设计 +CREATE INDEX idx_lifecycle_member ON member_lifecycle(member_id); +CREATE INDEX idx_lifecycle_stage ON member_lifecycle(stage); +``` + +**字段说明**: +- `stage`: 1-新会员,2-活跃期,3-沉默期,4-流失预警,5-流失 +- `intervention`: 干预措施 +- `intervention_result`: 1-成功,2-失败 + +--- + +### 2.2 预约域 + +#### 2.2.1 可预约资源表(booking_resource) + +```sql +CREATE TABLE booking_resource ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + resource_type SMALLINT NOT NULL, + resource_name VARCHAR(100) NOT NULL, + description VARCHAR(500), + capacity INT NOT NULL, + duration_minutes INT NOT NULL, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_resource_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_resource_store FOREIGN KEY (store_id) REFERENCES store(id) +); + +-- 索引设计 +CREATE INDEX idx_resource_tenant_store ON booking_resource(tenant_id, store_id); +CREATE INDEX idx_resource_type ON booking_resource(resource_type); +``` + +**字段说明**: +- `resource_type`: 1-团课,2-私教,3-器械,4-场地 +- `capacity`: 容量(人数) +- `status`: 1-启用,2-停用 + +#### 2.2.2 时段表(booking_slot) + +```sql +CREATE TABLE booking_slot ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + resource_id BIGINT NOT NULL, + coach_id BIGINT, + start_time TIMESTAMP NOT NULL, + end_time TIMESTAMP NOT NULL, + booked_count INT DEFAULT 0, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT fk_slot_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_slot_resource FOREIGN KEY (resource_id) REFERENCES booking_resource(id) +); + +-- 索引设计 +CREATE INDEX idx_slot_resource ON booking_slot(resource_id); +CREATE INDEX idx_slot_start_time ON booking_slot(start_time); +CREATE INDEX idx_slot_status ON booking_slot(status); +``` + +**字段说明**: +- `coach_id`: 教练 ID(私教课必填) +- `booked_count`: 已预约人数 +- `status`: 1-可预约,2-已满,3-已取消 + +#### 2.2.3 预约记录表(booking_record) + +```sql +CREATE TABLE booking_record ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + slot_id BIGINT NOT NULL, + booking_time TIMESTAMP NOT NULL, + status SMALLINT DEFAULT 1, + cancel_time TIMESTAMP, + cancel_reason VARCHAR(200), + checkin_time TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + + CONSTRAINT fk_booking_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id), + CONSTRAINT fk_booking_member FOREIGN KEY (member_id) REFERENCES member(id), + CONSTRAINT fk_booking_slot FOREIGN KEY (slot_id) REFERENCES booking_slot(id) +); + +-- 索引设计 +CREATE INDEX idx_booking_member ON booking_record(member_id); +CREATE INDEX idx_booking_slot ON booking_record(slot_id); +CREATE INDEX idx_booking_status ON booking_record(status); +``` + +**字段说明**: +- `booking_time`: 预约时间 +- `status`: 1-已预约,2-已签到,3-已取消,4-已爽约 +- `checkin_time`: 签到时间 + +--- + +### 2.3 订阅域 + +#### 2.3.1 租户模块配置表(tenant_module_config) + +```sql +CREATE TABLE tenant_module_config ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + module_code VARCHAR(32) NOT NULL, + enabled BOOLEAN NOT NULL, + config_data JSONB, + version INT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_tenant_module UNIQUE (tenant_id, module_code), + CONSTRAINT fk_tenant_module_config FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); + +-- 索引设计 +CREATE INDEX idx_tenant_module ON tenant_module_config(tenant_id, module_code); +``` + +#### 2.3.2 门店模块配置表(store_module_config) + +```sql +CREATE TABLE store_module_config ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + store_id BIGINT NOT NULL, + module_code VARCHAR(32) NOT NULL, + inherit_mode SMALLINT NOT NULL, + enabled BOOLEAN NOT NULL, + config_data JSONB, + version INT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_store_module UNIQUE (store_id, module_code), + CONSTRAINT fk_store_module_config FOREIGN KEY (store_id) REFERENCES store(id) +); + +-- 索引设计 +CREATE INDEX idx_store_module ON store_module_config(store_id, module_code); +``` + +#### 2.3.3 订阅记录表(subscription_record) + +```sql +CREATE TABLE subscription_record ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + subscription_no VARCHAR(32) NOT NULL, + module_code VARCHAR(32) NOT NULL, + billing_cycle SMALLINT NOT NULL, + amount DECIMAL(10,2) NOT NULL, + discount_amount DECIMAL(10,2) DEFAULT 0.00, + actual_amount DECIMAL(10,2) NOT NULL, + start_date DATE NOT NULL, + end_date DATE NOT NULL, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + created_by BIGINT, + updated_by BIGINT, + deleted_at TIMESTAMP DEFAULT NULL, + + CONSTRAINT uk_subscription_no UNIQUE (subscription_no), + CONSTRAINT fk_subscription_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id) +); + +-- 索引设计 +CREATE INDEX idx_subscription_tenant ON subscription_record(tenant_id); +CREATE INDEX idx_subscription_status ON subscription_record(status); +``` + +--- + +## 三、索引设计优化 + +### 3.1 核心索引清单 + +| 表名 | 索引字段 | 索引类型 | 说明 | +|------|---------|---------|------| +| member | (tenant_id, phone) | 唯一索引 | 租户级手机号唯一 | +| member | (tenant_id, store_id) | 复合索引 | 租户 + 门店查询 | +| member | (status) | 单列索引 | 会员状态筛选 | +| member_card | (member_id) | 复合索引 | 会员卡片查询 | +| member_card | (end_date) | 单列索引 | 到期提醒查询 | +| booking_record | (member_id, status) | 复合索引 | 会员预约查询 | +| booking_slot | (resource_id, start_time) | 复合索引 | 资源时段查询 | + +### 3.2 索引优化建议 + +1. **避免过度索引**:单表索引数不超过 5 个 +2. **优先复合索引**:将高频查询字段组合 +3. **定期分析索引使用**:通过 pg_stat_user_indexes 监控 +4. **及时删除无用索引**:减少写入开销 + +--- + +## 四、数据迁移策略 + +### 4.1 版本化管理 + +使用 **Flyway** 进行数据库版本管理: + +``` +db/ +├── migration/ +│ ├── V1__initial_schema.sql +│ ├── V2__add_member_lifecycle.sql +│ ├── V3__add_booking_resource.sql +│ └── ... +``` + +### 4.2 数据迁移流程 + +1. **开发环境**:创建迁移脚本 → 测试迁移 → 提交代码 +2. **测试环境**:自动执行迁移 → 验证数据 → 回归测试 +3. **生产环境**:备份数据 → 执行迁移 → 验证数据 → 监控告警 + +### 4.3 回滚策略 + +- 每个迁移脚本配备回滚脚本 +- 回滚前必须备份当前数据 +- 回滚后验证数据一致性 + +--- + +## 五、性能优化 + +### 5.1 查询优化 + +1. **避免 N+1 查询**:使用 JOIN 或批量查询 +2. **使用覆盖索引**:减少回表查询 +3. **分页优化**:使用游标分页替代 OFFSET + +### 5.2 连接池配置 + +```yaml +spring: + r2dbc: + pool: + max-size: 20 # 基础版 + max-size: 100 # 付费订阅版 + initial-size: 10 + max-idle-time: 30m + max-life-time: 60m +``` + +### 5.3 监控指标 + +- 连接池使用率 +- 慢查询(>1s) +- 锁等待时间 +- 表空间使用率 + +--- + +## 六、安全设计 + +### 6.1 数据加密 + +- 手机号:AES-256 加密 +- 身份证:AES-256 加密 +- 银行卡:AES-256 加密 + +### 6.2 数据脱敏 + +- 日志中的手机号:138****1234 +- 接口响应中的身份证:110101********1234 + +### 6.3 审计日志 + +- 关键操作记录(插入、更新、删除) +- 操作人、操作时间、IP 地址 +- 保留 180 天 + +--- + +**文档结束**