# AGENTS.md This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. ## 项目概览 零食商城(毕业设计)— 前后端分离的电商平台,**SpringBoot 4 + Vue 3** 单体多模块仓库: | 目录 | 角色 | 技术栈 | |------|------|--------| | `server-snack/` | 后端 API | Spring Boot 4.0.6、Java 21、MyBatis-Plus 3.5、Sa-Token 1.45、Redis、MySQL 8、Knife4j 4.6 | | `admin-snack/` | 管理后台前端 | Vue 3.5 + Vite 6 + TypeScript 5 + Element Plus 2.9 + Pinia 2 + ECharts 5 | | `db/` | 数据库脚本与设计文档 | `schema.sql`(建库 + 21 张表 DDL)、`seed.sql`(演示数据)、`BUSINESS_DESIGN.md` | | `功能设计文档.md` | 完整功能与接口设计(仓库根) | v1.2,覆盖用户端/管理端/客服/公告/优惠券/抢购 6 大模块 | > 用户端 `web-snack` 模块当前未在本仓库中(设计文档中描述),当前实际前端仅有 `admin-snack` 管理端。 ## 常用命令 ### 后端(`server-snack/`) ```bash # 构建(首次会下载依赖,可能需要较长时间) mvn clean package # 跳过测试运行 mvn spring-boot:run # 单独跑测试 mvn test mvn test -Dtest=类名#方法名 # 单个测试方法 # 开发工具热重启 mvn spring-boot:run -Dspring-boot.run.fork=true ``` - 启动类:`com.snack.server.ServerSnackApplication` - 监听端口:**8080** - 接口文档(Knife4j):`http://localhost:8080/doc.html` - MySQL 默认连接:`jdbc:mysql://localhost:3306/snack_mall`(账号 `root` / `123456`,见 `application.yml`) - Redis 默认:`localhost:6379` - **本地上传文件** 存储到 `server-snack/uploads/`,通过 `/uploads/**` 暴露为静态资源 ### 数据库(`db/`) ```bash mysql -u root -p < schema.sql # 建库 + 21 张表 mysql -u root -p < seed.sql # 演示数据(管理员 admin/123456,user001~user005/123456) ``` ### 管理端(`admin-snack/`) ```bash # 要求 Node ≥ 20 npm install npm run dev # 开发服务器(http://localhost:5173,已配置 /api 和 /uploads 代理到 8080) npm run build # 类型检查 + 生产构建(默认 mode) npm run build:test # 测试环境构建 npm run build:prod # 生产环境构建 npm run type-check # vue-tsc 类型检查 npm run lint # ESLint --fix npm run format # Prettier 格式化 ``` > 仓库内 `pnpm-lock.yaml` 已存在,但 `package.json` 脚本未区分包管理器;如本机用 pnpm,命令相同(`pnpm dev` 等)。 > 项目内默认账号 `admin` / `123456`(管理端登录页 + 数据库 seed 同步)。 ## 架构与约定 ### 后端分层 每个业务模块位于 `server-snack/src/main/java/com/snack/server/module//`,统一包结构: ``` module// ├── controller/ # @RestController,按"用户端 / 管理端"分文件(如 ProductPublicController / ProductAdminController) ├── service/ # 业务接口 │ └── impl/ # 业务实现 ├── mapper/ # MyBatis-Plus BaseMapper ├── entity/ # 数据库表实体(含 createTime / updateTime,会被 MetaObjectHandler 自动填充) ├── dto/req # 入参(带 jakarta.validation 注解) ├── vo/ # 出参(视图对象) ├── enums/ # 模块内枚举 └── constant/ # 模块内常量 ``` 跨模块共享放在 `com.snack.server.{common,config,constant,exception,handler,utils,enums,websocket}`。 ### 关键技术点 1. **认证 — Sa-Token(注意不是 JWT)** - 用户端与管理端走**两套登录体系**(`LoginType.USER` / `LoginType.ADMIN`),通过 `SaRouter.match` 区分 - Token 通过 `Authorization` 请求头传递(前端 `Bearer ${token}`) - 拦截器在 `config/SaTokenConfig.java` 集中维护,按 URL 模式做登录校验 - 业务异常(401/403)由 `GlobalExceptionHandler` 统一转 `Result` 2. **统一响应 — `Result`** - 结构:`{ code, message, data }`,封装在 `common/Result.java` - 业务码集中在 `common/ResultCode.java`(200/400/401/403/404/500 + 1xxx 业务码) - **前端 axios 拦截器在 `code === 200` 时直接返回 `data`**(`utils/request.ts`)— 这是已解包约定,调用方拿到的就是业务数据 3. **MyBatis-Plus 约定** - 逻辑删除字段:`deleted`(0 未删 / 1 已删),全局生效 - 主键自增:`id-type: auto` - 实体类继承 `com.baomidou.mybatisplus.annotation.*`,自动填充 `createTime` / `updateTime`(`MybatisPlusMetaObjectHandler`) - 复杂 SQL 走 XML:`src/main/resources/com/snack/server/module//mapper/*.xml` - **建表 DDL 与 `entity` 字段必须保持一致**,新增字段要同步两边 4. **Redis 键命名**(`constant/RedisKey.java`) - 抢购库存:`seckill:stock:{activityId}:{productId}` - 用户抢购记录(限购):`seckill:user:{userId}:{activityId}:{productId}` - 客服在线用户集合:`chat:online:users` - 会话未读数:`chat:unread:{sessionId}` - 验证码:`captcha:{uuid}` 5. **WebSocket 客服模块**(`websocket/` 当前为占位 `gitkeep`,`BUSINESS_DESIGN.md` 有详细方案) - Spring WebSocket + STOMP;握手时验证 JWT - 消息全部落库 `chat_message`,离线消息不丢 - 用户端用 `stomp.js`;管理端会话页在 `views/chat/` 6. **防超卖**(抢购核心) - Redis `DECR` 原子扣减库存 → 失败直接返回"已抢完" - 写入异步队列 → 消费端落 MySQL(详见 `功能设计文档.md` 3.6.2) 7. **接口规范** - 用户端路径:`/api/<资源>`(例:`/api/cart`、`/api/orders/{id}/pay`) - 管理端路径:`/api/admin/**`(登录、验证码、文件上传除外) - RESTful:列表 `GET`、创建 `POST`、详情 `GET {id}`、删除 `DELETE {id}` ### 前端约定(`admin-snack/`) 1. **路径别名**:`@` → `src/`(Vite + tsconfig 双侧配置) 2. **自动导入**(`unplugin-auto-import` + `unplugin-vue-components`) - 自动导入 Vue / Vue Router / Pinia API、Element Plus 组件 - 类型声明由插件生成到 `src/types/auto-imports.d.ts` 和 `src/types/components.d.ts` - 业务文件中**不要**手动 `import { ref, computed } from 'vue'` — 插件已处理 3. **状态管理 — Pinia + 持久化** - `stores/modules/user.ts` 持久化 key 为 `snack-admin-user` - 用 Composition API 写法(`defineStore('user', () => { ... })`),不是 Options API 4. **请求层**(`utils/request.ts`) - axios 实例,baseURL 取 `VITE_API_BASE_URL`(`.env.development` 默认 `http://localhost:8080`) - **响应拦截器已解包**:`code === 200` 时直接 `return data`,调用方拿到的就是业务数据 - 401 自动清空登录态并跳 `/login` - 所有 API 文件(`api/*.ts`)用 `request(config)` 二次封装 5. **路由**(`router/`) - `index.ts`:基础路由(login、404、仪表盘占位) - `async-routes.ts`:业务路由清单(与 `index.ts` 同步维护,权限动态挂载用) - 路由守卫在 `src/permission.ts`,白名单 `['/login', '/404']` - 默认页签布局:`layouts/BasicLayout.vue`(侧栏 + 顶栏 + TabsNav) 6. **样式** - SCSS 全局变量由 `vite.config.ts` 的 `additionalData` 自动注入 `@use "@/styles/variables.scss" as *;` - 业务组件中**直接使用变量**即可,不要重新 import - 设计令牌:主色 `#1E40AF` / `#3B82F6`,圆角 6/10px,背景 `#F8FAFC`(详见 `admin-snack/README.md`) 7. **Mock** - `src/mock/index.ts` 启动时拦截 API 返回本地假数据(基于 `mockjs` + `axios-mock-adapter`) - **联调后端时注释掉 `main.ts` 里的 `import './mock'` 即可关闭** 8. **设计系统**:Minimalism · Data-Dense Dashboard,状态色 success `#10B981` / warning `#F59E0B` / danger `#EF4444`,圆角与阴影三档(`admin-snack/README.md` 有完整表) ### 全局约定 - **不要 commit 真实密钥**:`.env.*.local`、`server-snack/application-local.yml`、`*.pem/*.key/*.crt` 已被 `.gitignore` 排除 - **Java 版本**:21(`21`) - **Node 版本**:≥ 20 - **包管理器**:管理端 `pnpm-lock.yaml` 已存在 - **Maven 仓库**:内置了华为云、阿里云镜像加速 - **不需要写** README、通用开发规范、显而易见的"添加注释"等指令 ## 关键文档 | 文档 | 路径 | 何时读 | |------|------|--------| | 功能设计总览 | `功能设计文档.md` | 任何新模块开发前 | | 业务核心设计(WebSocket + 防超卖) | `db/BUSINESS_DESIGN.md` | 客服/抢购相关开发前 | | 数据库表结构 | `db/schema.sql` + `db/README.md` | 新增/修改实体前 | | 管理端技术栈与命令 | `admin-snack/README.md` | 前端环境问题排查 | ## 调试与排错 - **后端启动失败**:先检查 MySQL/Redis 是否启动、端口是否被占用、knife4j 文档能否打开 - **前端 401 风暴**:检查 `VITE_API_BASE_URL`、浏览器是否带了 `Authorization` 头、Sa-Token 拦截器路径配置 - **抢购超卖**:先看 Redis 中 `seckill:stock:*` 键值,再用 `BUSINESS_DESIGN.md` 对照流程 - **类型错误**:运行 `npm run type-check`(管理端),不要绕过 `vue-tsc` 直接 build - **本地上传文件访问不到**:确认 `WebMvcConfig.addResourceHandlers` 中 `/uploads/**` → `file:./uploads/` 配置,并查看 `snack.upload.domain` 是否正确