# 健身房管理系统详细设计文档 - 签到模块(LLD)
> 文档编号: GYM-LLD-003
> 版本: v1.0
> 日期: 2026-02-28
> 作者: 张翔
> 状态: 初稿
---
## 文档修订历史
| 版本 | 日期 | 作者 | 修订内容 |
|------|------|------|---------|
| v1.0 | 2026-02-28 | 张翔 | 初稿 |
---
## 参考文档
- 《健身房管理系统产品设计文档》 GYM-PRD-001
- 《健身房管理系统概要设计文档》 GYM-HLD-001
- Spring Boot 3 官方文档
- R2DBC 规范文档
- PostgreSQL 官方文档
---
## 一、模块概述
### 1.1 模块定位
签到模块是健身房管理系统的核心业务模块,负责管理会员的入场签到和课程签到,支持多种签到方式:
- **二维码签到**:会员出示二维码,扫码签到
- **人脸识别签到**:通过人脸识别设备自动签到
- **NFC签到**:会员卡或手机NFC感应签到
- **教练代签**:教练手动为会员签到
### 1.2 模块边界
```mermaid
graph TB
subgraph Internal[签到模块内部]
C1[签到网关]
C2[签到验证]
C3[签到记录]
C4[签到统计]
end
subgraph ExternalDeps[外部依赖]
D1[会员模块
查询会员信息、验证会员状态]
D2[权益模块
验证权益有效性、扣减权益]
D3[预约模块
查询预约信息、验证签到资格]
D4[设备模块
人脸识别设备、NFC读卡器]
D5[消息模块
发送签到通知]
end
subgraph Deps[被依赖]
U1[财务模块
签到消费记录]
U2[数据模块
签到数据分析、会员活跃度统计]
U3[考勤模块
教练考勤统计]
end
```
### 1.3 签到类型
| 签到类型 | 说明 | 触发条件 | 验证规则 |
|---------|------|---------|---------|
| **入场签到** | 会员进入健身房 | 扫码/人脸/NFC | 验证会员卡有效性 |
| **课程签到** | 会员参加预约课程 | 扫码/教练代签 | 验证预约记录、时间窗口 |
| **私教签到** | 会员上私教课 | 教练代签 | 验证私教预约、教练身份 |
| **活动签到** | 会员参加活动 | 扫码 | 验证活动报名 |
---
## 二、数据模型设计
### 2.1 实体关系图
```mermaid
erDiagram
member ||--o{ checkin_record : makes
booking_record ||--o{ checkin_record : for
device ||--o{ checkin_record : used
member ||--o{ member_face : has
member {
bigint id PK
varchar name
varchar phone
smallint status
}
booking_record {
bigint id PK
bigint member_id FK
bigint slot_id FK
smallint status
smallint checkin_status
}
device {
bigint id PK
varchar name
smallint type
varchar location
smallint status
}
checkin_record {
bigint id PK
bigint tenant_id FK
bigint store_id FK
bigint member_id FK
bigint booking_id FK
bigint device_id FK
smallint type
smallint method
smallint status
timestamp checkin_at
date checkin_date
}
member_face {
bigint id PK
bigint member_id FK
bytea face_feature
smallint status
}
```
### 2.2 数据表设计
#### 2.2.1 签到记录表 (checkin_record)
```sql
CREATE TABLE checkin_record (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
store_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
booking_id BIGINT, -- 关联预约记录ID
type SMALLINT NOT NULL, -- 1:入场 2:课程 3:私教 4:活动
method SMALLINT NOT NULL, -- 1:二维码 2:人脸 3:NFC 4:教练代签
device_id BIGINT, -- 签到设备ID
device_name VARCHAR(64), -- 设备名称
operator_id BIGINT, -- 操作人ID(教练代签时)
operator_name VARCHAR(64), -- 操作人姓名
status SMALLINT DEFAULT 1, -- 1:成功 2:失败 3:已取消
checkin_at TIMESTAMP NOT NULL, -- 签到时间
checkin_date DATE NOT NULL, -- 签到日期(便于统计)
location VARCHAR(128), -- 签到位置
latitude DECIMAL(10,7), -- 纬度
longitude DECIMAL(10,7), -- 经度
fail_reason VARCHAR(256), -- 失败原因
benefit_id BIGINT, -- 扣减的权益ID
benefit_type SMALLINT, -- 权益类型
benefit_value DECIMAL(10,2), -- 扣减值
extra_data JSONB, -- 扩展数据
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP DEFAULT NULL,
CONSTRAINT fk_checkin_member FOREIGN KEY (member_id) REFERENCES member(id),
CONSTRAINT fk_checkin_booking FOREIGN KEY (booking_id) REFERENCES booking_record(id)
);
CREATE INDEX idx_checkin_tenant ON checkin_record(tenant_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_checkin_store ON checkin_record(store_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_checkin_member ON checkin_record(member_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_checkin_date ON checkin_record(checkin_date) WHERE deleted_at IS NULL;
CREATE INDEX idx_checkin_type ON checkin_record(type) WHERE deleted_at IS NULL;
CREATE INDEX idx_checkin_status ON checkin_record(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_checkin_time ON checkin_record(checkin_at) WHERE deleted_at IS NULL;
```
#### 2.2.2 会员人脸信息表 (member_face)
```sql
CREATE TABLE member_face (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
face_feature BYTEA NOT NULL, -- 人脸特征值(加密存储)
face_image VARCHAR(512), -- 人脸照片URL
feature_version VARCHAR(32), -- 特征版本
quality_score DECIMAL(5,2), -- 质量分数
status SMALLINT DEFAULT 1, -- 1:正常 2:待更新 3:已禁用
last_match_at TIMESTAMP, -- 最后匹配时间
match_count INT DEFAULT 0, -- 匹配次数
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP DEFAULT NULL,
CONSTRAINT fk_face_member FOREIGN KEY (member_id) REFERENCES member(id),
CONSTRAINT uk_face_member UNIQUE (member_id)
);
CREATE INDEX idx_face_tenant ON member_face(tenant_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_face_status ON member_face(status) WHERE deleted_at IS NULL;
```
#### 2.2.3 签到设备表 (checkin_device)
```sql
CREATE TABLE checkin_device (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
store_id BIGINT NOT NULL,
name VARCHAR(64) NOT NULL, -- 设备名称
code VARCHAR(32) NOT NULL, -- 设备编码
type SMALLINT NOT NULL, -- 1:人脸识别机 2:NFC读卡器 3:扫码枪 4:一体机
sn VARCHAR(64), -- 序列号
location VARCHAR(128), -- 安装位置
ip_address VARCHAR(64), -- IP地址
mac_address VARCHAR(32), -- MAC地址
status SMALLINT DEFAULT 1, -- 1:在线 2:离线 3:维护中
last_heartbeat TIMESTAMP, -- 最后心跳时间
config JSONB, -- 设备配置
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP DEFAULT NULL,
CONSTRAINT fk_device_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id),
CONSTRAINT fk_device_store FOREIGN KEY (store_id) REFERENCES store(id),
CONSTRAINT uk_device_code UNIQUE (tenant_id, code)
);
CREATE INDEX idx_device_tenant ON checkin_device(tenant_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_device_store ON checkin_device(store_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_device_status ON checkin_device(status) WHERE deleted_at IS NULL;
```
#### 2.2.4 签到统计表 (checkin_statistics)
```sql
CREATE TABLE checkin_statistics (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
store_id BIGINT NOT NULL,
stat_date DATE NOT NULL, -- 统计日期
stat_type SMALLINT NOT NULL, -- 1:日统计 2:周统计 3:月统计
total_count INT DEFAULT 0, -- 总签到次数
entry_count INT DEFAULT 0, -- 入场签到次数
course_count INT DEFAULT 0, -- 课程签到次数
private_count INT DEFAULT 0, -- 私教签到次数
activity_count INT DEFAULT 0, -- 活动签到次数
new_member_count INT DEFAULT 0, -- 新会员签到数
active_member_count INT DEFAULT 0, -- 活跃会员数
peak_hour SMALLINT, -- 高峰时段
peak_count INT, -- 高峰人数
avg_duration INT, -- 平均停留时长(分钟)
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP DEFAULT NULL,
CONSTRAINT uk_stat_date UNIQUE (tenant_id, store_id, stat_date, stat_type)
);
CREATE INDEX idx_stat_tenant ON checkin_statistics(tenant_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_stat_store ON checkin_statistics(store_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_stat_date ON checkin_statistics(stat_date) WHERE deleted_at IS NULL;
```
#### 2.2.5 签到规则表 (checkin_rule)
```sql
CREATE TABLE checkin_rule (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
store_id BIGINT, -- NULL表示全局规则
rule_type SMALLINT NOT NULL, -- 1:入场规则 2:课程规则 3:私教规则
name VARCHAR(64) NOT NULL, -- 规则名称
description VARCHAR(256), -- 规则描述
time_before INT DEFAULT 30, -- 提前签到时间(分钟)
time_after INT DEFAULT 15, -- 迟到允许时间(分钟)
late_penalty DECIMAL(3,2) DEFAULT 0.00, -- 迟到扣款比例
absent_penalty DECIMAL(3,2) DEFAULT 1.00, -- 缺席扣款比例
allow_late BOOLEAN DEFAULT TRUE, -- 是否允许迟到签到
allow_absent BOOLEAN DEFAULT FALSE, -- 是否允许缺席
max_daily_entry INT DEFAULT 1, -- 每日最大入场次数
interval_minutes INT DEFAULT 0, -- 签到间隔(分钟)
status SMALLINT DEFAULT 1, -- 1:启用 2:禁用
priority 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 fk_rule_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id)
);
CREATE INDEX idx_rule_tenant ON checkin_rule(tenant_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_rule_store ON checkin_rule(store_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_rule_type ON checkin_rule(rule_type) WHERE deleted_at IS NULL;
```
---
## 三、领域模型设计
### 3.1 聚合设计
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 签到聚合设计 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ CheckinRecord (聚合根) │ │
│ ├───────────────────────────────────────────────────────────────────┤ │
│ │ - id: Long │ │
│ │ - tenantId: Long │ │
│ │ - storeId: Long │ │
│ │ - memberId: Long │ │
│ │ - bookingId: Long? │ │
│ │ - type: CheckinType │ │
│ │ - method: CheckinMethod │ │
│ │ - device: DeviceInfo? │ │
│ │ - operator: OperatorInfo? │ │
│ │ - status: CheckinStatus │ │
│ │ - checkinAt: LocalDateTime │ │
│ │ - benefit: BenefitDeduction? │ │
│ │ │ │
│ │ 行为: │ │
│ │ + checkin(): void │ │
│ │ + cancel(reason: String): void │ │
│ │ + isLate(): Boolean │ │
│ │ + getDuration(): Duration │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────┐ ┌───────────────────────┐ │
│ │ CheckinGateway │ │ CheckinValidator │ │
│ │ (签到网关) │ │ (签到验证器) │ │
│ ├───────────────────────┤ ├───────────────────────┤ │
│ │ + processQRCode() │ │ + validateMember() │ │
│ │ + processFace() │ │ + validateBooking() │ │
│ │ + processNFC() │ │ + validateBenefit() │ │
│ │ + processManual() │ │ + validateRule() │ │
│ └───────────────────────┘ └───────────────────────┘ │
│ │
│ ┌───────────────────────┐ ┌───────────────────────┐ │
│ │ CheckinStatistics │ │ FaceRecognition │ │
│ │ (签到统计) │ │ (人脸识别) │ │
│ ├───────────────────────┤ ├───────────────────────┤ │
│ │ + dailyStats() │ │ + register() │ │
│ │ + weeklyStats() │ │ + match() │ │
│ │ + monthlyStats() │ │ + update() │ │
│ │ + memberStats() │ │ + delete() │ │
│ └───────────────────────┘ └───────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 3.2 值对象设计
```java
public enum CheckinType {
ENTRY(1, "入场签到"),
COURSE(2, "课程签到"),
PRIVATE(3, "私教签到"),
ACTIVITY(4, "活动签到");
private final int code;
private final String desc;
}
public enum CheckinMethod {
QRCODE(1, "二维码"),
FACE(2, "人脸识别"),
NFC(3, "NFC"),
MANUAL(4, "教练代签");
private final int code;
private final String desc;
}
public enum CheckinStatus {
SUCCESS(1, "成功"),
FAILED(2, "失败"),
CANCELLED(3, "已取消");
private final int code;
private final String desc;
}
public record DeviceInfo(
Long deviceId,
String deviceName,
String location
) {}
public record OperatorInfo(
Long operatorId,
String operatorName,
String operatorRole
) {}
public record BenefitDeduction(
Long benefitId,
Integer benefitType,
BigDecimal benefitValue
) {}
public record CheckinResult(
boolean success,
String message,
CheckinRecord record,
List warnings
) {}
```
### 3.3 领域服务设计
```java
public interface CheckinDomainService {
Mono processCheckin(CheckinRequest request);
Mono cancelCheckin(Long checkinId, String reason);
Mono validateCheckinEligibility(Long memberId, CheckinType type, Long bookingId);
Mono getCheckinRecord(Long checkinId);
Flux getMemberCheckinHistory(Long memberId, LocalDate startDate, LocalDate endDate);
}
public interface FaceRecognitionService {
Mono registerFace(Long memberId, byte[] faceImage);
Mono matchFace(byte[] faceFeature, Long tenantId);
Mono updateFace(Long memberId, byte[] faceImage);
Mono deleteFace(Long memberId);
}
public interface CheckinStatisticsService {
Mono generateDailyStatistics(Long tenantId, Long storeId, LocalDate date);
Mono getDailyStatistics(Long tenantId, Long storeId, LocalDate date);
Mono