Files
gym-manage/docs/design/technical/DB-数据库设计.md
T
2026-03-08 21:41:16 +08:00

15 KiB
Raw Blame History

健身房管理系统数据库设计文档

文档编号: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

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-VIP3-SVIP

2.1.2 会员卡表(member_card

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

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

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

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

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

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

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

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

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 连接池配置

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 天

文档结束