# 健身房管理系统基础版详细设计文档(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 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 roleIds) { if (roleIds == null || roleIds.isEmpty()) { return; } List 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 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 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 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 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 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 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 官方文档