Преглед на файлове

feat:更新readme文档和其他的开发文档

yangyi преди 4 месеца
родител
ревизия
8393711068
променени са 5 файла, в които са добавени 2348 реда и са изтрити 0 реда
  1. 42 0
      AGENTS.md
  2. 226 0
      README.md
  3. 1767 0
      detailDesign.md
  4. 172 0
      nextWork.md
  5. 141 0
      work.md

+ 42 - 0
AGENTS.md

@@ -0,0 +1,42 @@
+# AGENTS.md — Pet Service Management System (server)
+
+## Stack
+
+- **Spring Boot 3.5.15** + **MyBatis 3.0.5** + **MySQL**
+- Java 17, Maven (wrapper at `./mvnw`)
+
+## Quick commands
+
+```bash
+./mvnw spring-boot:run          # start dev server (port 8080, context-path /api)
+./mvnw clean package            # build
+./mvnw test                     # run tests
+```
+
+## Structure
+
+- Single Maven module; all source under `src/main/java/space/anyi/petServiceManagementSystem/server/`
+- Config at `src/main/resources/application.yaml`
+- No tests yet (`src/test/` exists but is empty)
+- Frontend lives in sibling `../ui/` directory (Vite + React + TypeScript)
+
+## API conventions
+
+- All endpoints prefixed with `/api` (set via `server.servlet.context-path` in yaml)
+- Default port: `8080`
+
+## Coding conventions
+
+- **OpenAPI annotations on every controller API** — use `@Operation`, `@Parameter`, `@Schema` from `io.swagger.v3.oas.annotations`
+- **DTO / VO for API params** — request params use DTO, response data uses VO. Do not expose entities directly in controller
+- **Business logic in Service layer** — Controller only delegates, Service contains `@Transactional` logic
+- **Entity property comments** — every field in entity classes must have an inline comment explaining its meaning
+- **Beginner-friendly style** — simple code, high readability over cleverness
+- **Business logic comments** — add inline comments explaining the implementation approach and reasoning at key steps
+
+## Notable
+
+- `build/` and `out/` are gitignored (build artifacts)
+- `.mvn/wrapper/maven-wrapper.properties` — Maven wrapper pinned version
+- No CI, no lint config, no formatter config, no codegen
+- No tests exist yet — add them under `src/test/java/space/anyi/petServiceManagementSystem/server/`

+ 226 - 0
README.md

@@ -0,0 +1,226 @@
+# Pet Service Management System
+
+宠物服务管理系统 - 一个完整的宠物服务预约管理平台,支持用户管理、宠物档案、服务预约、房态管理等功能。
+
+## 项目简介
+
+本项目是一个基于 Spring Boot 开发的宠物服务管理后端系统,为宠物主人提供便捷的在线预约服务,同时为管理员提供完善的后台管理功能。
+
+## 技术栈
+
+| 技术 | 版本 | 说明 |
+|-----|------|------|
+| Spring Boot | 3.5.15 | 核心框架 |
+| MyBatis | 3.0.5 | ORM 框架 |
+| MySQL | 8.x | 关系型数据库 |
+| Java | 17 | 开发语言 |
+| Maven | - | 构建工具 |
+| OpenAPI/Swagger | 2.8.16 | API 文档 |
+
+## 功能特性
+
+### 用户端功能
+
+- **用户认证**:注册、登录、登出
+- **个人中心**:信息管理、密码修改、头像上传
+- **宠物档案**:添加宠物、疫苗记录、驱虫记录
+- **服务预约**:浏览套餐、选择房型、提交预约
+- **预约管理**:查看预约状态、申请取消、支付定金
+
+### 管理端功能
+
+- **用户管理**:查看注册用户、宠物档案详情
+- **预约管理**:审核预约、处理取消申请
+- **套餐管理**:新增/修改/删除服务套餐
+- **房型管理**:房态查看、手动调整房态
+- **排班管理**:员工排班管理
+
+## 快速开始
+
+### 环境要求
+
+- JDK 17+
+- MySQL 8.x
+- Maven 3.6+
+
+### 安装步骤
+
+1. **克隆项目**
+   ```bash
+   git clone <repository-url>
+   cd server
+   ```
+
+2. **配置数据库**
+   
+   修改 `src/main/resources/application.yaml` 中的数据库连接信息:
+   ```yaml
+   spring:
+     datasource:
+       url: jdbc:mysql://localhost:3306/pet_service_management_system
+       username: your_username
+       password: your_password
+   ```
+
+3. **创建数据库**
+   ```sql
+   CREATE DATABASE pet_service_management_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+   ```
+
+4. **运行项目**
+   ```bash
+   # 使用 Maven Wrapper
+   ./mvnw spring-boot:run
+   
+   # 或打包后运行
+   ./mvnw clean package
+   java -jar target/server-0.0.1-SNAPSHOT.jar
+   ```
+
+5. **访问 API 文档**
+   
+   启动后访问:`http://localhost:8080/swagger-ui.html`
+
+## 项目结构
+
+```
+server/
+├── src/main/java/space/anyi/petServiceManagementSystem/server/
+│   ├── ServerApplication.java          # 主启动类
+│   ├── common/                         # 公共类(响应封装、异常)
+│   ├── config/                         # 配置类
+│   ├── controller/                     # 控制器层
+│   │   ├── AuthController.java
+│   │   ├── UserController.java
+│   │   ├── PetController.java
+│   │   ├── AppointmentController.java
+│   │   └── admin/                      # 管理员接口
+│   ├── service/                        # 服务层
+│   ├── mapper/                         # MyBatis Mapper
+│   ├── entity/                         # 数据库实体
+│   ├── dto/                            # 数据传输对象
+│   ├── vo/                             # 视图对象
+│   ├── interceptor/                    # 拦截器
+│   └── util/                           # 工具类
+├── src/main/resources/
+│   ├── application.yaml                # 应用配置
+│   └── mapper/                         # MyBatis XML 映射文件
+└── pom.xml                             # Maven 配置
+```
+
+## API 概览
+
+### 认证接口
+
+| 方法 | 端点 | 说明 |
+|------|------|------|
+| POST | `/auth/register` | 用户注册 |
+| POST | `/auth/login` | 用户登录 |
+| POST | `/auth/logout` | 退出登录 |
+
+### 用户接口
+
+| 方法 | 端点 | 说明 |
+|------|------|------|
+| GET | `/user/{id}` | 获取用户信息 |
+| PUT | `/user` | 修改用户信息 |
+| PUT | `/user/updatePassword` | 修改密码 |
+
+### 宠物接口
+
+| 方法 | 端点 | 说明 |
+|------|------|------|
+| GET | `/pets` | 获取宠物列表 |
+| POST | `/pets` | 新增宠物 |
+| GET | `/pets/{id}` | 获取宠物详情 |
+| GET | `/pets/{id}/vaccinations` | 疫苗记录 |
+| GET | `/pets/{id}/deworming` | 驱虫记录 |
+
+### 预约接口
+
+| 方法 | 端点 | 说明 |
+|------|------|------|
+| POST | `/appointments` | 提交预约 |
+| GET | `/appointments` | 我的预约列表 |
+| POST | `/appointments/{id}/cancel` | 申请取消 |
+| POST | `/appointments/{id}/pay-deposit` | 支付定金 |
+
+### 管理员接口
+
+| 方法 | 端点 | 说明 |
+|------|------|------|
+| GET | `/admin/users` | 用户列表 |
+| GET | `/admin/appointments` | 全部预约 |
+| PUT | `/admin/appointments/{id}/review` | 审核预约 |
+| POST | `/admin/packages` | 新增套餐 |
+| POST | `/admin/boarding-rooms` | 新增房型 |
+| POST | `/admin/schedules` | 添加排班 |
+
+## 数据库设计
+
+### 核心表
+
+- `user` - 用户表
+- `pet` - 宠物表
+- `appointment` - 预约表
+- `package` - 服务套餐表
+- `boarding_room` - 寄养房型表
+- `boarding_room_status` - 房型状态表
+- `schedule` - 排班表
+- `vaccination_record` - 疫苗记录表
+- `deworming_record` - 驱虫记录表
+
+## 开发规范
+
+### 分层架构
+
+- **Controller**:处理 HTTP 请求,参数校验
+- **Service**:业务逻辑,事务管理
+- **Mapper**:数据访问层
+- **Entity**:数据库实体
+- **DTO**:请求参数封装
+- **VO**:响应数据封装
+
+### 代码规范
+
+- 所有 Controller API 必须添加 OpenAPI 注解
+- 请求参数使用 DTO,响应数据使用 VO
+- 业务逻辑写在 Service 层,Controller 只负责委托
+- Entity 字段必须添加中文注释
+- 关键业务步骤添加注释说明
+
+## 安全配置
+
+- **认证方式**:Session 认证
+- **密码加密**:MD5 + SALT
+- **Session 超时**:30 分钟
+- **跨域配置**:已配置 CORS
+
+## 相关项目
+
+- **前端项目**:位于 `../ui/` 目录,使用 Vite + React + TypeScript 开发
+
+## 开发命令
+
+```bash
+# 启动开发服务器
+./mvnw spring-boot:run
+
+# 运行测试
+./mvnw test
+
+# 打包
+./mvnw clean package
+
+# 跳过测试打包
+./mvnw clean package -DskipTests
+```
+
+## 许可证
+
+[MIT License](LICENSE)
+
+---
+
+**作者**:anyi  
+**版本**:1.0.0

+ 1767 - 0
detailDesign.md

