1347 lines
54 KiB
Markdown
1347 lines
54 KiB
Markdown
# 健身房管理系统基础版详细设计文档(LLD)
|
|
|
|
> 文档编号: GYM-LLD-BASIC-001
|
|
> 版本: v1.0
|
|
> 日期: 2026-03-04
|
|
> 作者: 张翔
|
|
> 状态: 初稿
|
|
|
|
---
|
|
|
|
## 文档修订历史
|
|
|
|
| 版本 | 日期 | 作者 | 修订内容 |
|
|
| ---- | ---------- | ---- | -------- |
|
|
| v1.0 | 2026-03-04 | 张翔 | 创建基础版详细设计 |
|
|
|
|
---
|
|
|
|
## 参考文档
|
|
|
|
- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
|
|
- 《健身房管理系统基础版业务概要设计文档》 GYM-HLD-BASIC-001
|
|
- 《健身房管理系统详细设计文档》 GYM-LLD-000
|
|
- Spring Boot 3 官方文档
|
|
- R2DBC 规范文档
|
|
- PostgreSQL 官方文档
|
|
|
|
---
|
|
|
|
## 一、系统架构设计
|
|
|
|
### 1.1 总体架构
|
|
|
|
采用分层架构 + 模块化设计:
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ 基础版总体架构 │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 客户端层 │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 会员小程序 (uniapp+Vue3) │ │
|
|
│ │ • 教练端App (uniapp+Vue3) │ │
|
|
│ │ • 管理后台PC (Vue3+Vite) │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ API Gateway 统一网关 │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 路由转发 • 认证鉴权 • 限流熔断 • 日志追踪 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 业务层 │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 会员服务 (Member Service) │ │
|
|
│ │ • 预约服务 (Booking Service) │ │
|
|
│ │ • 签到服务 (CheckIn Service) │ │
|
|
│ │ • 数据服务 (Data Service) │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 公共服务层 │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 认证服务 • 消息服务 • 文件服务 • 缓存服务 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 基础设施层 │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • PostgreSQL • R2DBC • Caffeine • Redis(可选) │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 外部服务层 │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 微信开放平台 • 短信服务 • 支付服务 • OSS存储 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 1.2 技术架构
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ 技术架构 │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 前端技术栈 │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • uniapp (跨平台小程序) • Vue3 (前端框架) │ │
|
|
│ │ • Vite (构建工具) • TypeScript (类型安全) │ │
|
|
│ │ • Pinia (状态管理) • Element Plus (UI组件库) │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 后端技术栈 │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • Spring Boot 3.2 (应用框架) • Spring Security (安全框架) │ │
|
|
│ │ • R2DBC (响应式数据库) • Spring WebFlux (响应式Web) │ │
|
|
│ │ • Caffeine (本地缓存) • Redis (分布式缓存) │ │
|
|
│ │ • PostgreSQL (关系型数据库) • MyBatis (ORM框架) │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 部署架构 │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • Docker (容器化) • Kubernetes (容器编排) │ │
|
|
│ │ • Nginx (反向代理) • ELK (日志收集) │ │
|
|
│ │ • Prometheus (监控) • Grafana (可视化) │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 二、模块设计
|
|
|
|
### 2.1 会员模块
|
|
|
|
#### 2.1.1 模块概述
|
|
|
|
会员模块是基础版的核心基础模块,负责管理会员全生命周期,包括:
|
|
|
|
- 会员注册与信息管理
|
|
- 会员卡购买与管理
|
|
- 会员权益(时长/次数/储值/等级)管理
|
|
|
|
#### 2.1.2 数据模型设计
|
|
|
|
**会员表 (member)**
|
|
|
|
```sql
|
|
CREATE TABLE member (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant_id BIGINT NOT NULL,
|
|
store_id BIGINT NOT NULL,
|
|
member_no VARCHAR(32) NOT NULL,
|
|
name VARCHAR(64),
|
|
phone VARCHAR(64) NOT NULL,
|
|
phone_mask VARCHAR(20),
|
|
avatar VARCHAR(512),
|
|
gender SMALLINT,
|
|
birthday DATE,
|
|
height INT,
|
|
weight DECIMAL(5,2),
|
|
fitness_goal VARCHAR(64),
|
|
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_member_tenant_no UNIQUE (tenant_id, member_no),
|
|
CONSTRAINT uk_member_phone UNIQUE (phone),
|
|
CONSTRAINT fk_member_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id),
|
|
CONSTRAINT fk_member_store FOREIGN KEY (store_id) REFERENCES store(id)
|
|
);
|
|
```
|
|
|
|
**会员卡表 (member_card)**
|
|
|
|
```sql
|
|
CREATE TABLE member_card (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant_id BIGINT NOT NULL,
|
|
store_id BIGINT NOT NULL,
|
|
member_id BIGINT NOT NULL,
|
|
card_no VARCHAR(32) NOT NULL,
|
|
card_type SMALLINT NOT NULL,
|
|
card_name VARCHAR(64) NOT NULL,
|
|
total_amount DECIMAL(10,2),
|
|
balance DECIMAL(10,2),
|
|
total_count INT,
|
|
balance_count INT,
|
|
valid_days INT,
|
|
valid_from DATE,
|
|
valid_to DATE,
|
|
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_member_card_no UNIQUE (card_no),
|
|
CONSTRAINT fk_member_card_member FOREIGN KEY (member_id) REFERENCES member(id)
|
|
);
|
|
```
|
|
|
|
#### 2.1.3 核心服务设计
|
|
|
|
**会员注册服务**
|
|
|
|
```java
|
|
@Service
|
|
public class MemberRegistrationService {
|
|
|
|
@Autowired
|
|
private MemberRepository memberRepository;
|
|
|
|
@Autowired
|
|
private SmsService smsService;
|
|
|
|
@Autowired
|
|
private MemberCardService memberCardService;
|
|
|
|
/**
|
|
* 会员注册
|
|
*/
|
|
@Transactional
|
|
public Member register(MemberRegistrationRequest request) {
|
|
validatePhone(request.getPhone());
|
|
validateSmsCode(request.getPhone(), request.getSmsCode());
|
|
|
|
Member member = new Member();
|
|
member.setTenantId(request.getTenantId());
|
|
member.setStoreId(request.getStoreId());
|
|
member.setMemberNo(generateMemberNo(request.getTenantId()));
|
|
member.setName(request.getName());
|
|
member.setPhone(encryptPhone(request.getPhone()));
|
|
member.setPhoneMask(maskPhone(request.getPhone()));
|
|
member.setGender(request.getGender());
|
|
member.setBirthday(request.getBirthday());
|
|
member.setHeight(request.getHeight());
|
|
member.setWeight(request.getWeight());
|
|
member.setFitnessGoal(request.getFitnessGoal());
|
|
member.setStatus(1);
|
|
|
|
return memberRepository.save(member);
|
|
}
|
|
|
|
/**
|
|
* 验证手机号
|
|
*/
|
|
private void validatePhone(String phone) {
|
|
if (!memberRepository.findByPhone(phone).isEmpty()) {
|
|
throw new BusinessException("手机号已存在");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 验证短信验证码
|
|
*/
|
|
private void validateSmsCode(String phone, String smsCode) {
|
|
if (!smsService.verifySmsCode(phone, smsCode)) {
|
|
throw new BusinessException("验证码错误");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 生成会员号
|
|
*/
|
|
private String generateMemberNo(Long tenantId) {
|
|
String prefix = "M" + tenantId;
|
|
String timestamp = String.valueOf(System.currentTimeMillis());
|
|
String random = String.valueOf(new Random().nextInt(1000));
|
|
return prefix + timestamp.substring(timestamp.length() - 8) + random;
|
|
}
|
|
|
|
/**
|
|
* 加密手机号
|
|
*/
|
|
private String encryptPhone(String phone) {
|
|
return AESUtil.encrypt(phone);
|
|
}
|
|
|
|
/**
|
|
* 脱敏手机号
|
|
*/
|
|
private String maskPhone(String phone) {
|
|
return phone.substring(0, 3) + "****" + phone.substring(7);
|
|
}
|
|
}
|
|
```
|
|
|
|
**会员卡购买服务**
|
|
|
|
```java
|
|
@Service
|
|
public class MemberCardPurchaseService {
|
|
|
|
@Autowired
|
|
private MemberCardRepository memberCardRepository;
|
|
|
|
@Autowired
|
|
private PaymentService paymentService;
|
|
|
|
@Autowired
|
|
private BenefitService benefitService;
|
|
|
|
/**
|
|
* 购买会员卡
|
|
*/
|
|
@Transactional
|
|
public MemberCard purchase(MemberCardPurchaseRequest request) {
|
|
Member member = memberRepository.findById(request.getMemberId())
|
|
.orElseThrow(() -> new BusinessException("会员不存在"));
|
|
|
|
Payment payment = paymentService.createPayment(request);
|
|
|
|
MemberCard memberCard = new MemberCard();
|
|
memberCard.setTenantId(member.getTenantId());
|
|
memberCard.setStoreId(member.getStoreId());
|
|
memberCard.setMemberId(member.getId());
|
|
memberCard.setCardNo(generateCardNo(member.getTenantId()));
|
|
memberCard.setCardType(request.getCardType());
|
|
memberCard.setCardName(request.getCardName());
|
|
memberCard.setTotalAmount(request.getAmount());
|
|
memberCard.setBalance(request.getAmount());
|
|
memberCard.setTotalCount(request.getCount());
|
|
memberCard.setBalanceCount(request.getCount());
|
|
memberCard.setValidDays(request.getValidDays());
|
|
memberCard.setValidFrom(LocalDate.now());
|
|
memberCard.setValidTo(LocalDate.now().plusDays(request.getValidDays()));
|
|
memberCard.setStatus(1);
|
|
|
|
memberCard = memberCardRepository.save(memberCard);
|
|
|
|
benefitService.createBenefits(memberCard);
|
|
|
|
return memberCard;
|
|
}
|
|
|
|
/**
|
|
* 生成卡号
|
|
*/
|
|
private String generateCardNo(Long tenantId) {
|
|
String prefix = "C" + tenantId;
|
|
String timestamp = String.valueOf(System.currentTimeMillis());
|
|
String random = String.valueOf(new Random().nextInt(1000));
|
|
return prefix + timestamp.substring(timestamp.length() - 8) + random;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.2 预约模块
|
|
|
|
#### 2.2.1 模块概述
|
|
|
|
预约模块是基础版的核心业务模块,负责管理团课预约,包括:
|
|
|
|
- 团课管理
|
|
- 团课预约
|
|
- 预约取消
|
|
|
|
#### 2.2.2 数据模型设计
|
|
|
|
**课程表 (course)**
|
|
|
|
```sql
|
|
CREATE TABLE course (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant_id BIGINT NOT NULL,
|
|
name VARCHAR(128) NOT NULL,
|
|
code VARCHAR(32),
|
|
type SMALLINT NOT NULL,
|
|
category VARCHAR(64),
|
|
description TEXT,
|
|
cover_image VARCHAR(512),
|
|
duration INT NOT NULL,
|
|
capacity INT DEFAULT 20,
|
|
min_capacity INT DEFAULT 1,
|
|
difficulty SMALLINT DEFAULT 1,
|
|
calories INT,
|
|
equipment VARCHAR(256),
|
|
benefits JSONB,
|
|
price DECIMAL(10,2),
|
|
price_type SMALLINT DEFAULT 1,
|
|
price_value DECIMAL(10,2),
|
|
advance_days INT DEFAULT 7,
|
|
cancel_hours INT DEFAULT 2,
|
|
cancel_penalty DECIMAL(3,2) DEFAULT 0.00,
|
|
status SMALLINT DEFAULT 1,
|
|
sort_order 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_course_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id)
|
|
);
|
|
```
|
|
|
|
**课程时段表 (course_slot)**
|
|
|
|
```sql
|
|
CREATE TABLE course_slot (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant_id BIGINT NOT NULL,
|
|
store_id BIGINT NOT NULL,
|
|
course_id BIGINT NOT NULL,
|
|
coach_id BIGINT,
|
|
slot_date DATE NOT NULL,
|
|
start_time TIME NOT NULL,
|
|
end_time TIME NOT NULL,
|
|
capacity INT DEFAULT 20,
|
|
booked_count INT DEFAULT 0,
|
|
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 fk_course_slot_course FOREIGN KEY (course_id) REFERENCES course(id),
|
|
CONSTRAINT fk_course_slot_coach FOREIGN KEY (coach_id) REFERENCES coach(id)
|
|
);
|
|
```
|
|
|
|
**预约记录表 (booking_record)**
|
|
|
|
```sql
|
|
CREATE TABLE booking_record (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant_id BIGINT NOT NULL,
|
|
store_id BIGINT NOT NULL,
|
|
member_id BIGINT NOT NULL,
|
|
course_id BIGINT NOT NULL,
|
|
slot_id BIGINT NOT NULL,
|
|
booking_no VARCHAR(32) NOT NULL,
|
|
status SMALLINT DEFAULT 1,
|
|
booked_at TIMESTAMP DEFAULT NOW(),
|
|
cancelled_at TIMESTAMP,
|
|
cancel_reason VARCHAR(256),
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW(),
|
|
created_by BIGINT,
|
|
updated_by BIGINT,
|
|
deleted_at TIMESTAMP DEFAULT NULL,
|
|
|
|
CONSTRAINT uk_booking_no UNIQUE (booking_no),
|
|
CONSTRAINT fk_booking_member FOREIGN KEY (member_id) REFERENCES member(id),
|
|
CONSTRAINT fk_booking_slot FOREIGN KEY (slot_id) REFERENCES course_slot(id)
|
|
);
|
|
```
|
|
|
|
#### 2.2.3 核心服务设计
|
|
|
|
**团课预约服务**
|
|
|
|
```java
|
|
@Service
|
|
public class CourseBookingService {
|
|
|
|
@Autowired
|
|
private BookingRecordRepository bookingRecordRepository;
|
|
|
|
@Autowired
|
|
private CourseSlotRepository courseSlotRepository;
|
|
|
|
@Autowired
|
|
private BenefitService benefitService;
|
|
|
|
@Autowired
|
|
private MessageService messageService;
|
|
|
|
/**
|
|
* 预约团课
|
|
*/
|
|
@Transactional
|
|
public BookingRecord book(CourseBookingRequest request) {
|
|
validateBookingTime(request.getSlotId());
|
|
validateCapacity(request.getSlotId());
|
|
validateMemberBenefit(request.getMemberId(), request.getSlotId());
|
|
|
|
CourseSlot slot = courseSlotRepository.findById(request.getSlotId())
|
|
.orElseThrow(() -> new BusinessException("课程时段不存在"));
|
|
|
|
BookingRecord record = new BookingRecord();
|
|
record.setTenantId(slot.getTenantId());
|
|
record.setStoreId(slot.getStoreId());
|
|
record.setMemberId(request.getMemberId());
|
|
record.setCourseId(slot.getCourseId());
|
|
record.setSlotId(slot.getId());
|
|
record.setBookingNo(generateBookingNo(slot.getTenantId()));
|
|
record.setStatus(1);
|
|
|
|
record = bookingRecordRepository.save(record);
|
|
|
|
slot.setBookedCount(slot.getBookedCount() + 1);
|
|
courseSlotRepository.save(slot);
|
|
|
|
benefitService.deductBenefit(request.getMemberId(), slot.getCourseId());
|
|
|
|
messageService.sendBookingNotification(record);
|
|
|
|
return record;
|
|
}
|
|
|
|
/**
|
|
* 验证预约时间
|
|
*/
|
|
private void validateBookingTime(Long slotId) {
|
|
CourseSlot slot = courseSlotRepository.findById(slotId)
|
|
.orElseThrow(() -> new BusinessException("课程时段不存在"));
|
|
|
|
LocalDateTime slotDateTime = LocalDateTime.of(slot.getSlotDate(), slot.getStartTime());
|
|
if (slotDateTime.isBefore(LocalDateTime.now().plusMinutes(30))) {
|
|
throw new BusinessException("预约时间过短");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 验证容量
|
|
*/
|
|
private void validateCapacity(Long slotId) {
|
|
CourseSlot slot = courseSlotRepository.findById(slotId)
|
|
.orElseThrow(() -> new BusinessException("课程时段不存在"));
|
|
|
|
if (slot.getBookedCount() >= slot.getCapacity()) {
|
|
throw new BusinessException("课程已满");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 验证会员权益
|
|
*/
|
|
private void validateMemberBenefit(Long memberId, Long slotId) {
|
|
if (!benefitService.hasBenefit(memberId, slotId)) {
|
|
throw new BusinessException("会员权益不足");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 生成预约号
|
|
*/
|
|
private String generateBookingNo(Long tenantId) {
|
|
String prefix = "B" + tenantId;
|
|
String timestamp = String.valueOf(System.currentTimeMillis());
|
|
String random = String.valueOf(new Random().nextInt(1000));
|
|
return prefix + timestamp.substring(timestamp.length() - 8) + random;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.3 签到模块
|
|
|
|
#### 2.3.1 模块概述
|
|
|
|
签到模块是基础版的核心业务模块,负责管理会员的入场签到,支持:
|
|
|
|
- 二维码签到
|
|
- 签到记录管理
|
|
|
|
#### 2.3.2 数据模型设计
|
|
|
|
**签到记录表 (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,
|
|
type SMALLINT NOT NULL,
|
|
method SMALLINT NOT NULL,
|
|
status SMALLINT DEFAULT 1,
|
|
checkin_at TIMESTAMP NOT NULL,
|
|
checkin_date DATE NOT NULL,
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW(),
|
|
created_by BIGINT,
|
|
updated_by BIGINT,
|
|
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)
|
|
);
|
|
```
|
|
|
|
#### 2.3.3 核心服务设计
|
|
|
|
**扫码签到服务**
|
|
|
|
```java
|
|
@Service
|
|
public class QRCodeCheckInService {
|
|
|
|
@Autowired
|
|
private CheckInRecordRepository checkInRecordRepository;
|
|
|
|
@Autowired
|
|
private MemberRepository memberRepository;
|
|
|
|
@Autowired
|
|
private MemberCardRepository memberCardRepository;
|
|
|
|
/**
|
|
* 扫码签到
|
|
*/
|
|
@Transactional
|
|
public CheckInRecord checkIn(QRCodeCheckInRequest request) {
|
|
Member member = memberRepository.findByQrCode(request.getQrCode())
|
|
.orElseThrow(() -> new BusinessException("会员不存在"));
|
|
|
|
validateMemberCard(member.getId());
|
|
|
|
CheckInRecord record = new CheckInRecord();
|
|
record.setTenantId(member.getTenantId());
|
|
record.setStoreId(member.getStoreId());
|
|
record.setMemberId(member.getId());
|
|
record.setType(1);
|
|
record.setMethod(1);
|
|
record.setStatus(1);
|
|
record.setCheckInAt(LocalDateTime.now());
|
|
record.setCheckInDate(LocalDate.now());
|
|
|
|
return checkInRecordRepository.save(record);
|
|
}
|
|
|
|
/**
|
|
* 验证会员卡
|
|
*/
|
|
private void validateMemberCard(Long memberId) {
|
|
List<MemberCard> cards = memberCardRepository.findByMemberId(memberId);
|
|
if (cards.isEmpty()) {
|
|
throw new BusinessException("会员卡不存在");
|
|
}
|
|
|
|
boolean hasValidCard = cards.stream()
|
|
.anyMatch(card -> card.getStatus() == 1 &&
|
|
(card.getValidTo() == null || card.getValidTo().isAfter(LocalDate.now())));
|
|
|
|
if (!hasValidCard) {
|
|
throw new BusinessException("会员卡无效");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.4 数据统计模块
|
|
|
|
#### 2.4.1 模块概述
|
|
|
|
数据统计模块是基础版的核心业务模块,负责提供基础数据统计,包括:
|
|
|
|
- 会员数据统计
|
|
- 预约数据统计
|
|
- 签到数据统计
|
|
|
|
#### 2.4.2 核心服务设计
|
|
|
|
**数据统计服务**
|
|
|
|
```java
|
|
@Service
|
|
public class DataStatisticsService {
|
|
|
|
@Autowired
|
|
private MemberRepository memberRepository;
|
|
|
|
@Autowired
|
|
private BookingRecordRepository bookingRecordRepository;
|
|
|
|
@Autowired
|
|
private CheckInRecordRepository checkInRecordRepository;
|
|
|
|
/**
|
|
* 获取会员数据统计
|
|
*/
|
|
public MemberStatistics getMemberStatistics(Long tenantId, Long storeId, LocalDate startDate, LocalDate endDate) {
|
|
long totalMembers = memberRepository.countByTenantIdAndStoreIdAndCreatedAtBetween(
|
|
tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
|
|
|
|
long activeMembers = memberRepository.countActiveMembers(tenantId, storeId, startDate, endDate);
|
|
|
|
MemberStatistics statistics = new MemberStatistics();
|
|
statistics.setTotalMembers(totalMembers);
|
|
statistics.setActiveMembers(activeMembers);
|
|
statistics.setNewMembers(totalMembers);
|
|
|
|
return statistics;
|
|
}
|
|
|
|
/**
|
|
* 获取预约数据统计
|
|
*/
|
|
public BookingStatistics getBookingStatistics(Long tenantId, Long storeId, LocalDate startDate, LocalDate endDate) {
|
|
long totalBookings = bookingRecordRepository.countByTenantIdAndStoreIdAndBookedAtBetween(
|
|
tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
|
|
|
|
long cancelledBookings = bookingRecordRepository.countCancelledBookings(tenantId, storeId, startDate, endDate);
|
|
|
|
BookingStatistics statistics = new BookingStatistics();
|
|
statistics.setTotalBookings(totalBookings);
|
|
statistics.setCancelledBookings(cancelledBookings);
|
|
statistics.setSuccessBookings(totalBookings - cancelledBookings);
|
|
|
|
return statistics;
|
|
}
|
|
|
|
/**
|
|
* 获取签到数据统计
|
|
*/
|
|
public CheckInStatistics getCheckInStatistics(Long tenantId, Long storeId, LocalDate startDate, LocalDate endDate) {
|
|
long totalCheckIns = checkInRecordRepository.countByTenantIdAndStoreIdAndCheckInAtBetween(
|
|
tenantId, storeId, startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
|
|
|
|
CheckInStatistics statistics = new CheckInStatistics();
|
|
statistics.setTotalCheckIns(totalCheckIns);
|
|
|
|
return statistics;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.5 系统管理模块
|
|
|
|
#### 2.5.1 模块概述
|
|
|
|
系统管理模块是基础版的核心基础模块,负责管理系统用户和权限,包括:
|
|
|
|
- 用户管理
|
|
- 角色权限管理
|
|
|
|
#### 2.5.2 数据模型设计
|
|
|
|
**用户表 (user)**
|
|
|
|
```sql
|
|
CREATE TABLE user (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant_id BIGINT NOT NULL,
|
|
store_id BIGINT,
|
|
username VARCHAR(64) NOT NULL,
|
|
password VARCHAR(256) NOT NULL,
|
|
name VARCHAR(64) NOT NULL,
|
|
phone VARCHAR(64),
|
|
email VARCHAR(128),
|
|
avatar VARCHAR(512),
|
|
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_user_username UNIQUE (username),
|
|
CONSTRAINT fk_user_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id),
|
|
CONSTRAINT fk_user_store FOREIGN KEY (store_id) REFERENCES store(id)
|
|
);
|
|
```
|
|
|
|
**角色表 (role)**
|
|
|
|
```sql
|
|
CREATE TABLE role (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant_id BIGINT NOT NULL,
|
|
name VARCHAR(64) NOT NULL,
|
|
code VARCHAR(32) NOT NULL,
|
|
description VARCHAR(256),
|
|
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_role_code UNIQUE (code),
|
|
CONSTRAINT fk_role_tenant FOREIGN KEY (tenant_id) REFERENCES tenant(id)
|
|
);
|
|
```
|
|
|
|
**用户角色关联表 (user_role)**
|
|
|
|
```sql
|
|
CREATE TABLE user_role (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT NOT NULL,
|
|
role_id BIGINT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
created_by BIGINT,
|
|
|
|
CONSTRAINT uk_user_role UNIQUE (user_id, role_id),
|
|
CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES user(id),
|
|
CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES role(id)
|
|
);
|
|
```
|
|
|
|
#### 2.5.3 核心服务设计
|
|
|
|
**用户管理服务**
|
|
|
|
```java
|
|
@Service
|
|
public class UserService {
|
|
|
|
@Autowired
|
|
private UserRepository userRepository;
|
|
|
|
@Autowired
|
|
private UserRoleRepository userRoleRepository;
|
|
|
|
@Autowired
|
|
private PasswordEncoder passwordEncoder;
|
|
|
|
/**
|
|
* 创建用户
|
|
*/
|
|
@Transactional
|
|
public User createUser(UserCreateRequest request) {
|
|
validateUsername(request.getUsername());
|
|
|
|
User user = new User();
|
|
user.setTenantId(request.getTenantId());
|
|
user.setStoreId(request.getStoreId());
|
|
user.setUsername(request.getUsername());
|
|
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
|
user.setName(request.getName());
|
|
user.setPhone(request.getPhone());
|
|
user.setEmail(request.getEmail());
|
|
user.setStatus(1);
|
|
|
|
user = userRepository.save(user);
|
|
|
|
assignRoles(user.getId(), request.getRoleIds());
|
|
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* 验证用户名
|
|
*/
|
|
private void validateUsername(String username) {
|
|
if (userRepository.findByUsername(username).isPresent()) {
|
|
throw new BusinessException("用户名已存在");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 分配角色
|
|
*/
|
|
private void assignRoles(Long userId, List<Long> roleIds) {
|
|
if (roleIds == null || roleIds.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
List<UserRole> userRoles = roleIds.stream()
|
|
.map(roleId -> {
|
|
UserRole userRole = new UserRole();
|
|
userRole.setUserId(userId);
|
|
userRole.setRoleId(roleId);
|
|
return userRole;
|
|
})
|
|
.collect(Collectors.toList());
|
|
|
|
userRoleRepository.saveAll(userRoles);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 三、API设计
|
|
|
|
### 3.1 会员模块API
|
|
|
|
#### 3.1.1 会员注册
|
|
|
|
```
|
|
POST /api/v1/members/register
|
|
|
|
Request:
|
|
{
|
|
"tenantId": 1,
|
|
"storeId": 1,
|
|
"phone": "13800138000",
|
|
"smsCode": "123456",
|
|
"name": "张三",
|
|
"gender": 1,
|
|
"birthday": "1990-01-01",
|
|
"height": 175,
|
|
"weight": 70.5,
|
|
"fitnessGoal": "减脂"
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"code": 200,
|
|
"message": "注册成功",
|
|
"data": {
|
|
"id": 1,
|
|
"memberNo": "M10000000000000001",
|
|
"name": "张三",
|
|
"phoneMask": "138****8000",
|
|
"status": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3.1.2 购买会员卡
|
|
|
|
```
|
|
POST /api/v1/member-cards/purchase
|
|
|
|
Request:
|
|
{
|
|
"memberId": 1,
|
|
"cardType": 1,
|
|
"cardName": "月卡",
|
|
"amount": 299.00,
|
|
"count": 30,
|
|
"validDays": 30
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"code": 200,
|
|
"message": "购买成功",
|
|
"data": {
|
|
"id": 1,
|
|
"cardNo": "C10000000000000001",
|
|
"cardName": "月卡",
|
|
"balance": 299.00,
|
|
"balanceCount": 30,
|
|
"validFrom": "2026-03-04",
|
|
"validTo": "2026-04-03",
|
|
"status": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.2 预约模块API
|
|
|
|
#### 3.2.1 预约团课
|
|
|
|
```
|
|
POST /api/v1/bookings/course
|
|
|
|
Request:
|
|
{
|
|
"memberId": 1,
|
|
"slotId": 1
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"code": 200,
|
|
"message": "预约成功",
|
|
"data": {
|
|
"id": 1,
|
|
"bookingNo": "B10000000000000001",
|
|
"courseName": "瑜伽",
|
|
"slotDate": "2026-03-05",
|
|
"startTime": "10:00:00",
|
|
"endTime": "11:00:00",
|
|
"status": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.3 签到模块API
|
|
|
|
#### 3.3.1 扫码签到
|
|
|
|
```
|
|
POST /api/v1/checkins/qrcode
|
|
|
|
Request:
|
|
{
|
|
"qrCode": "M10000000000000001"
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"code": 200,
|
|
"message": "签到成功",
|
|
"data": {
|
|
"id": 1,
|
|
"memberName": "张三",
|
|
"checkInAt": "2026-03-04T10:00:00",
|
|
"status": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.4 数据统计模块API
|
|
|
|
#### 3.4.1 获取数据统计
|
|
|
|
```
|
|
GET /api/v1/statistics/overview?tenantId=1&storeId=1&startDate=2026-03-01&endDate=2026-03-31
|
|
|
|
Response:
|
|
{
|
|
"code": 200,
|
|
"message": "查询成功",
|
|
"data": {
|
|
"memberStatistics": {
|
|
"totalMembers": 100,
|
|
"activeMembers": 80,
|
|
"newMembers": 20
|
|
},
|
|
"bookingStatistics": {
|
|
"totalBookings": 500,
|
|
"cancelledBookings": 50,
|
|
"successBookings": 450
|
|
},
|
|
"checkInStatistics": {
|
|
"totalCheckIns": 800
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 四、缓存策略
|
|
|
|
### 4.1 缓存设计
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ 缓存策略 │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 本地缓存 (Caffeine) │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 会员信息缓存 (TTL: 30分钟) │ │
|
|
│ │ • 会员卡缓存 (TTL: 30分钟) │ │
|
|
│ │ • 课程信息缓存 (TTL: 1小时) │ │
|
|
│ │ • 课程时段缓存 (TTL: 30分钟) │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 分布式缓存 (Redis) │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 验证码缓存 (TTL: 5分钟) │ │
|
|
│ │ • 令牌缓存 (TTL: 24小时) │ │
|
|
│ │ • 限流计数器 (TTL: 1分钟) │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 4.2 缓存实现
|
|
|
|
**会员缓存服务**
|
|
|
|
```java
|
|
@Service
|
|
public class MemberCacheService {
|
|
|
|
@Autowired
|
|
private CacheManager cacheManager;
|
|
|
|
private static final String MEMBER_CACHE = "member";
|
|
private static final String MEMBER_CARD_CACHE = "memberCard";
|
|
|
|
/**
|
|
* 获取会员缓存
|
|
*/
|
|
public Optional<Member> getMember(Long memberId) {
|
|
Cache cache = cacheManager.getCache(MEMBER_CACHE);
|
|
if (cache != null) {
|
|
return Optional.ofNullable(cache.get(memberId, Member.class));
|
|
}
|
|
return Optional.empty();
|
|
}
|
|
|
|
/**
|
|
* 设置会员缓存
|
|
*/
|
|
public void setMember(Member member) {
|
|
Cache cache = cacheManager.getCache(MEMBER_CACHE);
|
|
if (cache != null) {
|
|
cache.put(member.getId(), member);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除会员缓存
|
|
*/
|
|
public void evictMember(Long memberId) {
|
|
Cache cache = cacheManager.getCache(MEMBER_CACHE);
|
|
if (cache != null) {
|
|
cache.evict(memberId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取会员卡缓存
|
|
*/
|
|
public Optional<MemberCard> getMemberCard(Long cardId) {
|
|
Cache cache = cacheManager.getCache(MEMBER_CARD_CACHE);
|
|
if (cache != null) {
|
|
return Optional.ofNullable(cache.get(cardId, MemberCard.class));
|
|
}
|
|
return Optional.empty();
|
|
}
|
|
|
|
/**
|
|
* 设置会员卡缓存
|
|
*/
|
|
public void setMemberCard(MemberCard card) {
|
|
Cache cache = cacheManager.getCache(MEMBER_CARD_CACHE);
|
|
if (cache != null) {
|
|
cache.put(card.getId(), card);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除会员卡缓存
|
|
*/
|
|
public void evictMemberCard(Long cardId) {
|
|
Cache cache = cacheManager.getCache(MEMBER_CARD_CACHE);
|
|
if (cache != null) {
|
|
cache.evict(cardId);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 五、异常处理
|
|
|
|
### 5.1 异常分类
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ 异常分类 │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 业务异常 (BusinessException) │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 会员不存在 • 会员卡无效 • 预约失败 • 签到失败 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 参数异常 (ValidationException) │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 参数为空 • 参数格式错误 • 参数超出范围 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 权限异常 (PermissionException) │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 无权限 • 权限不足 • 令牌过期 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 系统异常 (SystemException) │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 数据库异常 • 网络异常 • 服务异常 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 5.2 异常处理实现
|
|
|
|
**全局异常处理器**
|
|
|
|
```java
|
|
@RestControllerAdvice
|
|
public class GlobalExceptionHandler {
|
|
|
|
/**
|
|
* 业务异常
|
|
*/
|
|
@ExceptionHandler(BusinessException.class)
|
|
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
|
|
ErrorResponse response = new ErrorResponse();
|
|
response.setCode(e.getCode());
|
|
response.setMessage(e.getMessage());
|
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
|
}
|
|
|
|
/**
|
|
* 参数异常
|
|
*/
|
|
@ExceptionHandler(ValidationException.class)
|
|
public ResponseEntity<ErrorResponse> handleValidationException(ValidationException e) {
|
|
ErrorResponse response = new ErrorResponse();
|
|
response.setCode(400);
|
|
response.setMessage(e.getMessage());
|
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
|
}
|
|
|
|
/**
|
|
* 权限异常
|
|
*/
|
|
@ExceptionHandler(PermissionException.class)
|
|
public ResponseEntity<ErrorResponse> handlePermissionException(PermissionException e) {
|
|
ErrorResponse response = new ErrorResponse();
|
|
response.setCode(403);
|
|
response.setMessage(e.getMessage());
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response);
|
|
}
|
|
|
|
/**
|
|
* 系统异常
|
|
*/
|
|
@ExceptionHandler(SystemException.class)
|
|
public ResponseEntity<ErrorResponse> handleSystemException(SystemException e) {
|
|
ErrorResponse response = new ErrorResponse();
|
|
response.setCode(500);
|
|
response.setMessage("系统异常");
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 六、测试用例
|
|
|
|
### 6.1 会员模块测试用例
|
|
|
|
#### 6.1.1 会员注册测试
|
|
|
|
| 测试用例 | 输入 | 预期输出 |
|
|
|---------|------|---------|
|
|
| 正常注册 | 手机号、验证码、姓名 | 注册成功 |
|
|
| 手机号已存在 | 已存在的手机号 | 提示手机号已存在 |
|
|
| 验证码错误 | 错误的验证码 | 提示验证码错误 |
|
|
| 验证码过期 | 过期的验证码 | 提示验证码过期 |
|
|
|
|
#### 6.1.2 购买会员卡测试
|
|
|
|
| 测试用例 | 输入 | 预期输出 |
|
|
|---------|------|---------|
|
|
| 正常购买 | 会员ID、卡类型、金额 | 购买成功 |
|
|
| 会员不存在 | 不存在的会员ID | 提示会员不存在 |
|
|
| 支付失败 | 支付失败 | 提示支付失败 |
|
|
|
|
### 6.2 预约模块测试用例
|
|
|
|
#### 6.2.1 预约团课测试
|
|
|
|
| 测试用例 | 输入 | 预期输出 |
|
|
|---------|------|---------|
|
|
| 正常预约 | 会员ID、课程时段ID | 预约成功 |
|
|
| 预约时间过短 | 课程开始前30分钟内 | 提示预约时间过短 |
|
|
| 课程已满 | 已满的课程时段 | 提示课程已满 |
|
|
| 权益不足 | 权益不足的会员 | 提示权益不足 |
|
|
|
|
### 6.3 签到模块测试用例
|
|
|
|
#### 6.3.1 扫码签到测试
|
|
|
|
| 测试用例 | 输入 | 预期输出 |
|
|
|---------|------|---------|
|
|
| 正常签到 | 有效的二维码 | 签到成功 |
|
|
| 会员不存在 | 无效的二维码 | 提示会员不存在 |
|
|
| 会员卡无效 | 会员卡无效 | 提示会员卡无效 |
|
|
|
|
---
|
|
|
|
## 七、部署与运维
|
|
|
|
### 7.1 部署架构
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ 部署架构 │
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 负载均衡 (Nginx) │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 应用服务器 (Kubernetes) │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • Pod 1 • Pod 2 • Pod 3 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 数据库 (PostgreSQL) │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 主库 • 从库 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ 缓存 (Redis) │ │
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
│ │ • 主节点 • 从节点 │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 7.2 监控指标
|
|
|
|
| 指标类型 | 指标名称 | 阈值 |
|
|
|---------|---------|------|
|
|
| 系统指标 | CPU使用率 | ≤ 80% |
|
|
| 系统指标 | 内存使用率 | ≤ 80% |
|
|
| 系统指标 | 磁盘使用率 | ≤ 80% |
|
|
| 应用指标 | API响应时间 | ≤ 500ms |
|
|
| 应用指标 | 错误率 | ≤ 1% |
|
|
| 应用指标 | 并发数 | ≤ 100 |
|
|
|
|
---
|
|
|
|
## 八、附录
|
|
|
|
### 8.1 术语定义
|
|
|
|
| 术语 | 定义 |
|
|
|------|------|
|
|
| 会员 | 在健身房注册的用户 |
|
|
| 会员卡 | 会员购买的权益卡,包括时长卡、次卡、储值卡 |
|
|
| 权益 | 会员卡包含的时长、次数、储值、等级等权益 |
|
|
| 团课 | 集体课程,由教练带领多个会员一起上课 |
|
|
| 预约 | 会员预约团课 |
|
|
| 签到 | 会员到店记录 |
|
|
|
|
### 8.2 参考文档
|
|
|
|
- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
|
|
- 《健身房管理系统基础版业务概要设计文档》 GYM-HLD-BASIC-001
|
|
- 《健身房管理系统详细设计文档》 GYM-LLD-000
|
|
- Spring Boot 3 官方文档
|
|
- R2DBC 规范文档
|
|
- PostgreSQL 官方文档
|