@@ -0,0 +1,1767 @@
+# 宠物服务管理系统 — 服务端详细设计文档
+
+## 1. 技术栈
+
+| 技术 | 版本 | 用途 |
+|------|------|------|
+| Java | 17 | 开发语言 |
+| Spring Boot | 3.5.15 | 应用框架 |
+| Spring Boot Starter Web | 3.5.15 | Web MVC + 嵌入式 Tomcat |
+| MyBatis Spring Boot Starter | 3.0.5 | ORM 框架 |
+| MySQL Connector/J | (spring-boot-starter-parent 管理) | MySQL JDBC 驱动 |
+| SpringDoc OpenAPI | 2.8.16 | API 文档 (Swagger UI) |
+| Maven | (wrapper 3.9+) | 构建工具 |
+| MySQL | 8.0 | 数据库 |
+
+构建命令:`./mvnw spring-boot:run` — 启动开发服务器(端口 8080, context-path /)
+
+## 2. 项目结构
+
+```
+server/
+├── pom.xml                                    # Maven 项目描述文件
+├── sql/
+│   └── init.sql                               # 数据库初始化脚本(建库建表+种子数据)
+├── src/main/java/space/anyi/petServiceManagementSystem/server/
+│   ├── ServerApplication.java                 # Spring Boot 启动入口
+│   │
+│   ├── config/                                # 配置类
+│   │   ├── CorsConfig.java                    # 跨域 CORS 配置,允许 credentials + 任意 origin pattern
+│   │   ├── WebMvcConfig.java                  # 注册 AuthInterceptor 和 AdminInterceptor 拦截器
+│   │   ├── PasswordConfig.java                # MD5 + SALT 密码加密组件(@Component)
+│   │   └── GlobalExceptionHandler.java        # @RestControllerAdvice 全局异常处理
+│   │
+│   ├── interceptor/                           # 拦截器
+│   │   ├── AuthInterceptor.java               # HttpSession 登录认证(排除 /auth/**, /swagger-ui/**)
+│   │   └── AdminInterceptor.java              # admin 角色校验(匹配 /admin/**)
+│   │
+│   ├── common/                                # 通用基础设施
+│   │   ├── R.java                             # 统一响应 R<T> {code, message, data}
+│   │   ├── ResultCode.java                    # 状态码常量 (200/400/401/403/404/409/500)
+│   │   ├── BusinessException.java             # 业务异常 (code=400)
+│   │   ├── UnauthorizedException.java         # 未授权异常 (code=401)
+│   │   ├── ForbiddenException.java            # 无权限异常 (code=403)
+│   │   ├── ResourceNotFoundException.java     # 资源不存在异常 (code=404)
+│   │   └── PageVo.java                        # 分页结果封装 {data, total, pageNum, pageSize}
+│   │
+│   ├── entity/                                # 数据实体(对应数据库表)
+│   │   ├── User.java                          # 用户 user 表
+│   │   ├── Pet.java                           # 宠物 pet 表
+│   │   ├── VaccinationRecord.java             # 疫苗记录 vaccination_record 表
+│   │   ├── DewormingRecord.java               # 驱虫记录 deworming_record 表
+│   │   ├── ServicePackage.java                # 套餐 package 表(避 java.lang.Package 冲突)
+│   │   ├── BoardingRoom.java                  # 寄养房型 boarding_room 表
+│   │   ├── BoardingRoomStatus.java            # 房态 boarding_room_status 表
+│   │   ├── Appointment.java                   # 预约订单 appointment 表
+│   │   └── Schedule.java                      # 排班 schedule 表
+│   │
+│   ├── dto/                                   # 请求参数 DTO
+│   │   ├── auth/
+│   │   │   ├── RegisterDto.java               # 注册 (account, password, username) + @Valid 校验
+│   │   │   └── LoginDto.java                  # 登录 (account, password)
+│   │   ├── user/
+│   │   │   ├── UserDto.java                   # 新增/编辑用户
+│   │   │   ├── UpdateUserPasswordDto.java     # 修改密码 (id, oldPassword, newPassword)
+│   │   │   ├── UpdateUserAvatarDto.java       # 修改头像 (id, avatar)
+│   │   │   └── UpdateUserStatusDto.java       # 启用/禁用 (id, enable)
+│   │   ├── pet/
+│   │   │   ├── PetDto.java                    # 新增/编辑宠物
+│   │   │   ├── VaccinationDto.java            # 新增疫苗记录
+│   │   │   └── DewormingDto.java              # 新增驱虫记录
+│   │   ├── appointment/
+│   │   │   ├── AppointmentDto.java            # 提交预约
+│   │   │   ├── ReviewDto.java                 # 审核 (action + rejectReason)
+│   │   │   └── HandleCancelDto.java           # 处理取消 (action)
+│   │   ├── room/
+│   │   │   └── RoomStatusDto.java             # 修改房态 (date, status)
+│   │   ├── servicePackage/
+│   │   │   └── PackageDto.java                # 新增/编辑套餐
+│   │   └── schedule/
+│   │       └── ScheduleDto.java               # 新增排班
+│   │
+│   ├── vo/                                    # 返回结果 VO
+│   │   ├── user/
+│   │   │   ├── UserVo.java                    # 用户基本信息
+│   │   │   └── AdminUserDetailVo.java         # 管理员查看用户详情(含宠物列表)
+│   │   ├── pet/
+│   │   │   ├── PetVo.java                     # 宠物信息(含疫苗/驱虫记录列表)
+│   │   │   ├── VaccinationVo.java             # 疫苗记录
+│   │   │   └── DewormingVo.java               # 驱虫记录
+│   │   ├── appointment/
+│   │   │   └── AppointmentVo.java             # 预约订单详情
+│   │   ├── room/
+│   │   │   ├── BoardingRoomVo.java            # 寄养房型信息
+│   │   │   └── RoomStatusVo.java              # 房态(含 appointmentId)
+│   │   ├── servicePackage/
+│   │   │   └── PackageVo.java                 # 套餐信息
+│   │   └── schedule/
+│   │       └── ScheduleVo.java                # 排班信息
+│   │
+│   ├── mapper/                                # MyBatis Mapper 接口
+│   │   ├── UserMapper.java
+│   │   ├── PetMapper.java
+│   │   ├── VaccinationRecordMapper.java
+│   │   ├── DewormingRecordMapper.java
+│   │   ├── PackageMapper.java
+│   │   ├── BoardingRoomMapper.java
+│   │   ├── BoardingRoomStatusMapper.java
+│   │   ├── AppointmentMapper.java
+│   │   └── ScheduleMapper.java
+│   │
+│   ├── service/                               # 服务接口
+│   │   ├── AuthService.java
+│   │   ├── UserService.java
+│   │   ├── PetService.java
+│   │   ├── AppointmentService.java
+│   │   ├── BoardingRoomService.java
+│   │   ├── PackageService.java
+│   │   ├── ScheduleService.java
+│   │   └── impl/                              # 服务实现
+│   │       ├── AuthServiceImpl.java
+│   │       ├── UserServiceImpl.java
+│   │       ├── PetServiceImpl.java
+│   │       ├── AppointmentServiceImpl.java
+│   │       ├── BoardingRoomServiceImpl.java
+│   │       ├── PackageServiceImpl.java
+│   │       └── ScheduleServiceImpl.java
+│   │
+│   └── util/
+│       └── PageUtil.java                      # 分页工具类(利用 Supplier 封装列表+总数查询)
+│
+└── src/main/resources/
+    ├── application.yaml                       # Spring Boot 配置文件
+    └── mapper/                                # MyBatis XML 映射文件
+        ├── UserMapper.xml
+        ├── PetMapper.xml
+        ├── VaccinationRecordMapper.xml
+        ├── DewormingRecordMapper.xml
+        ├── PackageMapper.xml
+        ├── BoardingRoomMapper.xml
+        ├── BoardingRoomStatusMapper.xml
+        ├── AppointmentMapper.xml
+        └── ScheduleMapper.xml
+```
+
+## 3. 数据模型
+
+### 3.1 实体关系图 (E-R)
+
+```mermaid
+erDiagram
+    user ||--o{ pet : "拥有"
+    user ||--o{ appointment : "发起"
+    pet ||--o{ vaccination_record : "疫苗记录"
+    pet ||--o{ deworming_record : "驱虫记录"
+    pet ||--o{ appointment : "被预约"
+    appointment ||--|| package : "选择"
+    appointment ||--o| boarding_room : "使用房型(寄养)"
+    appointment ||--o{ boarding_room_status : "生成房态(审核通过后)"
+    boarding_room ||--o{ boarding_room_status : "房态记录"
+
+    user {
+        BIGINT id PK
+        VARCHAR account UK "登录账号"
+        VARCHAR username
+        VARCHAR password "MD5+SALT"
+        VARCHAR email
+        VARCHAR phone
+        VARCHAR avatar
+        VARCHAR role "user|admin"
+        TINYINT enable "0禁用 1启用"
+        DATETIME created_at
+        DATETIME updated_at
+    }
+
+    pet {
+        BIGINT id PK
+        BIGINT user_id FK
+        VARCHAR name
+        VARCHAR species "DOG|CAT|OTHER"
+        VARCHAR breed
+        VARCHAR gender "MALE|FEMALE"
+        DATE birth_date
+        DECIMAL weight
+        VARCHAR avatar
+        TEXT remark
+        DATETIME created_at
+        DATETIME updated_at
+    }
+
+    vaccination_record {
+        BIGINT id PK
+        BIGINT pet_id FK
+        VARCHAR vaccine_name
+        DATE vaccine_date
+        VARCHAR hospital
+        VARCHAR doctor
+        TEXT remark
+        DATETIME created_at
+    }
+
+    deworming_record {
+        BIGINT id PK
+        BIGINT pet_id FK
+        VARCHAR deworming_name
+        DATE deworming_date
+        VARCHAR hospital
+        VARCHAR doctor
+        TEXT remark
+        DATETIME created_at
+    }
+
+    package {
+        BIGINT id PK
+        VARCHAR name
+        VARCHAR type "BOARDING|GROOMING"
+        DECIMAL price
+        INT duration "寄养天数(仅寄养)"
+        TEXT description
+        TINYINT is_active "0禁用 1启用"
+        DATETIME created_at
+        DATETIME updated_at
+    }
+
+    boarding_room {
+        BIGINT id PK
+        VARCHAR room_name
+        VARCHAR room_type "STANDARD|VIP|DELUXE"
+        INT capacity "可容纳宠物数"
+        DECIMAL price_per_day "每晚价格"
+        TINYINT is_active "0禁用 1启用"
+        TEXT description
+        DATETIME created_at
+        DATETIME updated_at
+    }
+
+    boarding_room_status {
+        BIGINT id PK
+        BIGINT room_id FK "关联房型"
+        DATE date "日期"
+        VARCHAR status "AVAILABLE|OCCUPIED|MAINTENANCE"
+        BIGINT appointment_id "关联预约(系统写入)"
+        DATETIME created_at
+    }
+
+    appointment {
+        BIGINT id PK
+        BIGINT user_id FK
+        BIGINT pet_id FK
+        BIGINT package_id FK
+        BIGINT room_id FK "寄养房型(寄养必填)"
+        VARCHAR type "BOARDING|GROOMING"
+        VARCHAR status "PENDING|CONFIRMED|CANCELLED|CANCEL_REQUESTED"
+        DATE start_date "寄养开始/洗护预约日"
+        DATE end_date "寄养结束"
+        DECIMAL total_price
+        DECIMAL deposit "定金=总价*30%"
+        TINYINT deposit_paid "0未支付 1已支付"
+        TEXT cancel_reason
+        VARCHAR reject_reason
+        TEXT remark
+        DATETIME created_at
+        DATETIME updated_at
+    }
+
+    schedule {
+        BIGINT id PK
+        VARCHAR employee_name
+        DATE work_date
+        VARCHAR time_slot "MORNING|AFTERNOON|FULL_DAY"
+        TEXT remark
+        DATETIME created_at
+    }
+```
+
+### 3.2 核心 SQL 脚本设计要点
+
+**UNIQUE 索引:** `boarding_room_status` 表使用 `UNIQUE KEY uk_room_date (room_id, date)` 确保每个房型每天只有一条记录。通过 `INSERT ... ON DUPLICATE KEY UPDATE` 实现插入或更新(upsert):
+
+```sql
+INSERT INTO boarding_room_status (room_id, date, status, appointment_id, created_at)
+VALUES (#{roomId}, #{date}, #{status}, #{appointmentId}, NOW())
+ON DUPLICATE KEY UPDATE status = #{status}, appointment_id = #{appointmentId}
+```
+
+**索引策略:**
+
+| 表 | 索引 | 用途 |
+|---|------|------|
+| user | uk_account (account) | 登录/注册唯一性校验 |
+| pet | idx_user_id (user_id) | 快速查询某用户的所有宠物 |
+| vaccination_record | idx_pet_id (pet_id) | 查询某宠物疫苗记录 |
+| deworming_record | idx_pet_id (pet_id) | 查询某宠物驱虫记录 |
+| appointment | idx_user_id (user_id) | 查询某用户预约列表 |
+| appointment | idx_status (status) | 管理员按状态筛选订单 |
+| appointment | idx_package_id (package_id) | 统计套餐预约量 |
+| boarding_room_status | uk_room_date (room_id, date) | 唯一约束+日期范围查询 |
+| boarding_room_status | idx_appointment_id (appointment_id) | 取消订单时释放房态 |
+| schedule | idx_work_date (work_date) | 按月查询排班 |
+| package | idx_type (type) | 按类型筛选套餐 |
+
+**bagin 数据类型:** `price_per_day`, `total_price`, `deposit` 等金额字段使用 `DECIMAL(10,2)` 避免浮点精度丢失。
+
+## 4. 通用基础设施
+
+### 4.1 统一响应 R\<T\>
+
+```java
+public class R<T> {
+    private int code;       // 状态码
+    private String message; // 提示信息
+    private T data;         // 响应数据
+
+    public static <T> R<T> ok(T data)     // 成功返回 data (code=200, message="success")
+    public static <T> R<T> ok()           // 成功无数据 (code=200)
+    public static <T> R<T> error(int code, String message)  // 指定错误码
+    public static <T> R<T> error(String message)            // 默认 400 错误
+}
+```
+
+**响应示例:**
+
+```json
+// 成功
+{ "code": 200, "message": "success", "data": { "id": 1, "username": "张三" } }
+
+// 分页
+{ "code": 200, "message": "success", "data": { "data": [...], "total": 50, "pageNum": 1, "pageSize": 10 } }
+
+// 业务错误
+{ "code": 400, "message": "账号已存在", "data": null }
+
+// 未登录
+{ "code": 401, "message": "未登录或Session已过期", "data": null }
+
+// 无权限
+{ "code": 403, "message": "无权访问,需要管理员权限", "data": null }
+```
+
+### 4.2 业务异常 BusinessException
+
+```java
+public class BusinessException extends RuntimeException {
+    private int code;  // 默认 400
+
+    public BusinessException(String message)           // code=400
+    public BusinessException(int code, String message) // 自定义 code
+}
+```
+
+继承体系:
+
+```
+RuntimeException
+├── BusinessException          (400) — 业务校验失败
+├── UnauthorizedException      (401) — 未登录/Session 过期
+├── ForbiddenException         (403) — 无管理员权限
+└── ResourceNotFoundException  (404) — 资源不存在
+```
+
+### 4.3 全局异常处理 GlobalExceptionHandler
+
+拦截所有 Controller 抛出的异常,统一转换为 R 响应格式:
+
+| 异常类型 | HTTP Status | code | 处理逻辑 |
+|---------|-------------|------|---------|
+| BusinessException | 400 | 400 | 提取 message 返回业务提示 |
+| UnauthorizedException | 401 | 401 | 返回 "未登录或Session已过期" |
+| ForbiddenException | 403 | 403 | 返回 "无权访问" |
+| ResourceNotFoundException | 404 | 404 | 返回 "资源不存在" |
+| MethodArgumentNotValidException | 400 | 400 | 提取 @Valid 校验失败信息 |
+| BindException | 400 | 400 | 提取参数绑定失败信息 |
+| MissingServletRequestParameterException | 400 | 400 | 返回 "缺少必填参数" |
+| DuplicateKeyException | 409 | 409 | 返回 "数据重复" |
+| Exception (兜底) | 500 | 500 | 打印堆栈后返回 "服务器内部错误" |
+
+### 4.4 分页实现 PageVo + PageUtil
+
+```java
+public class PageVo<T> {
+    private List<T> data;     // 当前页数据
+    private long total;       // 总记录数
+    private int pageNum;      // 当前页码(从1开始)
+    private int pageSize;     // 每页大小
+}
+```
+
+`PageUtil` 工具类封装分页查询范式,通过 `Supplier` 解耦列表查询和总数查询:
+
+```java
+public static <T> PageVo<T> page(int pageNum, int pageSize,
+        Supplier<List<T>> queryList, Supplier<Long> queryCount) {
+    // 1. 执行列表查询: queryList.get()
+    // 2. 执行总数查询: queryCount.get()
+    // 3. 封装 PageVo 返回
+}
+```
+
+**算法步骤:**
+1. Controller 接收 `pageNum`, `pageSize` 及筛选参数
+2. Service 计算 `offset = (pageNum - 1) * pageSize`
+3. MyBatis XML 中使用 `LIMIT #{offset}, #{pageSize}` 分页查询
+4. 同时执行 `SELECT COUNT(*)` 获取总记录数
+5. PageUtil 将两部分结果封装为 `PageVo` 返回
+
+## 5. 认证与安全
+
+### 5.1 HttpSession 认证机制
+
+| 项目 | 说明 |
+|------|------|
+| 会话存储 | 服务端内存 Session,由 `JSESSIONID` Cookie 关联 |
+| Cookie 属性 | `HttpOnly=true`, `SameSite=Lax` |
+| 登录时机 | 登录/注册成功后调用 `request.getSession().setAttribute("user", userVo)` |
+| Session 内容 | `UserVo` 对象(含 id, role, username 等) |
+| 注销方式 | `request.getSession().invalidate()` |
+| 超时配置 | `server.servlet.session.timeout=1800` (30分钟) |
+
+### 5.2 AuthInterceptor 拦截器
+
+**配置路径:** 拦截所有 `/api/**`,排除 `/auth/**`、`/swagger-ui/**`、`/v3/api-docs/**`
+
+**算法步骤:**
+1. 调用 `request.getSession(false)` 获取 Session(不自动创建)
+2. 若 session == null → 抛出 `UnauthorizedException("未登录")`
+3. 从 session 获取 `UserVo user = (UserVo) session.getAttribute("user")`
+4. 若 user == null → 抛出 `UnauthorizedException("未登录或Session已过期")`
+5. 将 `userId` 和 `role` 设置到 request attribute,供 Controller 通过 `request.getAttribute("userId")` 获取
+
+```mermaid
+sequenceDiagram
+    participant Client as 客户端
+    participant Interceptor as AuthInterceptor
+    participant Session as HttpSession
+    participant Controller as Controller
+
+    Client->>Interceptor: HTTP 请求(携带 JSESSIONID Cookie)
+    Interceptor->>Session: request.getSession(false)
+    alt Session 为空
+        Session-->>Interceptor: null
+        Interceptor-->>Client: 401 Unauthorized
+    else Session 存在
+        Interceptor->>Session: getAttribute("user")
+        alt user 为空
+            Session-->>Interceptor: null
+            Interceptor-->>Client: 401 Unauthorized
+        else user 存在
+            Interceptor->>Interceptor: request.setAttribute("userId", user.id)
+            Interceptor->>Interceptor: request.setAttribute("role", user.role)
+            Interceptor->>Controller: 放行
+            Controller-->>Client: 正常响应
+        end
+    end
+```
+
+### 5.3 AdminInterceptor 拦截器
+
+**配置路径:** 匹配 `/admin/**`(在 AuthInterceptor 之后执行,所以 request attribute 中已有 role)
+
+**算法步骤:**
+1. 从 request attribute 获取 `role`
+2. 若 `role != "admin"` → 抛出 `ForbiddenException("无权访问,需要管理员权限")`
+3. 放行
+
+### 5.4 密码加密方案
+
+使用 **MD5 + 固定盐值** 加密:
+
+```java
+SALT = "PetService2026!@#";
+
+encrypt(password):
+    1. toEncrypt = password + SALT
+    2. MessageDigest.getInstance("MD5") 计算摘要
+    3. 将 16 字节摘要转为 32 位小写十六进制字符串
+    4. 返回密文
+
+matches(rawPassword, encryptedPassword):
+    1. 对 rawPassword 执行 encrypt
+    2. 与 encryptedPassword 比较(equals)
+```
+
+### 5.5 跨域配置
+
+```java
+registry.addMapping("/**")
+    .allowedOriginPatterns("*")     // 允许所有来源(开发阶段)
+    .allowCredentials(true)         // 允许携带 Cookie(必须)
+    .allowedMethods("GET","POST","PUT","DELETE","OPTIONS")
+    .allowedHeaders("*");
+```
+
+> 注意:`allowCredentials(true)` 与 `allowedOrigins("*")` 不能同时使用,使用 `allowedOriginPatterns("*")` 替代。
+
+## 6. 模块详细设计
+
+### 6.1 认证模块 (AuthController)
+
+**路由映射:** `@RequestMapping("/auth")`
+
+#### Controller 定义
+
+| 方法 | 路径 | 请求体 | 返回 | 校验 | 说明 |
+|------|------|--------|------|------|------|
+| POST | /auth/register | RegisterDto | R\<UserVo\> | @Valid | 注册并自动登录 |
+| POST | /auth/login | LoginDto | R\<UserVo\> | @Valid | 登录 |
+| POST | /auth/logout | - | R\<Void\> | - | 登出(销毁 Session) |
+
+#### DTO / VO 定义
+
+**RegisterDto** (src/dto/auth/RegisterDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| account | String | @NotBlank @Size(min=4, max=20) | 登录账号 |
+| password | String | @NotBlank @Size(min=4, max=20) | 明文密码 |
+| username | String | @NotBlank @Size(min=2, max=32) | 显示用户名 |
+
+**LoginDto** (src/dto/auth/LoginDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| account | String | @NotBlank | 登录账号 |
+| password | String | @NotBlank | 明文密码 |
+
+**UserVo** (src/vo/user/UserVo.java)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | Long | 用户 ID |
+| username | String | 用户名 |
+| account | String | 登录账号 |
+| email | String | 邮箱 |
+| phone | String | 联系方式 |
+| avatar | String | 头像 URL |
+| role | String | 角色 (user/admin) |
+| enable | Integer | 状态 (0禁用 1启用) |
+
+#### Service 算法
+
+**register(RegisterDto dto, HttpServletRequest request):**
+1. 调用 `userMapper.selectByAccount(dto.getAccount())` 检查账号是否已存在
+2. 若存在 → 抛出 `BusinessException("账号已存在")`
+3. 创建 User 对象,设置 `password = passwordConfig.encrypt(dto.getPassword())`, `role = "user"`, `enable = 1`
+4. 调用 `userMapper.insert(user)` 插入数据库
+5. 调用 `toUserVo(user)` 转为 UserVo
+6. 调用 `request.getSession().setAttribute("user", vo)` 自动登录
+7. 返回 UserVo
+
+**login(LoginDto dto, HttpServletRequest request):**
+1. 调用 `userMapper.selectByAccount(dto.getAccount())` 查找用户
+2. 若查不到 → 抛出 `BusinessException("账号或密码错误")`
+3. 检查 `user.getEnable() == 0` → 抛出 `BusinessException("账号已被禁用")`
+4. 调用 `passwordConfig.matches(dto.getPassword(), user.getPassword())` 校验密码
+5. 密码不匹配 → 抛出 `BusinessException("账号或密码错误")`
+6. 转为 UserVo,存入 Session
+7. 返回 UserVo
+
+#### 时序图:登录流程
+
+```mermaid
+sequenceDiagram
+    participant F as 前端
+    participant C as AuthController
+    participant S as AuthServiceImpl
+    participant M as UserMapper
+    participant DB as MySQL
+    participant Session as HttpSession
+
+    F->>+C: POST /auth/login { account, password }
+    C->>+S: login(dto, request)
+    S->>+M: selectByAccount(account)
+    M->>+DB: SELECT * FROM user WHERE account=?
+    DB-->>-M: User 实体
+    M-->>-S: User or null
+
+    alt 用户不存在
+        S-->>F: BusinessException("账号或密码错误")
+    else 用户被禁用
+        S-->>F: BusinessException("账号已被禁用")
+    else 密码错误
+        S-->>F: BusinessException("账号或密码错误")
+    else 登录成功
+        S->>Session: setAttribute("user", userVo)
+        Session-->>S: OK
+        S-->>-C: UserVo
+        C-->>-F: R<UserVo> { code:200, data: { id, username, role, ... } }
+    end
+```
+
+### 6.2 用户模块 (UserController)
+
+**路由映射:** `@RequestMapping("/user")`
+
+#### Controller 定义
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| GET | /user | query: pageNum, pageSize, username, account, role, enable | R\<PageVo\<UserVo\>\> | 分页查询用户列表 |
+| GET | /user/{id} | path: id | R\<UserVo\> | 按 ID 查询用户 |
+| POST | /user | UserDto | R\<Boolean\> | 新增用户 |
+| PUT | /user | UserDto | R\<Boolean\> | 修改用户 |
+| DELETE | /user | query: ids (逗号分隔) | R\<Boolean\> | 批量删除用户 |
+| PUT | /user/updateStatus | UpdateUserStatusDto | R\<Void\> | 启用/禁用用户 |
+| PUT | /user/updatePassword | UpdateUserPasswordDto | R\<Void\> | 修改密码 |
+| PUT | /user/updateAvatar | UpdateUserAvatarDto | R\<Void\> | 修改头像 |
+
+#### DTO / VO 定义
+
+**UserDto** (src/dto/user/UserDto.java)
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| id | Long | 编辑时必填 | 用户 ID |
+| account | String | 新增时必填 | 登录账号 |
+| username | String | 是 | 用户名 |
+| password | String | 新增时必填 | 密码 |
+| email | String | 否 | 邮箱 |
+| phone | String | 否 | 电话 |
+| avatar | String | 否 | 头像 URL |
+| role | String | 否 | 角色 (默认 user) |
+| enable | Integer | 否 | 状态 (默认 1) |
+
+**UpdateUserPasswordDto**
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| id | Long | - | 用户 ID |
+| oldPassword | String | @NotBlank | 旧密码 |
+| newPassword | String | @NotBlank | 新密码 |
+
+**UpdateUserAvatarDto**
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| id | Long | - | 用户 ID |
+| avatar | String | @NotBlank | 头像 URL |
+
+**UpdateUserStatusDto**
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| id | Long | - | 用户 ID |
+| enable | Integer | @NotNull | 0=禁用 1=启用 |
+
+#### Service 算法
+
+**queryByPage(pageNum, pageSize, username, account, role, enable):**
+1. 计算 `offset = (pageNum - 1) * pageSize`
+2. 调用 `userMapper.selectByPage(offset, pageSize, username, account, role, enable)` 查询当前页
+3. 调用 `userMapper.countByCondition(username, account, role, enable)` 查询总数
+4. 通过 `PageUtil.page()` 封装并返回
+
+**add(UserDto dto):**
+1. 校验账号唯一性:`userMapper.selectByAccount(dto.getAccount())` 若存在则抛异常
+2. 创建 User 对象,密码 MD5 加密
+3. `userMapper.insert(user)` 插入
+
+**edit(UserDto dto):**
+1. 修改 User 对象(密码为空字符串时不修改密码)
+2. `userMapper.update(user)` 动态更新非空字段
+
+**updatePassword(UpdateUserPasswordDto dto):**
+1. 查询用户:`userMapper.selectById(dto.getId())`
+2. 校验旧密码:`passwordConfig.matches(dto.getOldPassword(), user.getPassword())`
+3. 更新新密码:`userMapper.updatePassword(id, encrypt(newPassword))`
+
+**deleteByIds(List\<Long\> ids):**
+1. 调用 `userMapper.deleteByIds(ids)` 批量物理删除
+
+#### 流程图:分页查询
+
+```mermaid
+flowchart TD
+    A[前端请求 GET /api/user] --> B{接收参数}
+    B --> C[pageNum=1, pageSize=10]
+    B --> D[username=张三, role=user, enable=1]
+
+    C --> E[Service 层]
+    D --> E
+
+    E --> F[计算 offset=(pageNum-1)*pageSize]
+    F --> G[Mapper 层]
+    G --> H[selectByPage: LIMIT offset, pageSize + WHERE 条件]
+    G --> I[countByCondition: SELECT COUNT(*) + WHERE 条件]
+
+    H --> J[List< User > 当前页数据]
+    I --> K[long 总记录数]
+
+    J --> L[PageUtil.page 封装]
+    K --> L
+
+    L --> M[PageVo<UserVo>]
+    M --> N[返回前端: { data: [...], total: 50, pageNum: 1, pageSize: 10 }]
+```
+
+### 6.3 宠物模块 (PetController)
+
+**路由映射:** `@RequestMapping("/pets")`
+
+#### Controller 定义
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| GET | /pets | - | R\<List\<PetVo\>\> | 获取当前用户宠物列表 |
+| POST | /pets | PetDto | R\<PetVo\> | 新增宠物 |
+| GET | /pets/{id} | path: id | R\<PetVo\> | 获取宠物详情 |
+| PUT | /pets/{id} | path: id + PetDto | R\<Boolean\> | 修改宠物信息 |
+| DELETE | /pets/{id} | path: id | R\<Boolean\> | 删除宠物(级联删除疫苗/驱虫) |
+| GET | /pets/{id}/vaccinations | path: id | R\<List\<VaccinationVo\>\> | 获取疫苗记录 |
+| POST | /pets/{id}/vaccinations | path: id + VaccinationDto | R\<VaccinationVo\> | 新增疫苗记录 |
+| GET | /pets/{id}/deworming | path: id | R\<List\<DewormingVo\>\> | 获取驱虫记录 |
+| POST | /pets/{id}/deworming | path: id + DewormingDto | R\<DewormingVo\> | 新增驱虫记录 |
+
+> 所有接口从 request attribute 获取当前登录用户 ID,校验宠物归属权。
+
+#### DTO / VO 定义
+
+**PetDto** (src/dto/pet/PetDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| name | String | @NotBlank | 宠物名称 |
+| species | String | @NotBlank | 物种 (DOG/CAT/OTHER) |
+| breed | String | 否 | 品种 |
+| gender | String | 否 | MALE/FEMALE |
+| birthDate | LocalDate | 否 | 出生日期 |
+| weight | Double | 否 | 体重(kg) |
+| avatar | String | 否 | 头像 URL |
+| remark | String | 否 | 备注 |
+
+**VaccinationDto** (src/dto/pet/VaccinationDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| vaccineName | String | @NotBlank | 疫苗名称 |
+| vaccineDate | LocalDate | @NotNull | 接种日期 |
+| hospital | String | 否 | 接种机构 |
+| doctor | String | 否 | 接种兽医 |
+| remark | String | 否 | 备注 |
+
+**DewormingDto** (src/dto/pet/DewormingDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| dewormingName | String | @NotBlank | 驱虫名称 |
+| dewormingDate | LocalDate | @NotNull | 驱虫日期 |
+| hospital | String | 否 | 驱虫机构 |
+| doctor | String | 否 | 驱虫兽医 |
+| remark | String | 否 | 备注 |
+
+**PetVo** (src/vo/pet/PetVo.java)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | Long | 主键 |
+| userId | Long | 所属用户 ID |
+| name | String | 宠物名称 |
+| species | String | 物种 |
+| breed | String | 品种 |
+| gender | String | 性别 |
+| birthDate | LocalDate | 出生日期 |
+| weight | Double | 体重 |
+| avatar | String | 头像 |
+| remark | String | 备注 |
+| createdAt | LocalDateTime | 创建时间 |
+| vaccinations | List\<VaccinationVo\> | 疫苗记录列表(仅详情接口返回) |
+| dewormings | List\<DewormingVo\> | 驱虫记录列表(仅详情接口返回) |
+
+**VaccinationVo / DewormingVo** — 对应实体各字段 + createdAt。
+
+#### Service 算法
+
+**add(PetDto dto, Long userId):**
+1. 创建 Pet 实体,设置 `userId`,拷贝 dto 属性
+2. `petMapper.insert(pet)` — MyBatis 通过 `useGeneratedKeys` 回写 id
+3. 调用 `toPetVo(pet)` 转换后返回
+
+**delete(Long id, Long userId):**
+1. 查询宠物并校验归属权(`pet.getUserId().equals(userId)`)
+2. 级联删除:`vaccinationRecordMapper.deleteByPetId(id)`
+3. 级联删除:`dewormingRecordMapper.deleteByPetId(id)`
+4. `petMapper.deleteById(id)`
+
+**checkPetOwnership(petId, userId) — 归属校验工具方法:**
+1. `petMapper.selectById(petId)` 查询宠物
+2. 若宠物不存在或 `pet.getUserId() != userId` → 抛出 `BusinessException("宠物不存在或无权访问")`
+
+#### 时序图:新增宠物并添加疫苗记录
+
+```mermaid
+sequenceDiagram
+    participant F as 前端
+    participant C as PetController
+    participant S as PetServiceImpl
+    participant M as PetMapper
+    participant VRM as VaccinationRecordMapper
+    participant DB as MySQL
+
+    F->>+C: POST /api/pets { name, species, breed, gender }
+    Note over F,C: Header 携带 JSESSIONID
+
+    C->>C: request.getAttribute("userId")
+    C->>+S: add(petDto, userId)
+
+    S->>S: 创建 Pet 实体,设置 userId
+    S->>+M: insert(pet)
+    M->>+DB: INSERT INTO pet ...
+    DB-->>-M: auto-increment id
+    M-->>-S: 1 (影响行数)
+
+    S-->>-C: PetVo { id, name, species, ... }
+    C-->>-F: R<PetVo> { code:200, data: {...} }
+
+    Note over F: 用户添加疫苗记录
+
+    F->>+C: POST /api/pets/{petId}/vaccinations { vaccineName, vaccineDate }
+    C->>C: request.getAttribute("userId")
+    C->>+S: addVaccination(petId, dto, userId)
+
+    S->>+M: selectById(petId)
+    M-->>-S: Pet
+    S->>S: checkPetOwnership(petId, userId)
+
+    S->>S: 创建 VaccinationRecord 实体
+    S->>+VRM: insert(record)
+    VRM->>+DB: INSERT INTO vaccination_record ...
+    DB-->>-VRM: auto-increment id
+    VRM-->>-S: 1
+
+    S-->>-C: VaccinationVo { id, vaccineName, vaccineDate, ... }
+    C-->>-F: R<VaccinationVo> { code:200, data: {...} }
+```
+
+### 6.4 寄养房间模块
+
+#### 6.4.1 用户侧 (BoardingRoomController)
+
+**路由映射:** `@RequestMapping("/boarding-rooms")`
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| GET | /boarding-rooms | - | R\<List\<BoardingRoomVo\>\> | 获取所有启用的房型 |
+| GET | /boarding-rooms/{id}/status | query: month (yyyy-MM) | R\<List\<RoomStatusVo\>\> | 获取指定房型某月状态 |
+
+**请求示例:** `GET /api/boarding-rooms/1/status?month=2026-07`
+
+**响应示例:**
+```json
+{
+  "code": 200,
+  "message": "success",
+  "data": [
+    { "id": null, "roomId": 1, "date": "2026-07-01", "status": "AVAILABLE", "appointmentId": null },
+    { "id": 5, "roomId": 1, "date": "2026-07-02", "status": "OCCUPIED", "appointmentId": 3 },
+    { "id": null, "roomId": 1, "date": "2026-07-03", "status": "AVAILABLE", "appointmentId": null }
+  ]
+}
+```
+
+**BoardingRoomVo** (src/vo/room/BoardingRoomVo.java)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | Long | 房型 ID |
+| roomName | String | 房型名称 |
+| roomType | String | 分类 (STANDARD/VIP/DELUXE) |
+| capacity | Integer | 可容纳宠物数 |
+| pricePerDay | BigDecimal | 每晚价格 |
+| description | String | 房型描述 |
+
+**RoomStatusVo** (src/vo/room/RoomStatusVo.java)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | Long | 状态记录 ID (null 表示无记录) |
+| roomId | Long | 房型 ID |
+| date | LocalDate | 日期 |
+| status | String | AVAILABLE / OCCUPIED / MAINTENANCE |
+| appointmentId | Long | 关联预约 ID (null 表示无) |
+
+#### 6.4.2 管理员侧 (AdminBoardingRoomController)
+
+**路由映射:** `@RequestMapping("/admin/boarding-rooms")`
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| GET | /admin/boarding-rooms | - | R\<List\<BoardingRoomVo\>\> | 获取所有房型(含已禁用) |
+| POST | /admin/boarding-rooms | BoardingRoomCreateDto | R\<BoardingRoomVo\> | 新增房型 |
+| PUT | /admin/boarding-rooms/{id} | BoardingRoomCreateDto | R\<Boolean\> | 修改房型信息 |
+| DELETE | /admin/boarding-rooms/{id} | path: id | R\<Boolean\> | 删除房型(逻辑删除) |
+| GET | /admin/boarding-rooms/{id}/status | query: month | R\<List\<RoomStatusVo\>\> | 获取房型某月状态(管理员,含 appointmentId) |
+| PUT | /admin/boarding-rooms/{roomId}/status | RoomStatusDto | R\<Boolean\> | 手动修改某日房态 |
+| PUT | /admin/boarding-rooms/{roomId}/status/batch | BatchRoomStatusDto | R\<Boolean\> | 批量修改日期范围房态 |
+| GET | /admin/boarding-rooms/today-status | - | R\<TodayStatusVo\> | 今日所有房型入住概览 |
+
+#### DTO / VO 定义
+
+**RoomStatusDto** (src/dto/room/RoomStatusDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| date | LocalDate | @NotNull | 日期 |
+| status | String | @NotBlank | AVAILABLE / OCCUPIED / MAINTENANCE |
+
+**BatchRoomStatusDto**(新增 DTO)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| startDate | LocalDate | @NotNull | 开始日期 |
+| endDate | LocalDate | @NotNull | 结束日期 |
+| status | String | @NotBlank | 目标状态 |
+
+**BoardingRoomCreateDto**(新增 DTO)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| roomName | String | @NotBlank | 房型名称 |
+| roomType | String | 否 | 房型分类 |
+| capacity | Integer | 否 | 容纳数 |
+| pricePerDay | BigDecimal | @NotNull | 每晚价格 |
+| description | String | 否 | 描述 |
+
+**TodayStatusVo**(新增 VO)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| totalRooms | int | 总房型数 |
+| availableCount | int | 空闲房间数 |
+| occupiedCount | int | 入住房间数 |
+| maintenanceCount | int | 维护房间数 |
+| todayGuests | List\<TodayGuestVo\> | 今日入住宠物列表 |
+
+**TodayGuestVo**(新增 VO)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| roomName | String | 房型名称 |
+| petName | String | 宠物名称 |
+| ownerName | String | 主人姓名 |
+
+#### Service 算法
+
+**listRooms():**
+1. 调用 `boardingRoomMapper.selectActive()` 查询 `is_active = 1` 的所有房型
+2. 转为 `BoardingRoomVo` 列表返回
+
+**getStatus(Long roomId, String month):**
+1. 解析 `month = "2026-07"` 为 `YearMonth`,计算 `startDate = 当月1日`, `endDate = 当月最后一天`
+2. 调用 `boardingRoomStatusMapper.selectByRoomAndDateRange(roomId, startDate, endDate)` 查询已有记录
+3. 将查询结果构建为 `Map<LocalDate, BoardingRoomStatus>` 用于快速查找
+4. 遍历当月每一天:
+   - 若 statusMap 中存在该日期 → 使用数据库中的 status
+   - 若不存在 → 默认返回 `AVAILABLE`
+5. 返回 `List<RoomStatusVo>`
+
+**updateStatus(Long roomId, RoomStatusDto dto):**
+1. 创建 `BoardingRoomStatus` 实体:`roomId, date, status`
+2. 调用 `boardingRoomStatusMapper.insertOrUpdate(status)` — 使用 `ON DUPLICATE KEY UPDATE` 实现 upsert
+3. 若原记录有关联 `appointment_id`,手动修改时自动置为 NULL(由 SQL 中的 `appointment_id = #{appointmentId}` 控制,传入 null 时清空)
+
+**批量修改状态(batchUpdateStatus):**
+1. 校验 `startDate <= endDate`,差值不超过 31 天
+2. 在事务中循环每个日期,调用 `insertOrUpdate` 或批量 insert
+3. 对于 OCCUPIED 状态的日期,appointment_id 置为 NULL(手动修改)
+
+#### 时序图:管理员修改房间状态
+
+```mermaid
+sequenceDiagram
+    participant A as 管理员前端
+    participant C as AdminBoardingRoomController
+    participant S as BoardingRoomServiceImpl
+    participant M as BoardingRoomStatusMapper
+    participant DB as MySQL
+
+    A->>+C: PUT /api/admin/boarding-rooms/1/status
+    Note over A,C: { date: "2026-07-15", status: "MAINTENANCE" }
+
+    C->>+S: updateStatus(roomId=1, dto)
+
+    S->>S: 创建 BoardingRoomStatus 实体
+    Note over S: roomId=1, date=2026-07-15, status=MAINTENANCE, appointmentId=null
+
+    S->>+M: insertOrUpdate(status)
+    M->>+DB: INSERT INTO boarding_room_status ...
+    Note over DB: ON DUPLICATE KEY UPDATE<br/>status='MAINTENANCE',<br/>appointment_id=NULL
+    DB-->>-M: 1 (inserted or updated)
+    M-->>-S: 1
+
+    S-->>-C: true
+    C-->>-A: R<Boolean> { code:200, data: true }
+```
+
+#### 流程图:房间状态变更逻辑
+
+```mermaid
+flowchart TD
+    Start[商家选择修改房态] --> Input{输入参数}
+    Input --> RoomId[房型 ID]
+    Input --> Date[日期或日期范围]
+    Input --> TargetStatus[目标状态: AVAILABLE/OCCUPIED/MAINTENANCE]
+
+    Date --> CheckDate{是否过去的日期?}
+    CheckDate -->|是| RejectPast[拒绝: 不可修改历史数据]
+    CheckDate -->|否| CheckConflict{目标状态是 AVAILABLE?}
+
+    CheckConflict -->|是| CheckOrder{该日期有关联订单?}
+    CheckOrder -->|是| RejectOrder[拒绝: 存在关联订单,<br/>请先在订单管理中处理]
+    CheckOrder -->|否| DoUpdate
+
+    CheckConflict -->|否| DoUpdate[执行 UPDATE]
+
+    DoUpdate --> Upsert[INSERT ... ON DUPLICATE KEY UPDATE]
+    Upsert --> Result{执行结果}
+    Result -->|成功| Success[返回 true]
+    Result -->|失败| Error[抛出 BusinessException]
+```
+
+**请求/响应示例:**
+
+**新增房型:**
+```json
+// POST /api/admin/boarding-rooms
+// Request:
+{ "roomName": "豪华大间", "roomType": "VIP", "capacity": 2, "pricePerDay": 200.00, "description": "独立空调、24小时监控" }
+
+// Response:
+{ "code": 200, "message": "success", "data": { "id": 5, "roomName": "豪华大间", "roomType": "VIP", "capacity": 2, "pricePerDay": 200.00, "description": "独立空调、24小时监控" } }
+```
+
+**批量修改:**
+```json
+// PUT /api/admin/boarding-rooms/1/status/batch
+// Request:
+{ "startDate": "2026-07-10", "endDate": "2026-07-15", "status": "MAINTENANCE" }
+
+// Response:
+{ "code": 200, "message": "success", "data": true }
+```
+
+**今日概览:**
+```json
+// GET /api/admin/boarding-rooms/today-status
+// Response:
+{
+  "code": 200,
+  "message": "success",
+  "data": {
+    "totalRooms": 5,
+    "availableCount": 2,
+    "occupiedCount": 3,
+    "maintenanceCount": 0,
+    "todayGuests": [
+      { "roomName": "标准单间", "petName": "旺财", "ownerName": "张三" },
+      { "roomName": "豪华套房", "petName": "咪咪", "ownerName": "李四" }
+    ]
+  }
+}
+```
+
+### 6.5 套餐模块
+
+#### 6.5.1 用户侧 (PackageController)
+
+**路由映射:** `@RequestMapping("/packages")`
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| GET | /packages | query: type (BOARDING/GROOMING) | R\<List\<PackageVo\>\> | 获取套餐列表,可按类型筛选 |
+| GET | /packages/{id} | path: id | R\<PackageVo\> | 获取套餐详情 |
+
+**请求/响应示例:**
+```json
+// GET /api/packages?type=BOARDING
+// Response:
+{
+  "code": 200,
+  "message": "success",
+  "data": [
+    { "id": 1, "name": "标准寄养", "type": "BOARDING", "price": 100.00, "duration": 1, "description": "基础寄养服务", "createdAt": "2026-06-01T10:00:00" },
+    { "id": 2, "name": "豪华寄养", "type": "BOARDING", "price": 200.00, "duration": 1, "description": "豪华寄养服务", "createdAt": "2026-06-01T10:00:00" }
+  ]
+}
+```
+
+#### 6.5.2 管理员侧 (AdminPackageController)
+
+**路由映射:** `@RequestMapping("/admin/packages")`
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| POST | /admin/packages | PackageDto | R\<PackageVo\> | 新增套餐 |
+| PUT | /admin/packages/{id} | PackageDto | R\<Boolean\> | 修改套餐 |
+| DELETE | /admin/packages/{id} | path: id | R\<Boolean\> | 删除套餐(逻辑删除) |
+
+#### DTO / VO 定义
+
+**PackageDto** (src/dto/servicePackage/PackageDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| name | String | @NotBlank | 套餐名称 |
+| type | String | @NotBlank | BOARDING / GROOMING |
+| price | BigDecimal | @NotNull | 价格 |
+| duration | Integer | 否 | 寄养天数(仅寄养套餐) |
+| description | String | 否 | 描述 |
+| isActive | Integer | 否 | 是否启用 (默认 1) |
+
+**PackageVo** (src/vo/servicePackage/PackageVo.java)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | Long | 套餐 ID |
+| name | String | 名称 |
+| type | String | 类型 |
+| price | BigDecimal | 价格 |
+| duration | Integer | 天数 |
+| description | String | 描述 |
+| createdAt | LocalDateTime | 创建时间 |
+
+#### Service 算法
+
+**list(String type):**
+1. 若 type 为空 → 查询所有套餐;若 type 非空 → 按 type 筛选
+2. `packageMapper.selectByType(type)` — SQL 中 `WHERE type = #{type} AND is_active = 1`
+3. 转为 PackageVo 列表返回
+
+**create(PackageDto dto):**
+1. 若 `isActive` 为空,默认设为 1
+2. 创建 ServicePackage 实体,拷贝属性
+3. `packageMapper.insert(pkg)` — 返回自动生成的 id
+4. 转 PackageVo 返回
+
+**delete(Long id):**
+1. 调用 `packageMapper.deleteById(id)` — SQL: `UPDATE package SET is_active = 0 WHERE id = #{id}`
+2. 逻辑删除,不物理删除
+
+### 6.6 预约订单模块(最复杂模块)
+
+#### 6.6.1 用户侧 (AppointmentController)
+
+**路由映射:** `@RequestMapping("/appointments")`
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| POST | /appointments | AppointmentDto | R\<AppointmentVo\> | 提交预约 |
+| GET | /appointments | - | R\<List\<AppointmentVo\>\> | 获取我的预约列表 |
+| GET | /appointments/{id} | path: id | R\<AppointmentVo\> | 获取预约详情 |
+| POST | /appointments/{id}/cancel | body: { reason } | R\<Boolean\> | 申请取消 |
+| POST | /appointments/{id}/pay-deposit | - | R\<Boolean\> | 模拟支付定金 |
+
+#### 6.6.2 管理员侧 (AdminAppointmentController)
+
+**路由映射:** `@RequestMapping("/admin/appointments")`
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| GET | /admin/appointments | query: pageNum, pageSize, status, type | R\<PageVo\<AppointmentVo\>\> | 管理员分页查询全部预约 |
+| PUT | /admin/appointments/{id}/review | ReviewDto | R\<Boolean\> | 审核预约(通过/拒绝) |
+| PUT | /admin/appointments/{id}/handle-cancel | HandleCancelDto | R\<Boolean\> | 处理取消申请 |
+
+#### DTO / VO 定义
+
+**AppointmentDto** (src/dto/appointment/AppointmentDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| petId | Long | @NotNull | 宠物 ID |
+| packageId | Long | @NotNull | 套餐 ID |
+| roomId | Long | 寄养必填 | 寄养房型 ID |
+| startDate | LocalDate | 寄养必填 | 开始日期 |
+| endDate | LocalDate | 寄养必填 | 结束日期 |
+| remark | String | 否 | 备注 |
+
+> 注意:type 和 totalPrice 不由前端传入,由服务端根据套餐信息自动推导和计算。
+
+**ReviewDto** (src/dto/appointment/ReviewDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| action | String | @NotBlank | APPROVE / REJECT |
+| rejectReason | String | 拒绝时建议填写 | 拒绝原因 |
+
+**HandleCancelDto** (src/dto/appointment/HandleCancelDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| action | String | @NotBlank | AGREE / REJECT |
+
+**AppointmentVo** (src/vo/appointment/AppointmentVo.java)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | Long | 预约 ID |
+| userId | Long | 用户 ID |
+| petId | Long | 宠物 ID |
+| petName | String | 宠物名称(关联查询) |
+| packageId | Long | 套餐 ID |
+| packageName | String | 套餐名称(关联查询) |
+| roomId | Long | 房型 ID |
+| roomName | String | 房型名称(关联查询) |
+| type | String | BOARDING / GROOMING |
+| status | String | PENDING / CONFIRMED / CANCELLED / CANCEL_REQUESTED / COMPLETED |
+| startDate | LocalDate | 开始日期 |
+| endDate | LocalDate | 结束日期 |
+| totalPrice | BigDecimal | 总价 |
+| deposit | BigDecimal | 定金金额 |
+| depositPaid | Boolean | 是否支付定金 |
+| cancelReason | String | 取消原因 |
+| rejectReason | String | 拒绝原因 |
+| remark | String | 备注 |
+| createdAt | LocalDateTime | 创建时间 |
+
+#### Service 算法
+
+**a) create(AppointmentDto dto, Long userId) — 创建预约(@Transactional)**
+
+```
+1. 套餐校验:
+   package = packageMapper.selectById(dto.getPackageId())
+   若 package == null || package.isActive == 0 → 抛异常 "套餐不存在或已下架"
+
+2. 宠物校验:
+   pet = petMapper.selectById(dto.getPetId())
+   若 pet == null || pet.userId != userId → 抛异常 "宠物不存在或无权访问"
+
+3. 创建 Appointment 实体:
+   appointment.userId = userId
+   appointment.petId = dto.petId
+   appointment.packageId = dto.packageId
+   appointment.type = package.type
+   appointment.status = "PENDING"
+   appointment.depositPaid = false
+   appointment.remark = dto.remark
+
+4. 若 type == BOARDING(寄养):
+   a) 校验 roomId, startDate, endDate 不为空
+   b) room = boardingRoomMapper.selectById(dto.roomId),校验 room != null && room.isActive == 1
+   c) 设置 appointment.roomId, startDate, endDate
+   d) days = ChronoUnit.DAYS.between(startDate, endDate) — 计算天数
+   e) 若 days <= 0 → 抛异常 "结束日期必须晚于开始日期"
+   f) 房间可用性校验:
+      statusList = boardingRoomStatusMapper.selectByRoomAndDateRange(roomId, startDate, endDate-1)
+      遍历 statusList,若有任意 status != "AVAILABLE" → 抛异常 "所选日期范围内房型不可用"
+   g) totalPrice = room.pricePerDay * days(服务端计算,防止篡改)
+
+5. 若 type == GROOMING(洗护):
+   appointment.startDate = dto.startDate
+   appointment.totalPrice = package.price
+
+6. deposit = totalPrice * 0.3(定金 = 总价 × 30%)
+   appointment.deposit = deposit
+   appointment.totalPrice = totalPrice
+
+7. appointmentMapper.insert(appointment) — INSERT 并回写 id
+
+8. 返回 toAppointmentVo(appointment)
+```
+
+**b) list(Long userId):**
+1. `appointmentMapper.selectByUserId(userId)` — 按创建时间倒序
+2. 转为 AppointmentVo 列表返回
+
+**c) getById(Long id, Long userId):**
+1. 查询并校验归属权
+2. 转为 AppointmentVo 返回
+
+**d) cancel(Long id, String reason, Long userId) — 取消订单:**
+
+```
+1. 查询并校验归属权
+2. 校验状态:仅 "PENDING" 和 "CONFIRMED" 允许取消
+3. 若 status == "PENDING":
+   - 直接取消:appointmentMapper.updateCancelReason(id, reason)
+   - SQL:UPDATE appointment SET cancel_reason=?, status='CANCELLED' WHERE id=?
+   - 若有 boarding_room_status 关联 → 释放房间(releaseRoomStatus)
+4. 若 status == "CONFIRMED":
+   - 进入取消待审:appointmentMapper.updateCancelReason(id, reason)
+   - SQL:UPDATE appointment SET cancel_reason=?, status='CANCEL_REQUESTED' WHERE id=?
+   - 等待商家审核处理
+```
+
+**e) payDeposit(Long id, Long userId) — 支付定金:**
+
+```
+1. 查询并校验归属权
+2. 校验:depositPaid 必须为 false(防止重复支付)
+3. 校验:status 必须为 "PENDING"
+4. appointmentMapper.updateDepositPaid(id, true)
+5. 返回 true
+```
+
+**f) listAll(pageNum, pageSize, status, type) — 管理员查询:**
+
+```
+1. 计算 offset = (pageNum - 1) * pageSize
+2. appointmentMapper.selectAllByPage(offset, pageSize, status, type) — 动态 WHERE
+3. appointmentMapper.countAll(status, type) — 总数
+4. PageUtil.page() 封装返回
+```
+
+**g) review(Long id, ReviewDto dto) — 审核预约(@Transactional):**
+
+```
+1. 查询订单,校验 status == "PENDING"
+2. 若 action == "APPROVE":
+   a) appointmentMapper.updateStatus(id, "CONFIRMED")
+   b) 若 type == BOARDING && roomId != null && 日期齐全:
+      - 生成 [startDate, endDate) 之间的所有日期列表
+      - 为每个日期创建 BoardingRoomStatus:
+        roomId, date, status="OCCUPIED", appointmentId=id
+      - boardingRoomStatusMapper.insertBatch(statusList)
+      - 此处使用普通 INSERT(非 ON DUPLICATE),因为审核时已确认房间可用
+   c) 返回 true
+
+3. 若 action == "REJECT":
+   a) 设置 appointment.status = "CANCELLED"
+   b) 设置 appointment.rejectReason = dto.rejectReason
+   c) appointmentMapper.update(appointment) — 动态更新
+   d) 不修改 boarding_room_status(从未写入过)
+   e) 返回 true
+```
+
+**h) handleCancel(Long id, HandleCancelDto dto) — 处理取消申请(@Transactional):**
+
+```
+1. 查询订单,校验 status == "CANCEL_REQUESTED"
+2. 若 action == "AGREE":
+   a) appointmentMapper.updateStatus(id, "CANCELLED")
+   b) 若 type == BOARDING:
+      - boardingRoomStatusMapper.deleteByAppointmentId(id)
+      - SQL: DELETE FROM boarding_room_status WHERE appointment_id = #{id}
+      - 释放所有关联的房间状态记录
+   c) 返回 true
+
+3. 若 action == "REJECT":
+   a) appointmentMapper.updateStatus(id, "CONFIRMED") — 恢复原状态
+   b) 不修改房间状态
+   c) 返回 true
+```
+
+> 注意:现有实现采用物理删除而非 UPDATE status='AVAILABLE' + appointment_id=NULL,因为审核通过时已确认无冲突,取消时直接删除更简洁。
+
+#### 状态机:预约订单状态变迁
+
+```mermaid
+stateDiagram-v2
+    [*] --> PENDING: 创建预约
+
+    PENDING --> CONFIRMED: 管理员审核通过
+    PENDING --> CANCELLED: 用户直接取消
+    PENDING --> CANCELLED: 管理员拒绝审核
+
+    CONFIRMED --> CANCEL_REQUESTED: 用户申请取消
+    CONFIRMED --> COMPLETED: 服务完成
+
+    CANCEL_REQUESTED --> CANCELLED: 商家同意取消
+    CANCEL_REQUESTED --> CONFIRMED: 商家拒绝取消
+
+    CANCELLED --> [*]: 终态
+    COMPLETED --> [*]: 终态
+```
+
+**状态转换规则:**
+
+| 当前状态 | 允许操作 | 目标状态 | 说明 |
+|---------|---------|---------|------|
+| PENDING | 用户取消 | CANCELLED | 直接取消,房间未锁 |
+| PENDING | 支付定金 | PENDING | depositPaid=true |
+| PENDING | 管理员审核通过 | CONFIRMED | 锁定房间 |
+| PENDING | 管理员拒绝 | CANCELLED | 写入 rejectReason |
+| CONFIRMED | 用户申请取消 | CANCEL_REQUESTED | 等待商家处理 |
+| CONFIRMED | 服务完成 | COMPLETED | 后台操作 |
+| CANCEL_REQUESTED | 商家同意 | CANCELLED | 释放房间,退定金 |
+| CANCEL_REQUESTED | 商家拒绝 | CONFIRMED | 恢复原状 |
+
+#### 时序图:寄养预约创建流程
+
+```mermaid
+sequenceDiagram
+    participant U as 用户
+    participant F as 前端
+    participant C as AppointmentController
+    participant S as AppointmentServiceImpl
+    participant M as Mappers
+    participant DB as MySQL
+
+    U->>F: 选择寄养套餐/房型/日期/宠物
+    F->>F: 前端连续性校验
+
+    F->>+C: POST /api/appointments
+    Note over F,C: { petId:2, packageId:1, roomId:3,<br/>startDate:2026-07-01, endDate:2026-07-04 }
+
+    C->>+S: create(dto, userId)
+
+    S->>+M: packageMapper.selectById(1)
+    M->>DB: SELECT * FROM package WHERE id=1
+    DB-->>M: ServicePackage { type:BOARDING, price:100, isActive:1 }
+    M-->>-S: Package exists
+
+    S->>+M: petMapper.selectById(2)
+    M->>DB: SELECT * FROM pet WHERE id=2
+    DB-->>M: Pet { userId: userId, name:旺财 }
+    M-->>-S: Pet exists & owned
+
+    S->>+M: boardingRoomMapper.selectById(3)
+    M->>DB: SELECT * FROM boarding_room WHERE id=3
+    DB-->>M: BoardingRoom { pricePerDay:100, isActive:1 }
+    M-->>-S: Room exists & active
+
+    S->>+M: boardingRoomStatusMapper.selectByRoomAndDateRange(3, 2026-07-01, 2026-07-03)
+    M->>DB: SELECT * FROM boarding_room_status<br/>WHERE room_id=3 AND date BETWEEN '2026-07-01' AND '2026-07-03'
+    DB-->>M: [status=AVAILABLE, status=AVAILABLE, ...]
+    M-->>-S: All AVAILABLE → 可用
+
+    S->>S: days = 3, totalPrice = 100*3 = 300, deposit = 300*0.3 = 90
+
+    S->>+M: appointmentMapper.insert(appointment)
+    M->>DB: INSERT INTO appointment ...
+    DB-->>M: id generated
+    M-->>-S: 1
+
+    S-->>-C: AppointmentVo { id:10, status:PENDING, deposit:90, ... }
+    C-->>-F: R<AppointmentVo> { code:200, data: {...} }
+
+    F-->>U: 显示订单信息,引导支付
+```
+
+#### 时序图:取消 + 处理取消流程
+
+```mermaid
+sequenceDiagram
+    participant U as 用户
+    participant C as AppointmentController
+    participant AC as AdminAppointmentController
+    participant S as AppointmentServiceImpl
+    participant DB as MySQL
+
+    %% PENDING 状态取消
+    U->>C: POST /appointments/10/cancel { reason:"行程变更" }
+    C->>S: cancel(10, "行程变更", userId)
+    S->>DB: SELECT * FROM appointment WHERE id=10
+    S->>S: status == "PENDING" → 直接取消
+    S->>DB: UPDATE appointment SET status='CANCELLED', cancel_reason='行程变更' WHERE id=10
+    S-->>C: true
+    C-->>U: { code:200, data: true }
+
+    %% CONFIRMED 状态申请取消
+    U->>C: POST /appointments/11/cancel { reason:"宠物生病" }
+    C->>S: cancel(11, "宠物生病", userId)
+    S->>DB: SELECT * FROM appointment WHERE id=11
+    S->>S: status == "CONFIRMED" → 转为等待审核
+    S->>DB: UPDATE appointment SET status='CANCEL_REQUESTED', cancel_reason='宠物生病' WHERE id=11
+    S-->>C: true
+    C-->>U: { code:200, data: true }
+
+    %% 商家处理取消
+    商家->>AC: PUT /admin/appointments/11/handle-cancel { action:"AGREE" }
+    AC->>S: handleCancel(11, dto)
+    S->>DB: SELECT * FROM appointment WHERE id=11
+    S->>S: status == "CANCEL_REQUESTED" → 处理
+    S->>DB: UPDATE appointment SET status='CANCELLED' WHERE id=11
+    S->>DB: DELETE FROM boarding_room_status WHERE appointment_id=11
+    S-->>AC: true
+    AC-->>商家: { code:200, data: true }
+```
+
+**请求/响应示例:**
+
+**创建预约:**
+```json
+// POST /api/appointments
+// Request (寄养):
+{ "petId": 2, "packageId": 1, "roomId": 3, "startDate": "2026-07-01", "endDate": "2026-07-04", "remark": "猫咪胆小,请多关照" }
+
+// Request (洗护):
+{ "petId": 3, "packageId": 4, "startDate": "2026-07-05", "remark": "下午2点到店" }
+
+// Response:
+{ "code": 200, "message": "success", "data": { "id": 10, "userId": 1, "petId": 2, "petName": "旺财", "packageId": 1, "packageName": "标准寄养", "roomId": 3, "roomName": "标准单间", "type": "BOARDING", "status": "PENDING", "startDate": "2026-07-01", "endDate": "2026-07-04", "totalPrice": 300.00, "deposit": 90.00, "depositPaid": false, "createdAt": "2026-07-01T10:30:00" } }
+```
+
+**支付定金:**
+```json
+// POST /api/appointments/10/pay-deposit
+// Response:
+{ "code": 200, "message": "success", "data": true }
+```
+
+**审核预约:**
+```json
+// PUT /api/admin/appointments/10/review
+// Request (approved):
+{ "action": "APPROVE" }
+
+// Request (rejected):
+{ "action": "REJECT", "rejectReason": "该日期房间已被其他订单占用" }
+
+// Response:
+{ "code": 200, "message": "success", "data": true }
+```
+
+**用户取消:**
+```json
+// POST /api/appointments/10/cancel
+// Request:
+{ "reason": "行程变更" }
+
+// Response:
+{ "code": 200, "message": "success", "data": true }
+```
+
+**处理取消申请:**
+```json
+// PUT /api/admin/appointments/10/handle-cancel
+// Request (agree):
+{ "action": "AGREE" }
+
+// Request (reject):
+{ "action": "REJECT" }
+
+// Response:
+{ "code": 200, "message": "success", "data": true }
+```
+
+### 6.7 排班模块 (AdminScheduleController)
+
+**路由映射:** `@RequestMapping("/admin/schedules")`
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| GET | /admin/schedules | query: month (yyyy-MM) | R\<List\<ScheduleVo\>\> | 按月获取排班列表 |
+| POST | /admin/schedules | ScheduleDto | R\<ScheduleVo\> | 新增排班 |
+| DELETE | /admin/schedules/{id} | path: id | R\<Boolean\> | 删除排班 |
+
+#### DTO / VO 定义
+
+**ScheduleDto** (src/dto/schedule/ScheduleDto.java)
+
+| 字段 | 类型 | 约束 | 说明 |
+|------|------|------|------|
+| employeeName | String | @NotBlank | 员工姓名 |
+| workDate | LocalDate | @NotNull | 排班日期 |
+| timeSlot | String | @NotBlank | MORNING / AFTERNOON / FULL_DAY |
+| remark | String | 否 | 备注 |
+
+**ScheduleVo** (src/vo/schedule/ScheduleVo.java)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | Long | 排班 ID |
+| employeeName | String | 员工姓名 |
+| workDate | LocalDate | 排班日期 |
+| timeSlot | String | 时段 |
+| remark | String | 备注 |
+| createdAt | LocalDateTime | 创建时间 |
+
+#### Service 算法
+
+**list(String month):**
+1. 若 month 非空 → 解析为 YearMonth,计算 startDate(当月1日)和 endDate(当月最后一天)
+2. 若 month 为空 → 查询全部(使用 LocalDate.MIN / MAX 作为日期范围)
+3. `scheduleMapper.selectByMonth(startDate, endDate)` — SQL: `WHERE work_date BETWEEN ? AND ?`
+4. 转为 ScheduleVo 列表返回
+
+**create(ScheduleDto dto):**
+1. 冲突检测:`scheduleMapper.countConflict(dto.getEmployeeName(), dto.getWorkDate(), dto.getTimeSlot())`
+2. 若 count > 0 → 抛异常 "该员工在此时间段已有排班"
+3. 创建 Schedule 实体,插入数据库
+4. 返回 ScheduleVo
+
+**delete(Long id):**
+1. `scheduleMapper.deleteById(id)` — 物理删除
+
+#### 请求/响应示例
+
+```json
+// GET /api/admin/schedules?month=2026-07
+// Response:
+{ "code": 200, "message": "success", "data": [
+  { "id": 1, "employeeName": "张设计师", "workDate": "2026-07-01", "timeSlot": "MORNING", "remark": null, "createdAt": "2026-06-28T10:00:00" },
+  { "id": 2, "employeeName": "李美容师", "workDate": "2026-07-01", "timeSlot": "AFTERNOON", "remark": null, "createdAt": "2026-06-28T10:00:00" }
+] }
+
+// POST /api/admin/schedules
+// Request:
+{ "employeeName": "张设计师", "workDate": "2026-07-05", "timeSlot": "FULL_DAY" }
+// Response:
+{ "code": 200, "message": "success", "data": { "id": 3, "employeeName": "张设计师", "workDate": "2026-07-05", "timeSlot": "FULL_DAY", "createdAt": "2026-07-01T12:00:00" } }
+```
+
+### 6.8 管理员用户管理 (AdminUserController)
+
+**路由映射:** `@RequestMapping("/admin/users")`
+
+| 方法 | 路径 | 参数 | 返回 | 说明 |
+|------|------|------|------|------|
+| GET | /admin/users | query: pageNum, pageSize, username, account, role, enable | R\<PageVo\<UserVo\>\> | 分页查询注册用户列表 |
+| GET | /admin/users/{id} | path: id | R\<AdminUserDetailVo\> | 获取用户详情及宠物档案 |
+
+#### VO 定义
+
+**AdminUserDetailVo** (src/vo/user/AdminUserDetailVo.java)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| user | UserVo | 用户基本信息 |
+| pets | List\<PetVo\> | 该用户的所有宠物(含疫苗/驱虫记录) |
+
+#### Service 算法
+
+**getAdminDetail(Long id):**
+1. 查询用户:`userMapper.selectById(id)`,不存在则抛异常
+2. 查询宠物列表:`petMapper.selectByUserId(id)`
+3. 为每个宠物查询疫苗记录和驱虫记录:
+   - `vaccinationRecordMapper.selectByPetId(pet.getId())` → List\<VaccinationRecord\>
+   - `dewormingRecordMapper.selectByPetId(pet.getId())` → List\<DewormingRecord\>
+4. 组装 AdminUserDetailVo 返回
+
+**queryByPage — 复用 UserServiceImpl.queryByPage 方法,参数同 UserController。**
+
+## 7. 配置文件 (application.yaml)
+
+```yaml
+spring:
+  application:
+    name: PetServiceManagementSystem
+  datasource:
+    url: jdbc:mysql://localhost:3306/pet_service_managemen_system?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
+    username: root
+    password: root
+    driver-class-name: com.mysql.cj.jdbc.Driver
+
+mybatis:
+  mapper-locations: classpath:mapper/*.xml
+  type-aliases-package: space.anyi.petServiceManagementSystem.server.entity
+  configuration:
+    map-underscore-to-camel-case: true
+
+springdoc:
+  api-docs:
+    path: /v3/api-docs
+  swagger-ui:
+    path: /swagger-ui.html
+
+server:
+  port: 8080
+  servlet:
+    context-path: /
+    session:
+      timeout: 1800                  # Session 超时时间(秒)= 30分钟
+      cookie:
+        http-only: true              # 禁止客户端 JS 读取 Cookie
+        same-site: lax               # 防止 CSRF 攻击
+```
+
+**配置要点说明:**
+
+| 配置项 | 说明 |
+|--------|------|
+| `map-underscore-to-camel-case: true` | 数据库下划线命名自动映射为 Java 驼峰命名 |
+| `type-aliases-package` | MyBatis XML 中可直接使用类名(如 `resultType="User"`) |
+| `session.timeout=1800` | HttpSession 30 分钟无操作自动过期 |
+| `http-only: true` | 防止 XSS 攻击窃取 Session Cookie |
+| `same-site: lax` | 限制第三方网站跨站请求携带 Cookie |
+
+## 8. 模块协作规范
+
+### 8.1 分层职责
+
+```
+Controller 层 (@RestController)
+  职责:接收 HTTP 请求、参数校验(@Valid)、调用 Service、封装 R 响应
+  规则:不写业务逻辑,只做参数校验和结果封装
+
+Service 层 (@Service)
+  职责:业务逻辑编排、事务管理(@Transactional)、校验规则、调用 Mapper
+  规则:所有业务异常抛出 BusinessException
+
+Mapper 层 (@Mapper + XML)
+  职责:SQL 语句执行、ORM 映射
+  规则:复杂查询在 XML 中编写,简单查询使用注解
+
+Entity 层
+  职责:数据库表映射 POJO
+  规则:字段与表列一一对应,不包含业务逻辑
+
+DTO / VO
+  DTO:请求参数封装(可含 @Valid 注解)
+  VO:响应数据封装(不暴露实体敏感字段)
+```
+
+### 8.2 事务管理
+
+| 场景 | 事务注解 | 说明 |
+|------|---------|------|
+| 注册 | `@Transactional(rollbackFor = Exception.class)` | 账号唯一性校验 + 插入为用户原子操作 |
+| 创建预约 | `@Transactional(rollbackFor = Exception.class)` | 套餐校验 + 宠物校验 + 房间校验 + 插入订单 |
+| 支付定金 | `@Transactional(rollbackFor = Exception.class)` | 校验 + 更新支付状态 |
+| 审核预约 | `@Transactional(rollbackFor = Exception.class)` | 更新订单 + 批量生成房态 |
+| 取消订单 | `@Transactional(rollbackFor = Exception.class)` | 更新订单 + 释放房态 |
+| 手动改房态 | `@Transactional(rollbackFor = Exception.class)` | UPSERT 操作 |
+
+### 8.3 API 规范
+
+| 规范 | 说明 |
+|------|------|
+| 统一前缀 | 所有接口以 `/api` 为前缀(通过 nginx 或 context-path 配置) |
+| 统一响应 | 所有接口返回 `R<T>` 格式 |
+| 分页参数 | `pageNum` 从 1 开始,`pageSize` 默认 10 |
+| 日期格式 | 请求 `yyyy-MM-dd`,响应 `yyyy-MM-dd` / `yyyy-MM-dd'T'HH:mm:ss` |
+| 认证方式 | HttpSession (Cookie-based),前端需设置 `withCredentials: true` |
+| 权限控制 | 普通接口需登录(AuthInterceptor),`/admin/**` 接口需管理员(AdminInterceptor) |
+| 参数校验 | 使用 `@Valid` + `@NotBlank/@NotNull/@Size` 等 Jakarta Validation 注解 |
+
+## 9. 测试策略
+
+### 单元测试
+
+| 测试目标 | 框架 | 覆盖内容 |
+|---------|------|---------|
+| Service 实现类 | JUnit 5 + Mockito | 核心业务逻辑(创建预约校验、状态转换、房间可用性判断) |
+| 工具类 | JUnit 5 | PageUtil、PasswordConfig(MD5 加密/匹配) |
+| 异常路径 | JUnit 5 + Mockito | BusinessException 的各个触发场景 |
+
+### 集成测试
+
+| 测试目标 | 框架 | 覆盖内容 |
+|---------|------|---------|
+| Controller 接口 | Spring Boot Test + MockMvc | 所有 API 的请求/响应验证 |
+| Mapper 映射 | MyBatis Spring Boot Test | SQL 语句正确性、结果映射 |
+| 数据库交互 | @MybatisTest + H2 内存数据库 | CRUD 操作、唯一约束、事务回滚 |
+
+### 关键测试场景
+
+```
+1. 认证模块
+   - 注册成功并自动登录(Session 中有 user 属性)
+   - 注册已存在的账号(抛 BusinessException)
+   - 登录成功(Session 有 user)
+   - 登录失败(密码错误/账号禁用)
+   - 未登录访问受保护接口(返回 401)
+
+2. 预约模块(核心)
+   - 创建寄养预约成功(校验通过)
+   - 创建预约时房间不可用(房间状态校验)
+   - 创建预约时宠物冲突(时间重叠校验)
+   - 支付定金成功(depositPaid=true)
+   - 重复支付(抛 BusinessException)
+   - 审核通过并自动锁定房间(生成 OCCUPIED 记录)
+   - 审核拒绝(房间未锁定)
+   - PENDING 状态用户直接取消(状态变为 CANCELLED)
+   - CONFIRMED 状态用户申请取消(状态变为 CANCEL_REQUESTED)
+   - 商家同意取消(状态变为 CANCELLED + 房间释放)
+   - 商家拒绝取消(状态恢复为 CONFIRMED)
+
+3. 宠物房间管理
+   - 手动修改房态(UPSERT 正确)
+   - 批量修改房态(事务回滚测试)
+   - 查询某月房态(无记录的天默认 AVAILABLE)
+   - 新增/修改/删除房型
+
+4. 用户管理
+   - 分页查询(正确计算 offset)
+   - 新增用户(MD5 加密密码)
+   - 修改密码(旧密码校验)
+```

+ 172 - 0
nextWork.md

@@ -0,0 +1,172 @@
+# 下一步开发工作规划
+
+> 基于 `detailDesign.md` 与当前项目实际代码的差异分析
+
+## 项目当前状态
+
+**代码骨架已全部完成:** 基础设施、Entity、DTO、VO、Mapper/XML、Service、Controller 共计 ~70 个 Java 文件已全部创建。
+
+**可编译启动:** `./mvnw spring-boot:run` 可正常启动,但存在以下待办事项。
+
+---
+
+## 一、确认存在的 Bug(需优先修复)
+
+### 1.1 取消订单逻辑错误(严重)
+
+**问题位置:** `service/impl/AppointmentServiceImpl.java:119-130` + `mapper/AppointmentMapper.xml:56-58`
+
+| 当前状态 | 设计文档要求 | 当前代码行为 |
+|---------|-------------|-------------|
+| PENDING | 直接 `status='CANCELLED'` | 错误地设置为 `CANCEL_REQUESTED` |
+| CONFIRMED | 设置为 `status='CANCEL_REQUESTED'` | 正确(但需要与服务端逻辑解耦) |
+
+**根本原因:** `AppointmentMapper.xml` 中 `updateCancelReason` 硬编码了 `status = 'CANCEL_REQUESTED'`,导致 PENDING 和 CONFIRMED 两种场景都进入等待审核状态。
+
+**修复方案:**
+1. 删除 `updateCancelReason` 方法(或改为仅更新 `cancel_reason` 字段,不修改 status)
+2. `AppointmentServiceImpl.cancel()` 中根据当前 status 分别处理:
+   - PENDING → `updateStatus(id, "CANCELLED")` + 更新 `cancel_reason`(房间状态未锁定,无需释放)
+   - CONFIRMED → `updateStatus(id, "CANCEL_REQUESTED")` + 更新 `cancel_reason`
+
+---
+
+## 二、遗漏的管理后台房间管理功能(较大工作量)
+
+设计文档 `6.4.2` 节定义了 8 个管理员房间接口,当前仅实现了 1 个(`PUT .../status`)。
+
+### 2.1 需新建的 DTO
+
+| 文件 | 字段 |
+|------|------|
+| `dto/room/BatchRoomStatusDto.java` | startDate(LocalDate), endDate(LocalDate), status(String) |
+| `dto/room/BoardingRoomCreateDto.java` | roomName(String), roomType(String), capacity(Integer), pricePerDay(BigDecimal), description(String) |
+
+### 2.2 需新建的 VO
+
+| 文件 | 字段 |
+|------|------|
+| `vo/room/TodayStatusVo.java` | totalRooms(int), availableCount(int), occupiedCount(int), maintenanceCount(int), todayGuests(List\<TodayGuestVo\>) |
+| `vo/room/TodayGuestVo.java` | roomName(String), petName(String), ownerName(String) |
+
+### 2.3 需补充的 Mapper 层
+
+**`mapper/BoardingRoomMapper.java`** 新增方法:
+
+| 方法 | 用途 |
+|------|------|
+| `selectAll()` | 查询所有房型(含已禁用) |
+| `deleteById(Long id)` | 逻辑删除(`UPDATE ... SET is_active = 0`) |
+
+**`mapper/BoardingRoomMapper.xml`** 新增对应的 SQL 语句。
+
+### 2.4 需补充的 Service 层
+
+**`service/BoardingRoomService.java`** 接口新增:
+
+| 方法 | 说明 |
+|------|------|
+| `listAllRooms()` | 获取所有房型(含已禁用) |
+| `createRoom(BoardingRoomCreateDto dto)` | 新增房型 |
+| `updateRoom(Long id, BoardingRoomCreateDto dto)` | 修改房型信息 |
+| `deleteRoom(Long id)` | 逻辑删除房型 |
+| `batchUpdateStatus(Long roomId, BatchRoomStatusDto dto)` | 批量修改日期范围房态(@Transactional,范围 ≤31 天) |
+| `getTodayStatus()` | 今日所有房型入住概览 |
+
+**`service/impl/BoardingRoomServiceImpl.java`** 实现上述方法。
+
+> `getTodayStatus()` 算法要点:
+> 1. 查询 `boarding_room_status` 中今天日期的所有记录
+> 2. 统计 AVAILABLE/OCCUPIED/MAINTENANCE 数量
+> 3. 关联查询 appointment + pet + user 获取今日入住宠物列表
+
+### 2.5 需补充的 Controller 层
+
+**`controller/admin/AdminBoardingRoomController.java`** 新增 7 个接口:
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| GET | `/admin/boarding-rooms` | 获取所有房型 |
+| POST | `/admin/boarding-rooms` | 新增房型 |
+| PUT | `/admin/boarding-rooms/{id}` | 修改房型 |
+| DELETE | `/admin/boarding-rooms/{id}` | 删除房型 |
+| GET | `/admin/boarding-rooms/{id}/status` | 获取房态(管理员) |
+| PUT | `/admin/boarding-rooms/{roomId}/status/batch` | 批量修改房态 |
+| GET | `/admin/boarding-rooms/today-status` | 今日入住概览 |
+
+> 原有 `PUT /{roomId}/status` 保留不动。
+
+---
+
+## 三、实体类字段注释(中等工作量)
+
+设计规范要求 _"每个 Entity 字段必须有 inline 注释"_。当前所有 9 个 Entity 均缺少字段注释。
+
+**涉及文件:**
+- `entity/User.java` — 12 个字段
+- `entity/Pet.java` — 13 个字段
+- `entity/VaccinationRecord.java` — 6 个字段
+- `entity/DewormingRecord.java` — 6 个字段
+- `entity/ServicePackage.java` — 10 个字段
+- `entity/BoardingRoom.java` — 8 个字段
+- `entity/BoardingRoomStatus.java` — 5 个字段
+- `entity/Appointment.java` — 16 个字段
+- `entity/Schedule.java` — 5 个字段
+
+每个字段添加 `// 说明` 行尾注释,参考 `detailDesign.md` §3.1 中的字段描述。
+
+---
+
+## 四、补充测试(中等工作量)
+
+当前 `src/test/` 为空,需要按设计文档第 9 章建立测试:
+
+### 4.1 工具类测试
+- `util/PageUtil` — 分页逻辑
+- `config/PasswordConfig` — MD5 加密/匹配
+
+### 4.2 Service 单元测试(JUnit 5 + Mockito)
+- `AuthServiceImplTest` — 注册唯一性校验、登录密码校验、session 操作
+- `AppointmentServiceImplTest` — 创建预约(寄养/洗护)、房间可用性校验、状态转换、取消逻辑、审核逻辑
+
+### 4.3 Controller 集成测试(MockMvc)
+- 认证接口:注册/登录/登出/未登录返回 401
+- 预约接口:完整流程(创建→支付→审核→取消→处理取消)
+- 权限校验:普通用户访问 /admin/** 返回 403
+
+### 4.4 Mapper 测试(可选)
+- SQL 语句正确性校验(可在集成测试中覆盖)
+
+---
+
+## 五、构建验证
+
+```bash
+./mvnw clean package
+```
+
+确保编译通过、测试通过。
+
+---
+
+## 六、优先级与执行顺序建议
+
+| 优先级 | 任务 | 预估工时 | 说明 |
+|--------|------|---------|------|
+| P0 | 修复取消订单 Bug | ~30min | 影响核心业务流程 |
+| P0 | 补充 AdminBoardingRoom 管理功能 | ~3h | 设计文档承诺的功能缺失较大 |
+| P1 | 补充 Entity 字段注释 | ~30min | 编码规范要求 |
+| P1 | 构建验证 + 测试 | ~4h | 保证质量、可维护性 |
+
+---
+
+## 七、当前代码中值得注意的设计决策
+
+| 项目 | 说明 |
+|------|------|
+| Session 认证 | 基于服务端 HttpSession + Cookie,未使用 JWT |
+| 上下文路径 | `server.servlet.context-path: /`(无 `/api` 前缀,与设计文档描述一致) |
+| 密码加密 | MD5 + 固定盐值 "PetService2026!@#" |
+| 房间状态 upsert | 使用 `INSERT ... ON DUPLICATE KEY UPDATE` |
+| 预约状态机 | PENDING → CONFIRMED → CANCEL_REQUESTED → CANCELLED / COMPLETED |
+| 管理员拦截 | AuthInterceptor 先校验登录,AdminInterceptor 后校验角色 |

+ 141 - 0
work.md

@@ -0,0 +1,141 @@
+# 开发任务规划(基于 serverDesign.md)
+
+> 项目当前状态:仅有 `ServerApplication.java` 和 `application.yaml`,其余代码均需新建。
+
+---
+
+## 阶段一:公共基础设施层
+
+| # | 任务 | 涉及文件 | 说明 |
+|---|------|---------|------|
+| 1.1 | 统一响应类 | `common/R.java` | `code` + `message` + `data`,提供 `ok/error` 静态工厂 |
+| 1.2 | 错误码枚举 | `common/ResultCode.java` | 定义业务状态码常量 |
+| 1.3 | 业务异常 | `common/BusinessException.java` | 继承 `RuntimeException`,携带 `code` |
+| 1.4 | 全局异常处理 | `config/GlobalExceptionHandler.java` | 覆盖文档第 3.1.3 节列出的所有异常类型 |
+| 1.5 | 跨域配置 | `config/CorsConfig.java` | 允许所有来源(开发阶段),`allowCredentials(true)` |
+| 1.6 | 拦截器注册 | `config/WebMvcConfig.java` | 注册 AuthInterceptor 和 AdminInterceptor |
+| 1.7 | MD5 密码工具 | `config/PasswordConfig.java` | MD5 加盐加密工具 |
+| 1.8 | 分页 VO | `common/PageVo.java` | `data` + `total` + `pageNum` + `pageSize` |
+| 1.9 | 分页工具 | `util/PageUtil.java` | 封装 LIMIT 分页逻辑(`Supplier` 方式) |
+| 1.10 | 登录拦截器 | `interceptor/AuthInterceptor.java` | 从 Session 获取用户,抛 `UnauthorizedException` |
+| 1.11 | 管理员拦截器 | `interceptor/AdminInterceptor.java` | 校验 `role=admin`,抛 `ForbiddenException` |
+
+---
+
+## 阶段二:实体类(Entity)
+
+| # | 任务 | 文件 | 关键字段 |
+|---|------|------|---------|
+| 2.1 | 用户实体 | `entity/User.java` | id, account, username, password, email, phone, avatar, role, enable, createdAt, updatedAt |
+| 2.2 | 宠物实体 | `entity/Pet.java` | id, userId, name, species, breed, gender, birthDate, weight, avatar, remark, createdAt, updatedAt |
+| 2.3 | 疫苗记录 | `entity/VaccinationRecord.java` | id, petId, vaccineName, vaccineDate, hospital, doctor, remark, createdAt |
+| 2.4 | 驱虫记录 | `entity/DewormingRecord.java` | id, petId, dewormingName, dewormingDate, hospital, doctor, remark, createdAt |
+| 2.5 | 套餐实体 | `entity/ServicePackage.java` | id, name, type(BOARDING/GROOMING), price, duration, description, isActive, createdAt, updatedAt |
+| 2.6 | 寄养房型 | `entity/BoardingRoom.java` | id, roomName, roomType, capacity, pricePerDay, isActive, description, createdAt, updatedAt |
+| 2.7 | 房态记录 | `entity/BoardingRoomStatus.java` | id, roomId, date, status(AVAILABLE/OCCUPIED/MAINTENANCE), appointmentId, createdAt |
+| 2.8 | 预约订单 | `entity/Appointment.java` | id, userId, petId, packageId, roomId, type, status, startDate, endDate, totalPrice, deposit, depositPaid, cancelReason, rejectReason, remark, createdAt, updatedAt |
+| 2.9 | 排班 | `entity/Schedule.java` | id, employeeName, workDate, timeSlot, remark, createdAt |
+
+---
+
+## 阶段三:Mapper 接口 + MyBatis XML
+
+| # | 任务 | Mapper 接口 | XML 文件 | 关键 SQL |
+|---|------|------------|----------|---------|
+| 3.1 | 用户 Mapper | `mapper/UserMapper.java` | `UserMapper.xml` | insert/selectById/selectByAccount/update/delete, selectByPage, countByCondition |
+| 3.2 | 宠物 Mapper | `mapper/PetMapper.java` | `PetMapper.xml` | insert/selectById/selectByUserId/update/delete |
+| 3.3 | 疫苗 Mapper | `mapper/VaccinationRecordMapper.java` | `VaccinationRecordMapper.xml` | insert/selectByPetId/deleteByPetId |
+| 3.4 | 驱虫 Mapper | `mapper/DewormingRecordMapper.java` | `DewormingRecordMapper.xml` | insert/selectByPetId/deleteByPetId |
+| 3.5 | 套餐 Mapper | `mapper/PackageMapper.java` | `PackageMapper.xml` | insert/selectById/selectByType/update/isActive 逻辑删除 |
+| 3.6 | 房型 Mapper | `mapper/BoardingRoomMapper.java` | `BoardingRoomMapper.xml` | insert/selectById/selectActive/update |
+| 3.7 | 房态 Mapper | `mapper/BoardingRoomStatusMapper.java` | `BoardingRoomStatusMapper.xml` | insertOrUpdate(ON DUPLICATE KEY)/selectByRoomAndDate/deleteByAppointment |
+| 3.8 | 预约 Mapper | `mapper/AppointmentMapper.java` | `AppointmentMapper.xml` | insert/selectById/selectByUserId/selectAllByPage, updateStatus/updateDepositPaid |
+| 3.9 | 排班 Mapper | `mapper/ScheduleMapper.java` | `ScheduleMapper.xml` | insert/selectByMonth/deleteById, checkConflict |
+
+---
+
+## 阶段四:DTO(请求参数)
+
+| # | 任务 | 文件 |
+|---|------|------|
+| 4.1 | 认证 DTO | `dto/auth/LoginDto.java`, `RegisterDto.java` |
+| 4.2 | 用户 DTO | `dto/user/UserDto.java`, `UpdateUserPasswordDto.java`, `UpdateUserAvatarDto.java`, `UpdateUserStatusDto.java` |
+| 4.3 | 宠物 DTO | `dto/pet/PetDto.java`, `VaccinationDto.java`, `DewormingDto.java` |
+| 4.4 | 预约 DTO | `dto/appointment/AppointmentDto.java`, `ReviewDto.java`, `HandleCancelDto.java` |
+| 4.5 | 套餐 DTO | `dto/servicePackage/PackageDto.java` |
+| 4.6 | 房态 DTO | `dto/room/RoomStatusDto.java`, `QueryByPageParams.java` |
+| 4.7 | 排班 DTO | `dto/schedule/ScheduleDto.java` |
+
+---
+
+## 阶段五:VO(返回结果)
+
+| # | 任务 | 文件 |
+|---|------|------|
+| 5.1 | 用户 VO | `vo/user/UserVo.java` |
+| 5.2 | 宠物 VO | `vo/pet/PetVo.java`, `VaccinationVo.java`, `DewormingVo.java` |
+| 5.3 | 预约 VO | `vo/appointment/AppointmentVo.java` |
+| 5.4 | 套餐 VO | `vo/servicePackage/PackageVo.java` |
+| 5.5 | 房型/VO | `vo/room/BoardingRoomVo.java`, `RoomStatusVo.java` |
+| 5.6 | 排班 VO | `vo/schedule/ScheduleVo.java` |
+| 5.7 | 管理员用户详情 VO | `vo/user/AdminUserDetailVo.java` |
+
+---
+
+## 阶段六:Service 层(接口 + 实现)
+
+| # | 任务 | 接口 | 实现 | 关键逻辑 |
+|---|------|------|------|---------|
+| 6.1 | 认证服务 | `AuthService` | `AuthServiceImpl` | 注册(唯一性校验+MD5+session)、登录(密码校验+session)、logout |
+| 6.2 | 用户服务 | `UserService` | `UserServiceImpl` | 分页、CRUD、updatePassword/updateStatus/updateAvatar |
+| 6.3 | 宠物服务 | `PetService` | `PetServiceImpl` | 当前用户宠物列表、CRUD(校验归属)、疫苗/驱虫子记录 |
+| 6.4 | 预约服务 | `AppointmentService` | `AppointmentServiceImpl` | 创建(校验套餐/宠物/房态+计算价格)、支付定金、取消、审核、处理取消(含事务) |
+| 6.5 | 房型服务 | `BoardingRoomService` | `BoardingRoomServiceImpl` | 房型列表(active)、按月房态查询、手动修改房态 |
+| 6.6 | 套餐服务 | `PackageService` | `PackageServiceImpl` | 按类型查询(active)、CRUD(逻辑删除) |
+| 6.7 | 排班服务 | `ScheduleService` | `ScheduleServiceImpl` | 按月查询、新增(冲突校验)、删除 |
+
+---
+
+## 阶段七:Controller 层
+
+| # | 任务 | 文件 | 说明 |
+|---|------|------|------|
+| 7.1 | 认证控制器 | `controller/AuthController.java` | `/auth/register`, `/auth/login`, `/auth/logout` |
+| 7.2 | 用户控制器 | `controller/UserController.java` | `/user/**` 分页/CRUD/状态/密码/头像 |
+| 7.3 | 宠物控制器 | `controller/PetController.java` | `/pets/**` 宠物 CRUD + 疫苗/驱虫子接口 |
+| 7.4 | 预约控制器 | `controller/AppointmentController.java` | `/appointments/**` 创建/列表/详情/取消/支付 |
+| 7.5 | 房型控制器 | `controller/BoardingRoomController.java` | `/boarding-rooms/**` 房型列表 + 房态查询 |
+| 7.6 | 套餐控制器 | `controller/PackageController.java` | `/packages/**` 列表 + 详情 |
+| 7.7 | 排班视图 | `controller/ScheduleController.java` | `/schedules/**`(排班查询,用户侧) |
+| 7.8 | 管理员-预约 | `controller/admin/AdminAppointmentController.java` | `/admin/appointments` 审核 + 处理取消 |
+| 7.9 | 管理员-房态 | `controller/admin/AdminBoardingRoomController.java` | `/admin/boarding-rooms` 修改房态 |
+| 7.10 | 管理员-排班 | `controller/admin/AdminScheduleController.java` | `/admin/schedules` 排班 CRUD |
+| 7.11 | 管理员-套餐 | `controller/admin/AdminPackageController.java` | `/admin/packages` 套餐 CRUD |
+| 7.12 | 管理员-用户 | `controller/admin/AdminUserController.java` | `/admin/users` 用户列表 + 详情(含宠物档案) |
+
+---
+
+## 阶段八:测试
+
+| # | 任务 | 说明 |
+|---|------|------|
+| 8.1 | Service 单元测试 | 核心逻辑(AuthService、AppointmentService 等)JUnit 5 + Mockito |
+| 8.2 | 构建验证 | `./mvnw clean package` 确保编译通过 |
+
+---
+
+## 阶段九:最终检查
+
+- [x] 所有 Controller 添加 `@Operation` OpenAPI 注解
+- [x] 所有 DTO 添加 `@Valid` 校验注解
+- [x] 所有 Service 方法添加 `@Transactional(rollbackFor = Exception.class)`(写操作)
+- [ ] 所有 Entity 字段添加 inline 注释(待补充)
+- [x] 全局异常处理覆盖所有预期异常
+- [x] 验证分页接口实现正确
+- [x] `application.yaml` 与文档第 8 节一致
+
+## 设计变更记录
+
+1. `entity/Package.java` → `entity/ServicePackage.java`(`Package` 与 `java.lang.Package` 冲突)
+2. `dto/package/` → `dto/servicePackage/`(`package` 为 Java 关键字,不可用作包名)
+3. `vo/package/` → `vo/servicePackage/`(同上)