From 867c024c3418c8a7a943e3b88cf1884fed216e38 Mon Sep 17 00:00:00 2001 From: FXR-c <427808324@qq.com> Date: Wed, 22 Jul 2026 18:00:13 +0800 Subject: [PATCH 01/15] frat: --- .../0722\346\227\245\345\277\227.md" | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 "reports/daily/\346\226\271\344\275\263\347\272\257/0722\346\227\245\345\277\227.md" diff --git "a/reports/daily/\346\226\271\344\275\263\347\272\257/0722\346\227\245\345\277\227.md" "b/reports/daily/\346\226\271\344\275\263\347\272\257/0722\346\227\245\345\277\227.md" new file mode 100644 index 0000000..6a727d9 --- /dev/null +++ "b/reports/daily/\346\226\271\344\275\263\347\272\257/0722\346\227\245\345\277\227.md" @@ -0,0 +1,2 @@ +今日创建相关新分支; +了解并确定自己负责的功能模块 \ No newline at end of file -- Gitee From 579bb0fbeaaf339d29e9e099d7796a56439399fb Mon Sep 17 00:00:00 2001 From: FXR-c <427808324@qq.com> Date: Wed, 22 Jul 2026 10:02:37 +0000 Subject: [PATCH 02/15] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20re?= =?UTF-8?q?ports/daily/0722=E6=97=A5=E5=BF=97.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "reports/daily/0722\346\227\245\345\277\227.md" | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 "reports/daily/0722\346\227\245\345\277\227.md" diff --git "a/reports/daily/0722\346\227\245\345\277\227.md" "b/reports/daily/0722\346\227\245\345\277\227.md" deleted file mode 100644 index 6a727d9..0000000 --- "a/reports/daily/0722\346\227\245\345\277\227.md" +++ /dev/null @@ -1,2 +0,0 @@ -今日创建相关新分支; -了解并确定自己负责的功能模块 \ No newline at end of file -- Gitee From 3822b77d18013fec658bf9298fe5b69df4020621 Mon Sep 17 00:00:00 2001 From: FXR-c <427808324@qq.com> Date: Wed, 22 Jul 2026 10:50:13 +0000 Subject: [PATCH 03/15] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20re?= =?UTF-8?q?ports/daily/=E6=96=B9=E4=BD=B3=E7=BA=AF/0722=E6=97=A5=E5=BF=97.?= =?UTF-8?q?md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0722\346\227\245\345\277\227.md" | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 "reports/daily/\346\226\271\344\275\263\347\272\257/0722\346\227\245\345\277\227.md" diff --git "a/reports/daily/\346\226\271\344\275\263\347\272\257/0722\346\227\245\345\277\227.md" "b/reports/daily/\346\226\271\344\275\263\347\272\257/0722\346\227\245\345\277\227.md" deleted file mode 100644 index 6a727d9..0000000 --- "a/reports/daily/\346\226\271\344\275\263\347\272\257/0722\346\227\245\345\277\227.md" +++ /dev/null @@ -1,2 +0,0 @@ -今日创建相关新分支; -了解并确定自己负责的功能模块 \ No newline at end of file -- Gitee From e15f211314c0a176f3326de55dd93c2192fb758e Mon Sep 17 00:00:00 2001 From: wuh <3422047492@qq.com> Date: Wed, 22 Jul 2026 15:26:20 +0800 Subject: [PATCH 04/15] chore: add csharp project setup and planning docs --- .env.example | 12 + .gitignore | 28 + AGENT.md | 301 +++ README.md | 84 +- backend/Dockerfile | 19 + backend/Eshop.slnx | 8 + backend/README.md | 47 + backend/sql/data.sql | 52 + backend/sql/schema.sql | 117 + .../Eshop.Api/Controllers/AdminController.cs | 22 + .../Eshop.Api/Controllers/AuthController.cs | 34 + .../Eshop.Api/Controllers/CartController.cs | 33 + .../Eshop.Api/Controllers/HealthController.cs | 20 + .../Eshop.Api/Controllers/OrdersController.cs | 42 + .../Controllers/ProductsController.cs | 34 + backend/src/Eshop.Api/Eshop.Api.csproj | 18 + backend/src/Eshop.Api/Eshop.Api.http | 10 + .../Middleware/ExceptionHandlingMiddleware.cs | 35 + backend/src/Eshop.Api/Program.cs | 36 + .../Eshop.Api/Properties/launchSettings.json | 23 + .../Eshop.Api/appsettings.Development.json | 11 + .../Eshop.Api/appsettings.Local.example.json | 11 + backend/src/Eshop.Api/appsettings.json | 24 + .../Eshop.Application/Common/ApiResponse.cs | 14 + .../Eshop.Application/Common/PagedResult.cs | 3 + .../Eshop.Application.csproj | 13 + backend/src/Eshop.Domain/Entities/Address.cs | 16 + backend/src/Eshop.Domain/Entities/CartItem.cs | 12 + backend/src/Eshop.Domain/Entities/Category.cs | 10 + backend/src/Eshop.Domain/Entities/Order.cs | 18 + .../src/Eshop.Domain/Entities/OrderItem.cs | 12 + backend/src/Eshop.Domain/Entities/Payment.cs | 15 + backend/src/Eshop.Domain/Entities/Product.cs | 17 + .../src/Eshop.Domain/Entities/UserAccount.cs | 15 + backend/src/Eshop.Domain/Enums/OrderStatus.cs | 10 + .../src/Eshop.Domain/Enums/PaymentStatus.cs | 9 + .../src/Eshop.Domain/Enums/ProductStatus.cs | 7 + backend/src/Eshop.Domain/Enums/UserRole.cs | 7 + backend/src/Eshop.Domain/Enums/UserStatus.cs | 7 + backend/src/Eshop.Domain/Eshop.Domain.csproj | 9 + .../Data/DatabaseOptions.cs | 6 + .../Eshop.Infrastructure.csproj | 14 + docker-compose.yml | 38 + ...74\350\257\264\346\230\216\344\271\246.md" | 337 ++- ...37\345\236\213\350\256\276\350\256\241.md" | 351 +++ ...45\345\217\243\350\256\276\350\256\241.md" | 558 ++++- ...56\345\272\223\350\256\276\350\256\241.md" | 293 ++- ...66\346\236\204\350\256\276\350\256\241.md" | 206 +- ...06\345\267\245\350\256\241\345\210\222.md" | 130 + ...14\346\224\266\345\207\206\345\244\207.md" | 236 ++ ...06\346\224\257\350\247\204\345\210\222.md" | 161 ++ .../README.md" | 30 + ...36\347\216\260\350\257\264\346\230\216.md" | 25 + ...36\347\216\260\350\257\264\346\230\216.md" | 26 + ...64\346\230\216\346\250\241\346\235\277.md" | 59 + ...36\347\216\260\350\257\264\346\230\216.md" | 25 + ...36\347\216\260\350\257\264\346\230\216.md" | 26 + ...36\347\216\260\350\257\264\346\230\216.md" | 25 + ...36\347\216\260\350\257\264\346\230\216.md" | 25 + frontend/.env.example | 1 + frontend/index.html | 12 + frontend/package-lock.json | 2192 +++++++++++++++++ frontend/package.json | 25 + frontend/src/App.vue | 17 + frontend/src/api/http.ts | 23 + frontend/src/api/products.ts | 14 + frontend/src/env.d.ts | 8 + frontend/src/main.ts | 13 + frontend/src/router/index.ts | 21 + frontend/src/stores/auth.ts | 19 + frontend/src/styles/main.css | 72 + frontend/src/views/HomeView.vue | 10 + .../src/views/admin/AdminDashboardView.vue | 16 + frontend/src/views/cart/CartView.vue | 6 + frontend/src/views/order/OrderListView.vue | 6 + .../src/views/product/ProductListView.vue | 15 + frontend/src/views/user/LoginView.vue | 26 + frontend/tsconfig.json | 18 + frontend/vite.config.ts | 15 + global.json | 6 + 80 files changed, 6157 insertions(+), 164 deletions(-) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 AGENT.md create mode 100644 backend/Dockerfile create mode 100644 backend/Eshop.slnx create mode 100644 backend/README.md create mode 100644 backend/sql/data.sql create mode 100644 backend/sql/schema.sql create mode 100644 backend/src/Eshop.Api/Controllers/AdminController.cs create mode 100644 backend/src/Eshop.Api/Controllers/AuthController.cs create mode 100644 backend/src/Eshop.Api/Controllers/CartController.cs create mode 100644 backend/src/Eshop.Api/Controllers/HealthController.cs create mode 100644 backend/src/Eshop.Api/Controllers/OrdersController.cs create mode 100644 backend/src/Eshop.Api/Controllers/ProductsController.cs create mode 100644 backend/src/Eshop.Api/Eshop.Api.csproj create mode 100644 backend/src/Eshop.Api/Eshop.Api.http create mode 100644 backend/src/Eshop.Api/Middleware/ExceptionHandlingMiddleware.cs create mode 100644 backend/src/Eshop.Api/Program.cs create mode 100644 backend/src/Eshop.Api/Properties/launchSettings.json create mode 100644 backend/src/Eshop.Api/appsettings.Development.json create mode 100644 backend/src/Eshop.Api/appsettings.Local.example.json create mode 100644 backend/src/Eshop.Api/appsettings.json create mode 100644 backend/src/Eshop.Application/Common/ApiResponse.cs create mode 100644 backend/src/Eshop.Application/Common/PagedResult.cs create mode 100644 backend/src/Eshop.Application/Eshop.Application.csproj create mode 100644 backend/src/Eshop.Domain/Entities/Address.cs create mode 100644 backend/src/Eshop.Domain/Entities/CartItem.cs create mode 100644 backend/src/Eshop.Domain/Entities/Category.cs create mode 100644 backend/src/Eshop.Domain/Entities/Order.cs create mode 100644 backend/src/Eshop.Domain/Entities/OrderItem.cs create mode 100644 backend/src/Eshop.Domain/Entities/Payment.cs create mode 100644 backend/src/Eshop.Domain/Entities/Product.cs create mode 100644 backend/src/Eshop.Domain/Entities/UserAccount.cs create mode 100644 backend/src/Eshop.Domain/Enums/OrderStatus.cs create mode 100644 backend/src/Eshop.Domain/Enums/PaymentStatus.cs create mode 100644 backend/src/Eshop.Domain/Enums/ProductStatus.cs create mode 100644 backend/src/Eshop.Domain/Enums/UserRole.cs create mode 100644 backend/src/Eshop.Domain/Enums/UserStatus.cs create mode 100644 backend/src/Eshop.Domain/Eshop.Domain.csproj create mode 100644 backend/src/Eshop.Infrastructure/Data/DatabaseOptions.cs create mode 100644 backend/src/Eshop.Infrastructure/Eshop.Infrastructure.csproj create mode 100644 docker-compose.yml create mode 100644 "docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\345\216\237\345\236\213\350\256\276\350\256\241.md" create mode 100644 "docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/10\344\270\252\345\267\245\344\275\234\346\227\245\345\274\200\345\217\221\345\210\206\345\267\245\350\256\241\345\210\222.md" create mode 100644 "docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/6\344\272\272\345\210\206\345\267\245\344\270\216\351\252\214\346\224\266\345\207\206\345\244\207.md" create mode 100644 "docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/\346\210\220\345\221\230\345\274\200\345\217\221\345\210\206\346\224\257\350\247\204\345\210\222.md" create mode 100644 "docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/README.md" create mode 100644 "docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\345\220\216\345\217\260\347\256\241\347\220\206\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" create mode 100644 "docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\345\225\206\345\223\201\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" create mode 100644 "docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216\346\250\241\346\235\277.md" create mode 100644 "docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\346\265\213\350\257\225\351\203\250\347\275\262\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" create mode 100644 "docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\347\224\250\346\210\267\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" create mode 100644 "docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\350\256\242\345\215\225\346\224\257\344\273\230\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" create mode 100644 "docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\350\264\255\347\211\251\350\275\246\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" create mode 100644 frontend/.env.example create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/src/App.vue create mode 100644 frontend/src/api/http.ts create mode 100644 frontend/src/api/products.ts create mode 100644 frontend/src/env.d.ts create mode 100644 frontend/src/main.ts create mode 100644 frontend/src/router/index.ts create mode 100644 frontend/src/stores/auth.ts create mode 100644 frontend/src/styles/main.css create mode 100644 frontend/src/views/HomeView.vue create mode 100644 frontend/src/views/admin/AdminDashboardView.vue create mode 100644 frontend/src/views/cart/CartView.vue create mode 100644 frontend/src/views/order/OrderListView.vue create mode 100644 frontend/src/views/product/ProductListView.vue create mode 100644 frontend/src/views/user/LoginView.vue create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vite.config.ts create mode 100644 global.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2a61722 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# Frontend +VITE_API_BASE_URL=http://localhost:5081/api + +# Backend / MySQL +MYSQL_HOST=localhost +MYSQL_PORT=3306 +MYSQL_DATABASE=eshop_dev +MYSQL_USER=root +MYSQL_PASSWORD=change_me + +# JWT +JWT_SIGNING_KEY=replace-with-at-least-32-random-characters diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..155317a --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# .NET +bin/ +obj/ +.vs/ +*.user +*.suo +TestResults/ + + +# Local configuration +.env +*.local +appsettings.Local.json + +# Frontend +node_modules/ +dist/ +.vite/ + +# IDE +.idea/ +.vscode/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 0000000..b703174 --- /dev/null +++ b/AGENT.md @@ -0,0 +1,301 @@ +# AI Agent 使用规范 + +> 本文件为小组成员使用 AI 工具前的必读规则。 +> 适用对象:所有使用 ChatGPT、Codex、通义、文心、Kimi、Copilot 等 AI 工具参与本项目的成员。 +> 适用范围:需求、设计、前端、后端、数据库、测试、部署、答辩材料等全部项目活动。 + +--- + +## 1. 基本原则 + +### 1.1 AI 是辅助工具,不是代写者 + +- 小组成员可以使用 AI 辅助分析需求、生成代码、检查错误、补充测试用例和整理文档。 +- 提交到仓库的代码和文档,提交人必须能解释清楚:为什么这样写、有哪些关键逻辑、有哪些风险。 +- 答辩时不能以“AI 生成的,我不知道”为理由回避问题。 +- 不允许把自己负责模块整体交给 AI 代写后直接提交。 + +### 1.2 先理解项目,再让 AI 动手 + +使用 AI 前必须先阅读与任务相关的项目文件: + +| 任务类型 | 必读文件 | +|---|---| +| 总体任务、分工、进度 | `README.md`、`docs/00-项目要求/项目要求.md` | +| 需求分析 | `docs/01-需求文档/需求规格说明书.md` | +| 架构、技术栈 | `docs/02-设计文档/系统架构设计.md` | +| 数据库表设计 | `docs/02-设计文档/数据库设计.md` | +| 接口开发 | `docs/02-设计文档/接口设计.md` | +| 测试 | `docs/03-测试文档/测试计划.md`、`docs/03-测试文档/测试报告.md` | +| 部署与答辩 | `docs/05-总结答辩/项目总结报告.md` | + +### 1.3 文档先行 + +- 新增或修改接口前,必须先更新 `docs/02-设计文档/接口设计.md`。 +- 新增或修改数据库表前,必须先更新 `docs/02-设计文档/数据库设计.md`。 +- 新增模块页面前,必须在需求或设计文档中说明该页面入口、功能点和关联接口。 +- 代码实现与文档不一致时,以最新评审通过的文档为准。 + +--- + +## 2. AI 使用流程 + +### 2.1 任务开始前 + +向 AI 提问时,应说明以下信息: + +- 当前负责的模块,例如用户、商品、购物车、订单支付、后台管理、测试部署。 +- 使用的技术栈,例如 Vue、ASP.NET Core Web API、MySQL、EF Core。 +- 需要修改或生成的文件路径。 +- 已有接口、表结构或页面约定。 +- 希望 AI 输出代码、文档、检查意见还是排错步骤。 + +推荐提问格式: + +```text +我负责商品模块,技术栈是 Vue 3 + ASP.NET Core Web API + MySQL + EF Core。 +请根据 docs/02-设计文档/接口设计.md 中的商品接口,帮我生成 ProductController、ProductService、EF Core 查询和相关 DTO。 +要求不要改公共鉴权代码,不要改其他模块文件,并说明需要补哪些测试。 +``` + +### 2.2 任务执行中 + +- 每次只让 AI 处理一个明确任务,避免一次生成过多无法检查的代码。 +- 不让 AI 随意重构全项目结构。 +- AI 修改公共文件前,必须先和组长或相关模块负责人确认。 +- AI 生成 SQL、迁移脚本、删除脚本、部署脚本时,必须人工逐行检查。 +- AI 生成的代码必须本地运行、调试或测试通过后才能提交。 + +### 2.3 任务完成后 + +每次使用 AI 完成一个功能点后,提交人必须检查: + +- 是否符合需求文档。 +- 是否符合接口文档。 +- 是否符合数据库设计。 +- 是否存在硬编码账号、密码、Token、连接串。 +- 是否影响其他成员模块。 +- 是否补充必要的说明文档、测试用例或日报记录。 + +--- + +## 3. 模块技术说明文档要求 + +### 3.1 每个模块必须生成一份 `.md` + +制作任何业务模块时,模块负责人必须同步生成一份模块说明文档,建议存放在: + +```text +docs/06-模块实现说明/ +``` + +命名格式: + +```text +模块名-实现说明.md +``` + +示例: + +```text +docs/06-模块实现说明/用户模块-实现说明.md +docs/06-模块实现说明/商品模块-实现说明.md +docs/06-模块实现说明/购物车模块-实现说明.md +docs/06-模块实现说明/订单支付模块-实现说明.md +docs/06-模块实现说明/后台管理模块-实现说明.md +docs/06-模块实现说明/测试部署模块-实现说明.md +``` + +### 3.2 模块说明文档必须回答的问题 + +每份模块说明文档必须至少包含以下三项: + +```markdown +# 模块名-实现说明 + +## 1. 这个模块用了什么技术栈? + +- 前端: +- 后端: +- 数据库: +- 其他工具或中间件: + +## 2. 为什么使用这个技术栈? + +- 选择原因 1: +- 选择原因 2: +- 选择原因 3: + +## 3. 是否还有其他实现方式? + +- 方案 A: +- 方案 B: +- 当前方案与其他方案的对比: +``` + +### 3.3 建议补充内容 + +除上面三个必答问题外,建议继续补充: + +- 模块负责人。 +- 模块功能清单。 +- 涉及的数据表。 +- 涉及的后端接口。 +- 涉及的前端页面。 +- 核心业务流程。 +- 关键代码文件路径。 +- 测试用例或验证方式。 +- 使用 AI 辅助完成了哪些内容。 +- 自己理解并修改了哪些地方。 +- 当前模块还有哪些风险或未完成事项。 + +--- + +## 4. 仓库协作规则 + +### 4.1 分支规则 + +- `main`:只保存稳定、可演示版本。 +- `develop`:日常集成分支。 +- `feature/模块名`:个人模块开发分支。 + +推荐分支: + +```text +feature/user +feature/product +feature/cart +feature/order-payment +feature/admin +feature/test-deploy +``` + +### 4.2 提交规则 + +提交信息使用以下格式: + +```text +: <描述> +``` + +可用类型: + +```text +feat 新功能 +fix 修复问题 +docs 文档修改 +test 测试相关 +refactor 重构 +chore 构建、配置、依赖等杂项 +``` + +示例: + +```text +feat: 添加商品列表接口 +docs: 补充订单支付模块技术说明 +fix: 修复购物车数量更新失败问题 +``` + +### 4.3 避免冲突 + +- 每个成员优先修改自己模块目录下的文件。 +- 公共文件由组长或指定负责人统一维护,例如路由、菜单、统一响应、鉴权、全局异常处理。 +- 数据库总脚本 `backend/sql/schema.sql` 和 `backend/sql/data.sql` 不允许多人同时大改。 +- 修改公共配置前,必须先在小组群说明。 +- 每天开始开发前先拉取最新代码。 +- 提交前先本地构建或运行关键页面。 +- 合并前必须由至少一名其他成员 Review。 + +--- + +## 5. 禁止事项 + +AI 或成员都不得执行以下操作: + +- 不理解代码就提交。 +- 复制其他组代码或文档。 +- 提交真实密码、Token、Cookie、数据库连接串。 +- 为了调试方便关闭登录鉴权、权限校验或密码加密。 +- 将密码明文存储到数据库。 +- 拼接 SQL 字符串执行查询。 +- 直接删除他人代码或覆盖他人未合并工作。 +- 让 AI 批量重写整个项目结构。 +- 运行来源不明的脚本、安装包或依赖。 +- 在没有确认影响范围时执行删除数据库、清空表、强制推送、重置 Git 历史等高危操作。 + +--- + +## 6. 安全与隐私规则 + +- `.env`、`appsettings.Local.json`、数据库密码、JWT 密钥等本地配置不得提交。 +- 示例配置只能使用假账号、假密码、假密钥。 +- 日志和截图中不得出现真实密码、验证码、Token、手机号、邮箱等敏感信息。 +- 用户密码必须使用 BCrypt 等安全哈希方式存储。 +- 后端接口必须做参数校验。 +- 需要登录的接口必须校验 JWT。 +- 管理端接口必须校验管理员权限。 +- 错误信息不得暴露 SQL、服务器路径、连接串或堆栈细节。 + +--- + +## 7. C# 后端项目约定 + +后端默认采用: + +```text +ASP.NET Core Web API + MySQL + EF Core + JWT + Swagger / OpenAPI +``` + +建议遵循: + +- Controller 只负责接收请求、参数校验入口和返回结果。 +- 复杂业务逻辑放到 Service,避免把业务堆在 Controller 中。 +- 数据访问通过 EF Core 或 Repository 完成。 +- DTO 不直接等同于数据库 Entity。 +- 密码注册时使用 BCrypt 加密,登录时校验哈希。 +- 所有接口返回统一响应结构。 +- Swagger / OpenAPI 接口描述必须清晰,方便前端联调。 +- 配置从 `appsettings.json`、`appsettings.Local.json` 或环境变量读取,不在代码中硬编码密码、Token 或连接串。 + +--- + +## 8. 前端项目约定 + +如果前端使用 Vue,默认采用: + +```text +Vue 3 + Vite + TypeScript + Element Plus + Pinia + Axios +``` + +建议遵循: + +- 页面按业务模块划分目录。 +- 接口请求统一放在 `src/api/`。 +- 登录状态统一由 Pinia 管理。 +- 路由权限统一处理,不在每个页面重复判断。 +- 表单必须做前端基础校验,后端仍需再次校验。 +- 管理端页面和前台购物端页面要区分入口和权限。 + +--- + +## 9. AI 生成内容审查清单 + +提交 AI 辅助生成内容前,必须确认: + +- [ ] 我已阅读并理解生成内容。 +- [ ] 代码能在本地运行或通过构建。 +- [ ] 没有提交密钥、密码、Token、真实连接串。 +- [ ] 没有破坏其他成员模块。 +- [ ] 接口、数据库、页面与文档一致。 +- [ ] 新增模块已补充对应 `.md` 说明文档。 +- [ ] 必要测试或验证步骤已经完成。 +- [ ] 提交信息符合规范。 + +--- + +## 10. 变更记录 + +| 版本 | 日期 | 修订人 | 变更内容 | +|---|---|---|---| +| v1.0 | 2026-07-22 | 小组 AI 使用规范 | 初版:规定 AI 使用边界、协作规则、安全要求,并要求每个模块生成技术说明 `.md` | diff --git a/README.md b/README.md index 34626af..0522b38 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,12 @@ | 姓名 | 学号 | 角色 | 负责模块(全栈) | |------|------|------|------------------| -| | | 组长/架构员 | 项目脚手架、登录鉴权、公共组件、集成联调 + 1 个小模块 | +| | | 组长/架构员 | 项目脚手架、登录鉴权、统一响应、公共组件、集成联调 | +| | | 模块负责人 | 用户模块(注册/登录/个人信息/地址 + 后台用户管理) | | | | 模块负责人 | 商品模块(分类/列表/搜索/详情 + 后台商品管理) | -| | | 模块负责人 | 订单 + 支付模块(含后台订单管理) | -| | | 模块负责人 | 用户 + 购物车模块(含后台用户管理) | -| | | 模块负责人 | 测试 + 部署 + 选做功能 | +| | | 模块负责人 | 购物车模块(加购/改数量/删除/结算) | +| | | 模块负责人 | 订单 + 支付模块(下单/订单状态/模拟支付 + 后台订单管理) | +| | | 模块负责人 | 测试 + 部署 + 选做/挑战模块 | > 分工规则详见 `docs/00-项目要求/项目要求.md` 第三节,模块难度权重与考核挂钩。 > 4 人组时,测试与部署职责分摊到各模块负责人,选做功能由认领者负责。 @@ -25,10 +26,15 @@ | 分层 | 技术选型 | 版本 | |------|----------|------| -| 前端 | | | -| 后端 | | | -| 数据库 | | | -| 部署 | | | +| 前端 | Vue 3 + Vite + TypeScript + Element Plus + Pinia + Axios | 见 `frontend/package.json` | +| 后端 | C# + ASP.NET Core Web API | .NET 10 | +| 数据库 | MySQL | 8.x | +| ORM/数据访问 | EF Core(后续接入) | 待定 | +| 鉴权 | JWT | 后续接入 | +| 接口文档 | Swagger / OpenAPI | ASP.NET OpenAPI | +| 配置 | `appsettings.json` + 本地配置模板 | 见 `backend/src/Eshop.Api/appsettings.Local.example.json` | +| 日志 | ASP.NET Core Logging | 内置 | +| 部署 | Docker Compose | 见 `docker-compose.yml` | ## 四、仓库目录结构 @@ -40,12 +46,17 @@ │ ├── 02-设计文档/ # 架构设计、数据库设计、接口设计 │ ├── 03-测试文档/ # 测试计划、测试报告 │ ├── 04-会议记录/ # 小组会议纪要 -│ └── 05-总结答辩/ # 项目总结报告、答辩材料 +│ ├── 05-总结答辩/ # 项目总结报告、答辩材料 +│ └── 06-模块实现说明/ # 每个模块的技术栈说明与实现记录 ├── reports/ # 日报周报 │ ├── daily/ # 日报(每人每天一份) │ └── weekly/ # 周报(每组每周一份,组长汇总) -├── frontend/ # 前端代码(自行创建) -└── backend/ # 后端代码(自行创建) +├── frontend/ # Vue 前端代码 +├── backend/ # ASP.NET Core 后端代码与 SQL +├── AGENT.md # 小组成员使用 AI 前必读规范 +├── .env.example # 环境变量示例 +├── docker-compose.yml # MySQL + ASP.NET Core API 容器编排 +└── global.json # .NET SDK 版本锁定 ``` ## 五、关键时间节点(4 周) @@ -71,3 +82,54 @@ 2. 完成分工表、确定技术栈并填入本文件。 3. 按 `docs/01-需求文档/` 模板开始编写需求文档。 4. 每天下班前提交个人日报到 `reports/daily/`。 + +## 八、本地启动 + +### 后端 + +```bash +cd backend +dotnet restore Eshop.slnx +dotnet build Eshop.slnx +dotnet run --project src/Eshop.Api/Eshop.Api.csproj --urls http://localhost:5081 +``` + +后端健康检查: + +```text +GET http://localhost:5081/api/health +``` + +OpenAPI 地址: + +```text +http://localhost:5081/openapi/v1.json +``` + +### 前端 + +```bash +cd frontend +npm install +npm run dev +``` + +前端默认地址: + +```text +http://localhost:5173 +``` + +### Docker Compose + +```bash +docker compose up --build +``` + +## 九、AI 使用要求 + +全组成员使用 AI 前必须阅读 `AGENT.md`。制作任何模块时,必须在 `docs/06-模块实现说明/` 下维护对应模块说明,回答: + +1. 这个模块用了什么技术栈? +2. 为什么使用这个技术栈? +3. 是否还有其他实现方式? diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..6c092d1 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src + +COPY Eshop.slnx ./ +COPY src/Eshop.Api/Eshop.Api.csproj src/Eshop.Api/ +COPY src/Eshop.Application/Eshop.Application.csproj src/Eshop.Application/ +COPY src/Eshop.Domain/Eshop.Domain.csproj src/Eshop.Domain/ +COPY src/Eshop.Infrastructure/Eshop.Infrastructure.csproj src/Eshop.Infrastructure/ +RUN dotnet restore Eshop.slnx + +COPY . . +RUN dotnet publish src/Eshop.Api/Eshop.Api.csproj -c Release -o /app/publish --no-restore + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime +WORKDIR /app +COPY --from=build /app/publish . + +EXPOSE 8080 +ENTRYPOINT ["dotnet", "Eshop.Api.dll"] diff --git a/backend/Eshop.slnx b/backend/Eshop.slnx new file mode 100644 index 0000000..8d542cd --- /dev/null +++ b/backend/Eshop.slnx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..72e38cc --- /dev/null +++ b/backend/README.md @@ -0,0 +1,47 @@ +# E-Shop Backend + +后端采用 C# / ASP.NET Core Web API 骨架,当前目录用于统一后端工程结构。 + +## 项目结构 + +```text +backend/ + Eshop.slnx + src/ + Eshop.Api/ # HTTP API、Controller、中间件、配置 + Eshop.Application/ # 通用响应、DTO、业务服务接口 + Eshop.Domain/ # 实体、枚举、领域模型 + Eshop.Infrastructure/ # 数据库、仓储、外部服务实现 + sql/ + schema.sql + data.sql +``` + +## 本地启动 + +```bash +cd backend +dotnet restore Eshop.slnx +dotnet build Eshop.slnx +dotnet run --project src/Eshop.Api/Eshop.Api.csproj --urls http://localhost:5081 +``` + +健康检查: + +```text +GET http://localhost:5081/api/health +``` + +OpenAPI: + +```text +http://localhost:5081/openapi/v1.json +``` + +## 开发约定 + +- Controller 只做请求入口、参数接收和响应输出。 +- 复杂业务逻辑放到 Service,后续各模块自行补充。 +- 数据访问后续接入 EF Core + MySQL。 +- 本地密码写入 `appsettings.Local.json`,不要提交到 Git。 +- 新接口先更新 `docs/02-设计文档/接口设计.md`。 diff --git a/backend/sql/data.sql b/backend/sql/data.sql new file mode 100644 index 0000000..55b1d3f --- /dev/null +++ b/backend/sql/data.sql @@ -0,0 +1,52 @@ +USE eshop; + +INSERT INTO category (id, name, sort_order) VALUES + (1, '数码电子', 10), + (2, '生活百货', 20), + (3, '食品饮料', 30) +ON DUPLICATE KEY UPDATE name = VALUES(name), sort_order = VALUES(sort_order); + +INSERT INTO user_account (id, username, password_hash, phone, role, status) VALUES + (1, 'admin', 'TODO_BCRYPT_HASH', '13800000000', 9, 1), + (2, 'buyer', 'TODO_BCRYPT_HASH', '13900000000', 1, 1) +ON DUPLICATE KEY UPDATE phone = VALUES(phone), role = VALUES(role), status = VALUES(status); + +INSERT INTO product (id, category_id, name, description, price, stock, image_url, status) VALUES + (1, 1, '无线蓝牙耳机', '演示商品,请替换为真实描述', 199.00, 80, '/images/products/1.png', 1), + (2, 1, '机械键盘', '演示商品,请替换为真实描述', 299.00, 60, '/images/products/2.png', 1), + (3, 1, '无线鼠标', '演示商品,请替换为真实描述', 89.00, 120, '/images/products/3.png', 1), + (4, 1, '移动电源', '演示商品,请替换为真实描述', 129.00, 100, '/images/products/4.png', 1), + (5, 1, '智能手环', '演示商品,请替换为真实描述', 169.00, 70, '/images/products/5.png', 1), + (6, 1, '平板保护套', '演示商品,请替换为真实描述', 59.00, 150, '/images/products/6.png', 1), + (7, 1, '手机支架', '演示商品,请替换为真实描述', 29.00, 200, '/images/products/7.png', 1), + (8, 1, '高清数据线', '演示商品,请替换为真实描述', 19.90, 300, '/images/products/8.png', 1), + (9, 1, '快充充电器', '演示商品,请替换为真实描述', 69.00, 160, '/images/products/9.png', 1), + (10, 1, '电脑双肩包', '演示商品,请替换为真实描述', 149.00, 90, '/images/products/10.png', 1), + (11, 2, '保温杯', '演示商品,请替换为真实描述', 79.00, 130, '/images/products/11.png', 1), + (12, 2, '收纳箱', '演示商品,请替换为真实描述', 49.00, 180, '/images/products/12.png', 1), + (13, 2, '桌面台灯', '演示商品,请替换为真实描述', 109.00, 85, '/images/products/13.png', 1), + (14, 2, '洗衣液', '演示商品,请替换为真实描述', 39.90, 210, '/images/products/14.png', 1), + (15, 2, '抽纸套装', '演示商品,请替换为真实描述', 32.90, 260, '/images/products/15.png', 1), + (16, 2, '厨房置物架', '演示商品,请替换为真实描述', 89.00, 95, '/images/products/16.png', 1), + (17, 2, '雨伞', '演示商品,请替换为真实描述', 35.00, 160, '/images/products/17.png', 1), + (18, 2, '拖鞋', '演示商品,请替换为真实描述', 25.00, 220, '/images/products/18.png', 1), + (19, 2, '毛巾三件套', '演示商品,请替换为真实描述', 45.00, 140, '/images/products/19.png', 1), + (20, 2, '床上四件套', '演示商品,请替换为真实描述', 199.00, 55, '/images/products/20.png', 1), + (21, 3, '坚果礼盒', '演示商品,请替换为真实描述', 128.00, 100, '/images/products/21.png', 1), + (22, 3, '咖啡豆', '演示商品,请替换为真实描述', 88.00, 75, '/images/products/22.png', 1), + (23, 3, '即饮茶饮', '演示商品,请替换为真实描述', 59.90, 180, '/images/products/23.png', 1), + (24, 3, '早餐麦片', '演示商品,请替换为真实描述', 36.90, 120, '/images/products/24.png', 1), + (25, 3, '牛奶整箱', '演示商品,请替换为真实描述', 69.00, 90, '/images/products/25.png', 1), + (26, 3, '饼干礼盒', '演示商品,请替换为真实描述', 49.90, 130, '/images/products/26.png', 1), + (27, 3, '果汁饮料', '演示商品,请替换为真实描述', 45.00, 110, '/images/products/27.png', 1), + (28, 3, '方便面组合', '演示商品,请替换为真实描述', 42.00, 150, '/images/products/28.png', 1), + (29, 3, '巧克力', '演示商品,请替换为真实描述', 29.90, 200, '/images/products/29.png', 1), + (30, 3, '蜂蜜', '演示商品,请替换为真实描述', 78.00, 65, '/images/products/30.png', 1) +ON DUPLICATE KEY UPDATE + category_id = VALUES(category_id), + name = VALUES(name), + description = VALUES(description), + price = VALUES(price), + stock = VALUES(stock), + image_url = VALUES(image_url), + status = VALUES(status); diff --git a/backend/sql/schema.sql b/backend/sql/schema.sql new file mode 100644 index 0000000..4cacaac --- /dev/null +++ b/backend/sql/schema.sql @@ -0,0 +1,117 @@ +CREATE DATABASE IF NOT EXISTS eshop + DEFAULT CHARACTER SET utf8mb4 + DEFAULT COLLATE utf8mb4_unicode_ci; + +USE eshop; + +CREATE TABLE IF NOT EXISTS user_account ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + phone VARCHAR(20) NULL, + role TINYINT NOT NULL DEFAULT 1 COMMENT '1 member, 9 admin', + status TINYINT NOT NULL DEFAULT 1 COMMENT '1 enabled, 0 disabled', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_user_account_username (username) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS category ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(100) NOT NULL, + sort_order INT NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS product ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + category_id BIGINT NOT NULL, + name VARCHAR(150) NOT NULL, + description TEXT NULL, + price DECIMAL(10, 2) NOT NULL, + stock INT NOT NULL DEFAULT 0, + image_url VARCHAR(500) NULL, + status TINYINT NOT NULL DEFAULT 1 COMMENT '1 on sale, 0 off sale', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_product_category_id (category_id), + KEY idx_product_name (name), + CONSTRAINT fk_product_category FOREIGN KEY (category_id) REFERENCES category(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS address ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT NOT NULL, + receiver_name VARCHAR(50) NOT NULL, + phone VARCHAR(20) NOT NULL, + province VARCHAR(50) NOT NULL, + city VARCHAR(50) NOT NULL, + district VARCHAR(50) NOT NULL, + detail VARCHAR(255) NOT NULL, + is_default TINYINT NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_address_user_id (user_id), + CONSTRAINT fk_address_user FOREIGN KEY (user_id) REFERENCES user_account(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS cart_item ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + quantity INT NOT NULL DEFAULT 1, + selected TINYINT NOT NULL DEFAULT 1, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_cart_item_user_product (user_id, product_id), + KEY idx_cart_item_product_id (product_id), + CONSTRAINT fk_cart_item_user FOREIGN KEY (user_id) REFERENCES user_account(id), + CONSTRAINT fk_cart_item_product FOREIGN KEY (product_id) REFERENCES product(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS orders ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_no VARCHAR(64) NOT NULL, + user_id BIGINT NOT NULL, + address_id BIGINT NOT NULL, + total_amount DECIMAL(10, 2) NOT NULL, + status TINYINT NOT NULL DEFAULT 10 COMMENT '10 pending, 20 paid, 30 shipped, 40 completed, 90 cancelled', + paid_at DATETIME NULL, + shipped_at DATETIME NULL, + completed_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_orders_order_no (order_no), + KEY idx_orders_user_id (user_id), + CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES user_account(id), + CONSTRAINT fk_orders_address FOREIGN KEY (address_id) REFERENCES address(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS order_item ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + product_name VARCHAR(150) NOT NULL, + product_price DECIMAL(10, 2) NOT NULL, + quantity INT NOT NULL, + subtotal DECIMAL(10, 2) NOT NULL, + KEY idx_order_item_order_id (order_id), + KEY idx_order_item_product_id (product_id), + CONSTRAINT fk_order_item_order FOREIGN KEY (order_id) REFERENCES orders(id), + CONSTRAINT fk_order_item_product FOREIGN KEY (product_id) REFERENCES product(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS payment ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_id BIGINT NOT NULL, + payment_no VARCHAR(64) NOT NULL, + amount DECIMAL(10, 2) NOT NULL, + status TINYINT NOT NULL DEFAULT 10 COMMENT '10 pending, 20 success, 30 failed, 40 refunded', + paid_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_payment_payment_no (payment_no), + UNIQUE KEY uk_payment_order_id (order_id), + CONSTRAINT fk_payment_order FOREIGN KEY (order_id) REFERENCES orders(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/backend/src/Eshop.Api/Controllers/AdminController.cs b/backend/src/Eshop.Api/Controllers/AdminController.cs new file mode 100644 index 0000000..140e65a --- /dev/null +++ b/backend/src/Eshop.Api/Controllers/AdminController.cs @@ -0,0 +1,22 @@ +using Eshop.Application.Common; +using Microsoft.AspNetCore.Mvc; + +namespace Eshop.Api.Controllers; + +[ApiController] +[Route("api/admin")] +public class AdminController : ControllerBase +{ + [HttpGet("dashboard")] + public ActionResult> Dashboard() + { + return Ok(ApiResponse.Ok(new + { + users = 0, + products = 0, + orders = 0, + salesAmount = 0, + next = "TODO: require admin role and return real dashboard metrics" + })); + } +} diff --git a/backend/src/Eshop.Api/Controllers/AuthController.cs b/backend/src/Eshop.Api/Controllers/AuthController.cs new file mode 100644 index 0000000..818b38f --- /dev/null +++ b/backend/src/Eshop.Api/Controllers/AuthController.cs @@ -0,0 +1,34 @@ +using Eshop.Application.Common; +using Microsoft.AspNetCore.Mvc; + +namespace Eshop.Api.Controllers; + +[ApiController] +[Route("api/auth")] +public class AuthController : ControllerBase +{ + [HttpPost("register")] + public ActionResult> Register(RegisterRequest request) + { + return Ok(ApiResponse.Ok(new + { + request.Username, + next = "TODO: validate input, hash password with BCrypt, save user, return user summary" + })); + } + + [HttpPost("login")] + public ActionResult> Login(LoginRequest request) + { + return Ok(ApiResponse.Ok(new + { + request.Username, + token = "TODO_JWT_TOKEN", + next = "TODO: verify password hash and issue JWT" + })); + } +} + +public record RegisterRequest(string Username, string Password, string? Phone); + +public record LoginRequest(string Username, string Password); diff --git a/backend/src/Eshop.Api/Controllers/CartController.cs b/backend/src/Eshop.Api/Controllers/CartController.cs new file mode 100644 index 0000000..bf6616c --- /dev/null +++ b/backend/src/Eshop.Api/Controllers/CartController.cs @@ -0,0 +1,33 @@ +using Eshop.Application.Common; +using Microsoft.AspNetCore.Mvc; + +namespace Eshop.Api.Controllers; + +[ApiController] +[Route("api/cart")] +public class CartController : ControllerBase +{ + [HttpGet] + public ActionResult> List() + { + return Ok(ApiResponse.Ok(new + { + items = Array.Empty(), + totalAmount = 0, + next = "TODO: require JWT, load current user's cart items" + })); + } + + [HttpPost] + public ActionResult> Add(AddCartItemRequest request) + { + return Ok(ApiResponse.Ok(new + { + request.ProductId, + request.Quantity, + next = "TODO: validate stock and upsert cart item" + })); + } +} + +public record AddCartItemRequest(long ProductId, int Quantity); diff --git a/backend/src/Eshop.Api/Controllers/HealthController.cs b/backend/src/Eshop.Api/Controllers/HealthController.cs new file mode 100644 index 0000000..b657f29 --- /dev/null +++ b/backend/src/Eshop.Api/Controllers/HealthController.cs @@ -0,0 +1,20 @@ +using Eshop.Application.Common; +using Microsoft.AspNetCore.Mvc; + +namespace Eshop.Api.Controllers; + +[ApiController] +[Route("api/health")] +public class HealthController : ControllerBase +{ + [HttpGet] + public ActionResult> Get() + { + return Ok(ApiResponse.Ok(new + { + service = "Eshop.Api", + status = "ok", + time = DateTimeOffset.UtcNow + })); + } +} diff --git a/backend/src/Eshop.Api/Controllers/OrdersController.cs b/backend/src/Eshop.Api/Controllers/OrdersController.cs new file mode 100644 index 0000000..5ff9d6d --- /dev/null +++ b/backend/src/Eshop.Api/Controllers/OrdersController.cs @@ -0,0 +1,42 @@ +using Eshop.Application.Common; +using Microsoft.AspNetCore.Mvc; + +namespace Eshop.Api.Controllers; + +[ApiController] +[Route("api/orders")] +public class OrdersController : ControllerBase +{ + [HttpGet] + public ActionResult> List() + { + return Ok(ApiResponse.Ok(new + { + list = Array.Empty(), + next = "TODO: require JWT, return current user's order list" + })); + } + + [HttpPost] + public ActionResult> Create(CreateOrderRequest request) + { + return Ok(ApiResponse.Ok(new + { + request.AddressId, + orderNo = "TODO_ORDER_NO", + next = "TODO: create order, snapshot items, deduct stock in transaction" + })); + } + + [HttpPost("{id:long}/pay")] + public ActionResult> Pay(long id) + { + return Ok(ApiResponse.Ok(new + { + orderId = id, + next = "TODO: simulate payment and update order/payment status idempotently" + })); + } +} + +public record CreateOrderRequest(long AddressId, IReadOnlyList CartItemIds); diff --git a/backend/src/Eshop.Api/Controllers/ProductsController.cs b/backend/src/Eshop.Api/Controllers/ProductsController.cs new file mode 100644 index 0000000..14a8079 --- /dev/null +++ b/backend/src/Eshop.Api/Controllers/ProductsController.cs @@ -0,0 +1,34 @@ +using Eshop.Application.Common; +using Microsoft.AspNetCore.Mvc; + +namespace Eshop.Api.Controllers; + +[ApiController] +[Route("api/products")] +public class ProductsController : ControllerBase +{ + [HttpGet] + public ActionResult>> List([FromQuery] ProductQuery query) + { + var items = new[] + { + new ProductListItemResponse(1, "演示商品", "默认分类", 99.00m, 100, "/placeholder-product.png") + }; + + return Ok(ApiResponse>.Ok( + new PagedResult(items, items.Length, query.Page, query.PageSize))); + } + + [HttpGet("{id:long}")] + public ActionResult> Detail(long id) + { + return Ok(ApiResponse.Ok( + new ProductDetailResponse(id, "演示商品", "默认分类", "TODO: 商品详情描述", 99.00m, 100))); + } +} + +public record ProductQuery(int Page = 1, int PageSize = 10, long? CategoryId = null, string? Keyword = null); + +public record ProductListItemResponse(long Id, string Name, string CategoryName, decimal Price, int Stock, string? ImageUrl); + +public record ProductDetailResponse(long Id, string Name, string CategoryName, string Description, decimal Price, int Stock); diff --git a/backend/src/Eshop.Api/Eshop.Api.csproj b/backend/src/Eshop.Api/Eshop.Api.csproj new file mode 100644 index 0000000..de7f227 --- /dev/null +++ b/backend/src/Eshop.Api/Eshop.Api.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + diff --git a/backend/src/Eshop.Api/Eshop.Api.http b/backend/src/Eshop.Api/Eshop.Api.http new file mode 100644 index 0000000..4afb2ac --- /dev/null +++ b/backend/src/Eshop.Api/Eshop.Api.http @@ -0,0 +1,10 @@ +@Eshop.Api_HostAddress = http://localhost:5081 + +GET {{Eshop.Api_HostAddress}}/api/health +Accept: application/json + +### +GET {{Eshop.Api_HostAddress}}/api/products +Accept: application/json + +### diff --git a/backend/src/Eshop.Api/Middleware/ExceptionHandlingMiddleware.cs b/backend/src/Eshop.Api/Middleware/ExceptionHandlingMiddleware.cs new file mode 100644 index 0000000..3fb98c3 --- /dev/null +++ b/backend/src/Eshop.Api/Middleware/ExceptionHandlingMiddleware.cs @@ -0,0 +1,35 @@ +using System.Net; +using System.Text.Json; +using Eshop.Application.Common; + +namespace Eshop.Api.Middleware; + +public class ExceptionHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception. TraceId: {TraceId}", context.TraceIdentifier); + + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + context.Response.ContentType = "application/json; charset=utf-8"; + + var response = ApiResponse.Fail(500, "服务器内部错误", context.TraceIdentifier); + await context.Response.WriteAsync(JsonSerializer.Serialize(response)); + } + } +} diff --git a/backend/src/Eshop.Api/Program.cs b/backend/src/Eshop.Api/Program.cs new file mode 100644 index 0000000..a08f404 --- /dev/null +++ b/backend/src/Eshop.Api/Program.cs @@ -0,0 +1,36 @@ +using Eshop.Api.Middleware; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddOpenApi(); + +builder.Services.AddCors(options => +{ + options.AddPolicy("Frontend", policy => + { + policy + .WithOrigins(builder.Configuration.GetSection("Cors:AllowedOrigins").Get() ?? ["http://localhost:5173"]) + .AllowAnyHeader() + .AllowAnyMethod(); + }); +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseMiddleware(); +app.UseHttpsRedirection(); +app.UseCors("Frontend"); + +// TODO: Enable JWT authentication after Jwt settings are finalized. +// app.UseAuthentication(); +// app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/backend/src/Eshop.Api/Properties/launchSettings.json b/backend/src/Eshop.Api/Properties/launchSettings.json new file mode 100644 index 0000000..49cd7b8 --- /dev/null +++ b/backend/src/Eshop.Api/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5128", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7220;http://localhost:5128", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/backend/src/Eshop.Api/appsettings.Development.json b/backend/src/Eshop.Api/appsettings.Development.json new file mode 100644 index 0000000..260b274 --- /dev/null +++ b/backend/src/Eshop.Api/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Port=3306;Database=eshop_dev;User=root;Password=CHANGE_ME;" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/backend/src/Eshop.Api/appsettings.Local.example.json b/backend/src/Eshop.Api/appsettings.Local.example.json new file mode 100644 index 0000000..2da06f2 --- /dev/null +++ b/backend/src/Eshop.Api/appsettings.Local.example.json @@ -0,0 +1,11 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Port=3306;Database=eshop_dev;User=root;Password=your_local_password;" + }, + "Jwt": { + "Issuer": "Eshop", + "Audience": "Eshop.Web", + "SigningKey": "replace-with-at-least-32-random-characters", + "AccessTokenMinutes": 60 + } +} diff --git a/backend/src/Eshop.Api/appsettings.json b/backend/src/Eshop.Api/appsettings.json new file mode 100644 index 0000000..4ee54e1 --- /dev/null +++ b/backend/src/Eshop.Api/appsettings.json @@ -0,0 +1,24 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Port=3306;Database=eshop;User=root;Password=CHANGE_ME;" + }, + "Jwt": { + "Issuer": "Eshop", + "Audience": "Eshop.Web", + "SigningKey": "CHANGE_ME_TO_A_LONG_RANDOM_SECRET", + "AccessTokenMinutes": 60 + }, + "Cors": { + "AllowedOrigins": [ + "http://localhost:5173", + "http://127.0.0.1:5173" + ] + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/backend/src/Eshop.Application/Common/ApiResponse.cs b/backend/src/Eshop.Application/Common/ApiResponse.cs new file mode 100644 index 0000000..c2a00e9 --- /dev/null +++ b/backend/src/Eshop.Application/Common/ApiResponse.cs @@ -0,0 +1,14 @@ +namespace Eshop.Application.Common; + +public record ApiResponse(int Code, string Message, T? Data, string? TraceId = null) +{ + public static ApiResponse Ok(T data, string message = "ok", string? traceId = null) + { + return new ApiResponse(0, message, data, traceId); + } + + public static ApiResponse Fail(int code, string message, string? traceId = null) + { + return new ApiResponse(code, message, default, traceId); + } +} diff --git a/backend/src/Eshop.Application/Common/PagedResult.cs b/backend/src/Eshop.Application/Common/PagedResult.cs new file mode 100644 index 0000000..44fc6d5 --- /dev/null +++ b/backend/src/Eshop.Application/Common/PagedResult.cs @@ -0,0 +1,3 @@ +namespace Eshop.Application.Common; + +public record PagedResult(IReadOnlyCollection List, long Total, int Page, int PageSize); diff --git a/backend/src/Eshop.Application/Eshop.Application.csproj b/backend/src/Eshop.Application/Eshop.Application.csproj new file mode 100644 index 0000000..896965d --- /dev/null +++ b/backend/src/Eshop.Application/Eshop.Application.csproj @@ -0,0 +1,13 @@ + + + + + + + + net10.0 + enable + enable + + + diff --git a/backend/src/Eshop.Domain/Entities/Address.cs b/backend/src/Eshop.Domain/Entities/Address.cs new file mode 100644 index 0000000..53bc783 --- /dev/null +++ b/backend/src/Eshop.Domain/Entities/Address.cs @@ -0,0 +1,16 @@ +namespace Eshop.Domain.Entities; + +public class Address +{ + public long Id { get; set; } + public long UserId { get; set; } + public string ReceiverName { get; set; } = string.Empty; + public string Phone { get; set; } = string.Empty; + public string Province { get; set; } = string.Empty; + public string City { get; set; } = string.Empty; + public string District { get; set; } = string.Empty; + public string Detail { get; set; } = string.Empty; + public bool IsDefault { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/backend/src/Eshop.Domain/Entities/CartItem.cs b/backend/src/Eshop.Domain/Entities/CartItem.cs new file mode 100644 index 0000000..2dc2a70 --- /dev/null +++ b/backend/src/Eshop.Domain/Entities/CartItem.cs @@ -0,0 +1,12 @@ +namespace Eshop.Domain.Entities; + +public class CartItem +{ + public long Id { get; set; } + public long UserId { get; set; } + public long ProductId { get; set; } + public int Quantity { get; set; } + public bool Selected { get; set; } = true; + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/backend/src/Eshop.Domain/Entities/Category.cs b/backend/src/Eshop.Domain/Entities/Category.cs new file mode 100644 index 0000000..3246fa4 --- /dev/null +++ b/backend/src/Eshop.Domain/Entities/Category.cs @@ -0,0 +1,10 @@ +namespace Eshop.Domain.Entities; + +public class Category +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public int SortOrder { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/backend/src/Eshop.Domain/Entities/Order.cs b/backend/src/Eshop.Domain/Entities/Order.cs new file mode 100644 index 0000000..68cd822 --- /dev/null +++ b/backend/src/Eshop.Domain/Entities/Order.cs @@ -0,0 +1,18 @@ +using Eshop.Domain.Enums; + +namespace Eshop.Domain.Entities; + +public class Order +{ + public long Id { get; set; } + public string OrderNo { get; set; } = string.Empty; + public long UserId { get; set; } + public long AddressId { get; set; } + public decimal TotalAmount { get; set; } + public OrderStatus Status { get; set; } = OrderStatus.PendingPayment; + public DateTime? PaidAt { get; set; } + public DateTime? ShippedAt { get; set; } + public DateTime? CompletedAt { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/backend/src/Eshop.Domain/Entities/OrderItem.cs b/backend/src/Eshop.Domain/Entities/OrderItem.cs new file mode 100644 index 0000000..2a76edf --- /dev/null +++ b/backend/src/Eshop.Domain/Entities/OrderItem.cs @@ -0,0 +1,12 @@ +namespace Eshop.Domain.Entities; + +public class OrderItem +{ + public long Id { get; set; } + public long OrderId { get; set; } + public long ProductId { get; set; } + public string ProductName { get; set; } = string.Empty; + public decimal ProductPrice { get; set; } + public int Quantity { get; set; } + public decimal Subtotal { get; set; } +} diff --git a/backend/src/Eshop.Domain/Entities/Payment.cs b/backend/src/Eshop.Domain/Entities/Payment.cs new file mode 100644 index 0000000..284937d --- /dev/null +++ b/backend/src/Eshop.Domain/Entities/Payment.cs @@ -0,0 +1,15 @@ +using Eshop.Domain.Enums; + +namespace Eshop.Domain.Entities; + +public class Payment +{ + public long Id { get; set; } + public long OrderId { get; set; } + public string PaymentNo { get; set; } = string.Empty; + public decimal Amount { get; set; } + public PaymentStatus Status { get; set; } = PaymentStatus.Pending; + public DateTime? PaidAt { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/backend/src/Eshop.Domain/Entities/Product.cs b/backend/src/Eshop.Domain/Entities/Product.cs new file mode 100644 index 0000000..3e4b39b --- /dev/null +++ b/backend/src/Eshop.Domain/Entities/Product.cs @@ -0,0 +1,17 @@ +using Eshop.Domain.Enums; + +namespace Eshop.Domain.Entities; + +public class Product +{ + public long Id { get; set; } + public long CategoryId { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public decimal Price { get; set; } + public int Stock { get; set; } + public string? ImageUrl { get; set; } + public ProductStatus Status { get; set; } = ProductStatus.OnSale; + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/backend/src/Eshop.Domain/Entities/UserAccount.cs b/backend/src/Eshop.Domain/Entities/UserAccount.cs new file mode 100644 index 0000000..c5f35f7 --- /dev/null +++ b/backend/src/Eshop.Domain/Entities/UserAccount.cs @@ -0,0 +1,15 @@ +using Eshop.Domain.Enums; + +namespace Eshop.Domain.Entities; + +public class UserAccount +{ + public long Id { get; set; } + public string Username { get; set; } = string.Empty; + public string PasswordHash { get; set; } = string.Empty; + public string? Phone { get; set; } + public UserRole Role { get; set; } = UserRole.Member; + public UserStatus Status { get; set; } = UserStatus.Enabled; + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/backend/src/Eshop.Domain/Enums/OrderStatus.cs b/backend/src/Eshop.Domain/Enums/OrderStatus.cs new file mode 100644 index 0000000..b6c37d4 --- /dev/null +++ b/backend/src/Eshop.Domain/Enums/OrderStatus.cs @@ -0,0 +1,10 @@ +namespace Eshop.Domain.Enums; + +public enum OrderStatus +{ + PendingPayment = 10, + Paid = 20, + Shipped = 30, + Completed = 40, + Cancelled = 90 +} diff --git a/backend/src/Eshop.Domain/Enums/PaymentStatus.cs b/backend/src/Eshop.Domain/Enums/PaymentStatus.cs new file mode 100644 index 0000000..2be47ea --- /dev/null +++ b/backend/src/Eshop.Domain/Enums/PaymentStatus.cs @@ -0,0 +1,9 @@ +namespace Eshop.Domain.Enums; + +public enum PaymentStatus +{ + Pending = 10, + Success = 20, + Failed = 30, + Refunded = 40 +} diff --git a/backend/src/Eshop.Domain/Enums/ProductStatus.cs b/backend/src/Eshop.Domain/Enums/ProductStatus.cs new file mode 100644 index 0000000..c12b874 --- /dev/null +++ b/backend/src/Eshop.Domain/Enums/ProductStatus.cs @@ -0,0 +1,7 @@ +namespace Eshop.Domain.Enums; + +public enum ProductStatus +{ + OffSale = 0, + OnSale = 1 +} diff --git a/backend/src/Eshop.Domain/Enums/UserRole.cs b/backend/src/Eshop.Domain/Enums/UserRole.cs new file mode 100644 index 0000000..4d003bb --- /dev/null +++ b/backend/src/Eshop.Domain/Enums/UserRole.cs @@ -0,0 +1,7 @@ +namespace Eshop.Domain.Enums; + +public enum UserRole +{ + Member = 1, + Admin = 9 +} diff --git a/backend/src/Eshop.Domain/Enums/UserStatus.cs b/backend/src/Eshop.Domain/Enums/UserStatus.cs new file mode 100644 index 0000000..c72b58f --- /dev/null +++ b/backend/src/Eshop.Domain/Enums/UserStatus.cs @@ -0,0 +1,7 @@ +namespace Eshop.Domain.Enums; + +public enum UserStatus +{ + Disabled = 0, + Enabled = 1 +} diff --git a/backend/src/Eshop.Domain/Eshop.Domain.csproj b/backend/src/Eshop.Domain/Eshop.Domain.csproj new file mode 100644 index 0000000..b760144 --- /dev/null +++ b/backend/src/Eshop.Domain/Eshop.Domain.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/backend/src/Eshop.Infrastructure/Data/DatabaseOptions.cs b/backend/src/Eshop.Infrastructure/Data/DatabaseOptions.cs new file mode 100644 index 0000000..0fc783a --- /dev/null +++ b/backend/src/Eshop.Infrastructure/Data/DatabaseOptions.cs @@ -0,0 +1,6 @@ +namespace Eshop.Infrastructure.Data; + +public class DatabaseOptions +{ + public string DefaultConnection { get; set; } = string.Empty; +} diff --git a/backend/src/Eshop.Infrastructure/Eshop.Infrastructure.csproj b/backend/src/Eshop.Infrastructure/Eshop.Infrastructure.csproj new file mode 100644 index 0000000..07d5c49 --- /dev/null +++ b/backend/src/Eshop.Infrastructure/Eshop.Infrastructure.csproj @@ -0,0 +1,14 @@ + + + + + + + + + net10.0 + enable + enable + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9d2fc2d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +services: + mysql: + image: mysql:8.0 + container_name: eshop-mysql + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: change_me + MYSQL_DATABASE: eshop + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + - ./backend/sql/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro + - ./backend/sql/data.sql:/docker-entrypoint-initdb.d/02-data.sql:ro + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + + api: + build: + context: ./backend + container_name: eshop-api + restart: unless-stopped + depends_on: + - mysql + environment: + ASPNETCORE_ENVIRONMENT: Development + ASPNETCORE_URLS: http://+:8080 + ConnectionStrings__DefaultConnection: Server=mysql;Port=3306;Database=eshop;User=root;Password=change_me; + Jwt__Issuer: Eshop + Jwt__Audience: Eshop.Web + Jwt__SigningKey: replace-with-at-least-32-random-characters + Jwt__AccessTokenMinutes: 60 + Cors__AllowedOrigins__0: http://localhost:5173 + Cors__AllowedOrigins__1: http://127.0.0.1:5173 + ports: + - "8080:8080" + +volumes: + mysql_data: diff --git "a/docs/01-\351\234\200\346\261\202\346\226\207\346\241\243/\351\234\200\346\261\202\350\247\204\346\240\274\350\257\264\346\230\216\344\271\246.md" "b/docs/01-\351\234\200\346\261\202\346\226\207\346\241\243/\351\234\200\346\261\202\350\247\204\346\240\274\350\257\264\346\230\216\344\271\246.md" index 25f3362..a35bdbf 100644 --- "a/docs/01-\351\234\200\346\261\202\346\226\207\346\241\243/\351\234\200\346\261\202\350\247\204\346\240\274\350\257\264\346\230\216\344\271\246.md" +++ "b/docs/01-\351\234\200\346\261\202\346\226\207\346\241\243/\351\234\200\346\261\202\350\247\204\346\240\274\350\257\264\346\230\216\344\271\246.md" @@ -1,27 +1,47 @@ # 电子商城需求规格说明书 -> 组别:____ 编写人:____ 编写日期:____ 版本:v1.0 -> 截止:第 1 周周三提交初稿 +> 班级:1 班 组号:6 组 编写人:全组维护 编写日期:2026-07-22 版本:v1.0 +> 本文档面向:组长/架构员、各业务模块负责人、测试部署负责人、指导教师。开发、联调、测试和答辩均以本文档为需求基准。 ## 修订记录 | 版本 | 日期 | 修改人 | 修改说明 | |------|------|--------|----------| -| v1.0 | | | 初稿 | +| v1.0 | 2026-07-22 | 全组 | 完成 P0 必做功能需求、页面原型范围、接口路径约定与验收口径 | ## 一、引言 ### 1.1 编写目的 -(说明本文档的读者对象和用途) + +本文档明确电子商城系统的业务范围、用户角色、功能需求、非功能需求、页面原型入口和验收标准,保证每位成员在开发自己模块时能够统一理解: + +- 做什么功能、做到什么程度算完成; +- 页面、接口、数据库之间如何衔接; +- 哪些接口路径和字段命名必须保持一致; +- 答辩时每个模块需要讲清的业务链路。 ### 1.2 项目背景 -(描述项目背景、要解决的问题) -### 1.3 术语定义 +本项目是暑期企业级综合项目实战,目标是在 4 周内以小组协作方式完成一个可演示、可部署、可答辩的 B2C 电子商城系统。系统覆盖用户注册登录、商品浏览、购物车、订单、模拟支付和后台管理等核心流程,并通过 Markdown 文档、Git 提交、日报周报和交叉 Code Review 体现完整研发过程。 + +### 1.3 项目范围 + +| 范围 | 说明 | +|------|------| +| 前台购物端 | 游客/会员浏览商品,会员登录后管理地址、购物车、订单和模拟支付 | +| 后台管理端 | 管理员维护商品、分类、订单状态和用户状态,查看运营概览 | +| 数据支撑 | MySQL 存储用户、商品、购物车、订单、支付等核心数据 | +| 外部系统 | 不接入真实支付、物流、短信和第三方登录,相关能力采用模拟实现 | + +### 1.4 术语定义 | 术语 | 说明 | |------|------| -| SKU | 例:库存量单位,最小的商品售卖单元 | +| SKU | 库存量单位,本项目 P0 阶段按商品维度管理库存,SKU 作为选做扩展 | +| 会员 | 已注册并登录的买家用户 | +| 管理员 | 拥有后台管理权限的用户,角色编码为 `9` | +| 模拟支付 | 不调用真实支付渠道,通过接口将订单和支付记录状态更新为支付成功 | +| P0/P1/P2 | P0 为必须完成,P1 为选做增强,P2 为后续优化 | ## 二、总体描述 @@ -29,62 +49,299 @@ | 角色 | 描述 | 主要操作 | |------|------|----------| -| 游客 | | | -| 会员(买家) | | | -| 管理员 | | | +| 游客 | 未登录用户 | 浏览首页、商品列表、搜索商品、查看商品详情、跳转登录/注册 | +| 会员(买家) | 已登录普通用户 | 维护个人信息和地址、加入购物车、提交订单、模拟支付、查看订单 | +| 管理员 | 后台运营人员 | 管理商品/分类、处理订单发货、禁用/启用用户、查看后台概览 | +| 测试/答辩成员 | 项目验收参与者 | 按验收清单演示页面、接口、数据库状态变化和异常场景 | -### 2.2 业务流程图 -(用 Mermaid 或图片描述核心购物流程:浏览 → 加购 → 下单 → 支付 → 发货 → 完成) +### 2.2 核心业务流程 ```mermaid flowchart LR - A[浏览商品] --> B[加入购物车] --> C[提交订单] --> D[支付] --> E[商家发货] --> F[确认收货] + A[游客/会员进入首页] --> B[浏览商品列表] + B --> C[搜索或分类筛选] + C --> D[查看商品详情] + D --> E{是否登录} + E -- 否 --> F[登录/注册] + E -- 是 --> G[加入购物车] + F --> G + G --> H[选择购物车商品] + H --> I[提交订单] + I --> J[模拟支付] + J --> K[订单状态变为已支付] + K --> L[管理员发货] + L --> M[会员确认收货/订单完成] ``` ### 2.3 功能模块清单 -| 模块编号 | 模块名称 | 优先级(P0/P1/P2) | 负责人 | -|----------|----------|------------------|--------| -| M01 | 用户模块 | P0 | | -| M02 | 商品模块 | P0 | | -| M03 | 购物车 | P0 | | -| M04 | 订单模块 | P0 | | -| M05 | 支付模块(模拟) | P0 | | -| M06 | 后台管理 | P0 | | -| M07 | (选做功能) | P1 | | +| 模块编号 | 模块名称 | 优先级 | 负责人建议 | 说明 | +|----------|----------|--------|------------|------| +| M00 | 公共基建 | P0 | 组长/架构员 | 脚手架、统一响应、鉴权、路由守卫、公共布局、集成联调 | +| M01 | 用户模块 | P0 | 用户购物车包 | 注册、登录、退出、个人信息、地址管理、后台用户管理 | +| M02 | 商品模块 | P0 | 商品包 | 分类、列表、搜索、详情、后台商品管理 | +| M03 | 购物车模块 | P0 | 用户购物车包 | 加购、列表、改数量、删除、勾选结算 | +| M04 | 订单模块 | P0 | 订单支付包 | 下单、订单列表、订单详情、取消、后台订单管理 | +| M05 | 支付模块 | P0 | 订单支付包 | 模拟支付、支付状态流转、支付记录 | +| M06 | 后台管理 | P0 | 各模块共同维护 | 后台概览、商品管理、订单管理、用户管理 | +| M07 | 测试部署 | P0 | 测试部署包 | 测试计划/报告、部署脚本、演示数据、验收清单 | +| M08 | 选做功能 | P1 | 自愿认领 | 推荐优先:商品收藏/浏览历史、后台操作日志 | +| M09 | 挑战模块 | P1 | 自愿认领 | 推荐优先:订单超时取消、数据统计看板或缓存优化 | + +### 2.4 页面范围 + +页面原型详见 `docs/02-设计文档/原型设计.md`。P0 页面至少包括: + +| 端 | 页面 | 前端路由 | 关键接口 | +|----|------|----------|----------| +| 前台 | 首页 | `/` | `GET /api/products` | +| 前台 | 登录/注册 | `/login` | `POST /api/auth/login`、`POST /api/auth/register` | +| 前台 | 商品列表 | `/products` | `GET /api/products` | +| 前台 | 商品详情 | `/products/:id` | `GET /api/products/{id}` | +| 前台 | 购物车 | `/cart` | `GET /api/cart`、`POST /api/cart` | +| 前台 | 订单列表 | `/orders` | `GET /api/orders` | +| 后台 | 管理首页 | `/admin` | `GET /api/admin/dashboard` | +| 后台 | 商品/订单/用户管理 | `/admin/*` | 统一使用 `/api/admin/...` 前缀 | ## 三、功能需求详述 -> 每个功能点按以下格式编写,编号规则:模块编号-序号,如 M01-01。 +### M00 公共基建 + +#### M00-01 统一接口响应 + +- **描述**:后端所有业务接口统一返回 `{ code, message, data, traceId }`。 +- **前置条件**:后端 API 项目已启用 Controller;前端使用统一 `http` 实例。 +- **主流程**: + 1. Controller 返回 `ApiResponse.Ok(data)` 或 `ApiResponse.Fail(code, message)`。 + 2. 前端 Axios 拦截器统一解析响应体。 + 3. 业务页面只处理 `data` 或错误提示。 +- **异常流程**:服务端未捕获异常时由异常中间件转换为统一失败响应。 +- **验收要点**:成功/失败响应结构一致,接口文档示例与实际返回一致。 + +#### M00-02 登录鉴权与路由守卫 + +- **描述**:登录后保存 token,访问购物车、订单、后台等页面时必须校验登录态。 +- **前置条件**:登录接口返回 token;前端 Pinia/LocalStorage 可读取登录态。 +- **主流程**: + 1. 用户登录成功,前端保存 `eshop_token`。 + 2. Axios 请求自动带 `Authorization: Bearer `。 + 3. 未登录访问受保护页面时跳转 `/login`。 +- **异常流程**:token 过期或无权限时提示并回到登录页。 +- **验收要点**:未登录不能访问购物车/订单/后台;管理员页面普通会员不可访问。 + +### M01 用户模块 + +#### M01-01 用户注册 + +- **描述**:游客可注册为会员。 +- **前置条件**:用户名未被占用。 +- **主流程**: + 1. 用户输入用户名、密码、确认密码、手机号。 + 2. 前端校验必填项、密码长度、两次密码一致。 + 3. 调用 `POST /api/auth/register`。 + 4. 注册成功后跳转登录页或自动登录。 +- **异常流程**:用户名已存在、密码格式错误、手机号格式错误时给出明确提示。 +- **验收要点**:重复注册被拦截;密码后端加密存储,不允许明文入库。 + +#### M01-02 用户登录/退出 + +- **描述**:会员和管理员使用用户名密码登录系统,登录后可退出。 +- **前置条件**:账号存在且未被禁用。 +- **主流程**: + 1. 用户输入用户名和密码。 + 2. 调用 `POST /api/auth/login`。 + 3. 成功后保存 token、角色和用户名。 + 4. 点击退出时清除本地登录态并跳转首页。 +- **异常流程**:密码错误、账号不存在、账号被禁用时提示原因。 +- **验收要点**:登录态刷新后保持;退出后受保护页面无法继续访问。 + +#### M01-03 个人信息维护 + +- **描述**:会员查看并修改自己的昵称、手机号等基础信息。 +- **前置条件**:会员已登录。 +- **主流程**:进入个人中心,加载当前用户信息,修改后提交保存。 +- **异常流程**:手机号格式错误、token 失效时提示。 +- **验收要点**:只能修改自己的信息,不能越权访问他人信息。 + +#### M01-04 收货地址管理 + +- **描述**:会员维护下单所需收货地址。 +- **前置条件**:会员已登录。 +- **主流程**: + 1. 查看地址列表。 + 2. 新增/编辑收货人、手机号、省市区、详细地址。 + 3. 设置默认地址。 + 4. 删除不再使用的地址。 +- **异常流程**:删除订单已引用地址时保留历史订单快照,不影响已下单记录。 +- **验收要点**:增删改查完整;默认地址最多一个;下单页可选择地址。 + +### M02 商品模块 + +#### M02-01 商品列表与分类浏览 + +- **描述**:游客和会员都可分页浏览在售商品。 +- **前置条件**:数据库存在分类和商品演示数据。 +- **主流程**: + 1. 进入 `/products`。 + 2. 调用 `GET /api/products?page=1&pageSize=10`。 + 3. 展示商品图片、名称、分类、价格、库存。 + 4. 用户可按分类筛选。 +- **异常流程**:无商品时展示空状态;接口失败时展示重试提示。 +- **验收要点**:分页正确;下架商品前台不可见;演示数据不少于 30 个商品。 + +#### M02-02 商品搜索 -### M01-01 用户注册 +- **描述**:用户可按关键词模糊搜索商品。 +- **前置条件**:商品名称或描述包含关键词。 +- **主流程**:输入关键词,调用 `GET /api/products?keyword=...`,展示匹配结果。 +- **异常流程**:无匹配结果展示空状态。 +- **验收要点**:关键词搜索有结果;搜索条件与分页可组合。 -- **描述**: -- **前置条件**: +#### M02-03 商品详情 + +- **描述**:用户查看商品图片、价格、库存、描述,并可加入购物车。 +- **前置条件**:商品存在且处于上架状态。 +- **主流程**: + 1. 点击商品卡片进入详情页。 + 2. 调用 `GET /api/products/{id}`。 + 3. 展示商品详情并选择购买数量。 + 4. 点击加入购物车。 +- **异常流程**:商品不存在、下架、库存不足时提示。 +- **验收要点**:详情数据与列表一致;库存为 0 时禁止加购。 + +### M03 购物车模块 + +#### M03-01 加入购物车 + +- **描述**:会员可将商品加入购物车。 +- **前置条件**:会员已登录;商品在售且库存充足。 +- **主流程**: + 1. 商品详情页选择数量。 + 2. 调用 `POST /api/cart`。 + 3. 若购物车已有该商品,则累加数量。 +- **异常流程**:未登录跳转登录;库存不足提示。 +- **验收要点**:重复加购不产生重复行;数量不能小于 1。 + +#### M03-02 购物车列表、改数量与删除 + +- **描述**:会员查看购物车并维护商品数量。 +- **前置条件**:会员已登录。 +- **主流程**: + 1. 进入 `/cart` 加载购物车列表。 + 2. 修改数量时实时更新小计和总价。 + 3. 删除商品或清空无效商品。 +- **异常流程**:商品下架或库存不足时标记不可结算。 +- **验收要点**:金额计算准确;删除后总价同步变化。 + +#### M03-03 勾选结算 + +- **描述**:会员选择购物车商品进入下单。 +- **前置条件**:至少选择一个有效购物车项。 +- **主流程**:勾选商品,点击结算,进入确认订单页。 +- **异常流程**:未选择商品或商品库存不足时禁止结算。 +- **验收要点**:结算商品与下单请求中的 `cartItemIds` 一致。 + +### M04 订单模块 + +#### M04-01 提交订单 + +- **描述**:会员基于购物车商品和收货地址生成订单。 +- **前置条件**:会员已登录;购物车项有效;收货地址存在。 +- **主流程**: + 1. 确认商品、数量、地址和总价。 + 2. 调用 `POST /api/orders`。 + 3. 后端创建订单、订单项、锁定/扣减库存。 + 4. 返回订单号和订单金额。 +- **异常流程**:库存不足、地址无效、重复提交时提示。 +- **验收要点**:订单号唯一;订单项保存商品名称和价格快照;库存变化正确。 + +#### M04-02 订单列表与详情 + +- **描述**:会员查看自己的订单和订单详情。 +- **前置条件**:会员已登录。 +- **主流程**:进入 `/orders` 加载订单列表,可按状态筛选并查看详情。 +- **异常流程**:无订单展示空状态;不能查看他人订单。 +- **验收要点**:列表状态、金额、时间正确;详情包含订单项和收货地址。 + +#### M04-03 取消订单 + +- **描述**:会员可取消未支付订单。 +- **前置条件**:订单状态为待支付。 +- **主流程**:点击取消,确认后调用取消接口,订单状态变为已取消并回补库存。 +- **异常流程**:已支付/已发货/已完成订单不可取消。 +- **验收要点**:状态流转正确;库存回补正确。 + +### M05 支付模块 + +#### M05-01 模拟支付 + +- **描述**:会员对待支付订单执行模拟支付。 +- **前置条件**:订单状态为待支付。 - **主流程**: - 1. - 2. -- **异常流程**:(如用户名已存在、密码格式错误) -- **验收要点**: + 1. 订单页点击支付。 + 2. 调用 `POST /api/orders/{id}/pay`。 + 3. 后端创建或更新支付记录。 + 4. 订单状态变为已支付。 +- **异常流程**:重复支付应幂等返回成功;取消订单不可支付。 +- **验收要点**:支付后订单状态为 `20`,支付记录状态为 `20`。 + +### M06 后台管理 + +#### M06-01 后台概览 -### M01-02 用户登录 +- **描述**:管理员查看用户数、商品数、订单数和销售额。 +- **前置条件**:管理员已登录。 +- **主流程**:进入 `/admin`,调用 `GET /api/admin/dashboard`。 +- **异常流程**:非管理员访问返回无权限。 +- **验收要点**:指标从真实数据统计,不写死。 -(同上格式,逐个功能点补全…) +#### M06-02 后台商品管理 + +- **描述**:管理员维护商品和分类。 +- **前置条件**:管理员已登录。 +- **主流程**:新增、编辑、删除/下架商品,维护分类,商品变更后前台同步展示。 +- **异常流程**:已有订单引用的商品建议下架,不做物理删除。 +- **验收要点**:增删改查完整;上下架生效;价格和库存校验正确。 + +#### M06-03 后台订单管理 + +- **描述**:管理员查看订单并处理发货。 +- **前置条件**:管理员已登录。 +- **主流程**:按状态筛选订单,对已支付订单执行发货,状态变为已发货。 +- **异常流程**:待支付/已取消订单不可发货。 +- **验收要点**:状态流转符合 `10 -> 20 -> 30 -> 40` 或 `10 -> 90`。 + +#### M06-04 后台用户管理 + +- **描述**:管理员查看用户列表并禁用/启用用户。 +- **前置条件**:管理员已登录。 +- **主流程**:搜索用户,禁用异常账号,启用正常账号。 +- **异常流程**:禁用后该用户不可登录;已登录用户下次请求应失效或提示账号禁用。 +- **验收要点**:用户状态变更入库,登录接口正确拦截禁用用户。 ## 四、非功能需求 -| 类别 | 需求描述 | -|------|----------| -| 性能 | 常规页面加载 < 2s,列表接口分页返回 | -| 安全 | 密码加密存储、登录鉴权、输入校验 | -| 兼容 | Chrome/Edge 最新版 | -| 数据 | 演示数据不少于 30 个商品 | +| 类别 | 需求描述 | 验收口径 | +|------|----------|----------| +| 性能 | 常规页面首屏加载小于 2 秒;分页接口默认 `pageSize=10` | Chrome/Edge 本地演示无明显卡顿 | +| 安全 | 密码加密存储;登录接口不返回密码;后台接口校验管理员角色 | 抽查数据库和接口响应 | +| 数据 | 演示数据不少于 30 个商品、3 个分类、2 个测试用户、1 个管理员 | 运行 `backend/sql/data.sql` 后可展示 | +| 可维护性 | 前后端路径、字段、状态码以接口文档为准 | 联调前先更新 `接口设计.md` | +| 兼容性 | 支持 Chrome/Edge 最新版 | 验收现场浏览器可正常操作 | +| 稳定性 | 统一异常处理,无白屏、无未处理异常 | 演示全流程可连续完成 | +| 部署 | 支持本地启动和 Docker Compose 部署 | 后端健康检查 `/api/health` 可访问 | ## 五、界面原型 -(可贴手绘/墨刀/Figma 原型图,至少覆盖:首页、商品详情、购物车、下单、订单列表、后台商品管理) + +详细页面结构、交互说明、状态设计和“页面 -> 接口”映射见: + +- `docs/02-设计文档/原型设计.md` + +成员实现页面时必须先查看原型中的对应页面,再对照 `docs/02-设计文档/接口设计.md` 调接口,避免页面字段和后端路径不一致。 ## 六、需求确认 | 确认人 | 角色 | 日期 | 意见 | |--------|------|------|------| -| | 指导教师 | | | +| 全组成员 | 开发/测试/部署 | 2026-07-22 | 按本文档拆分模块任务,接口变更先改接口文档 | +| 指导教师 | 验收方 | 待确认 | 待评审 | diff --git "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\345\216\237\345\236\213\350\256\276\350\256\241.md" "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\345\216\237\345\236\213\350\256\276\350\256\241.md" new file mode 100644 index 0000000..086453f --- /dev/null +++ "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\345\216\237\345\236\213\350\256\276\350\256\241.md" @@ -0,0 +1,351 @@ +# 原型设计 + +> 班级:1 班 组号:6 组 编写人:全组维护 编写日期:2026-07-22 版本:v1.0 +> 本文档用于统一每位成员对页面、交互、接口路径和模块边界的理解。页面实现前先看本文档,再对照 `接口设计.md`。 + +## 一、原型目标 + +1. 让每个成员明确自己负责页面的入口、展示内容、操作按钮和异常状态。 +2. 统一前端页面路由与后端 API 路径,避免联调时路径不一致。 +3. 让测试成员可按页面流程编写用例,答辩成员可按页面讲清业务链路。 +4. P0 阶段先完成文字低保真原型,后续可补充截图、Figma 或墨刀链接。 + +## 二、全局布局 + +```text +┌──────────────────────────────────────────────────────────────┐ +│ 顶部导航:Logo | 首页 | 商品 | 购物车 | 我的订单 | 后台入口 | 登录/用户名 │ +├──────────────────────────────────────────────────────────────┤ +│ 页面主内容区 │ +│ - 前台页面:以商品、购物车、订单为主 │ +│ - 后台页面:左侧菜单 + 右侧表格/表单 │ +├──────────────────────────────────────────────────────────────┤ +│ 页脚:项目名称 | 班级组号 | 演示环境信息 │ +└──────────────────────────────────────────────────────────────┘ +``` + +### 2.1 全局交互约定 + +| 场景 | 页面表现 | +|------|----------| +| 加载中 | 表格/卡片区域显示加载状态 | +| 空数据 | 显示空状态文案和返回/新增按钮 | +| 接口失败 | 显示错误提示,并保留重试入口 | +| 未登录 | 跳转 `/login`,登录成功后回到原目标页 | +| 无权限 | 显示“无权限访问”,提供返回首页按钮 | +| 表单校验失败 | 在字段下方提示具体原因 | + +## 三、页面与接口总览 + +| 页面 | 前端路由 | 主要接口 | 负责模块 | +|------|----------|----------|----------| +| 首页 | `/` | `GET /api/products` | 商品包/公共基建 | +| 登录/注册 | `/login` | `POST /api/auth/login`、`POST /api/auth/register` | 用户购物车包 | +| 商品列表 | `/products` | `GET /api/products`、`GET /api/categories` | 商品包 | +| 商品详情 | `/products/:id` | `GET /api/products/{id}`、`POST /api/cart` | 商品包/用户购物车包 | +| 购物车 | `/cart` | `GET /api/cart`、`PUT /api/cart/{id}`、`DELETE /api/cart/{id}` | 用户购物车包 | +| 确认订单 | `/checkout` | `GET /api/addresses`、`POST /api/orders` | 订单支付包 | +| 订单列表 | `/orders` | `GET /api/orders`、`POST /api/orders/{id}/pay` | 订单支付包 | +| 订单详情 | `/orders/:id` | `GET /api/orders/{id}`、`POST /api/orders/{id}/cancel` | 订单支付包 | +| 后台首页 | `/admin` | `GET /api/admin/dashboard` | 公共基建/后台管理 | +| 后台商品 | `/admin/products` | `/api/admin/products` | 商品包 | +| 后台订单 | `/admin/orders` | `/api/admin/orders` | 订单支付包 | +| 后台用户 | `/admin/users` | `/api/admin/users` | 用户购物车包 | + +## 四、前台页面原型 + +### 4.1 首页 `/` + +**页面目标**:让用户快速进入商品浏览和购物流程。 + +```text +┌──────────────── 首页 ────────────────┐ +│ Banner:电子商城演示系统 │ +│ 搜索框:[输入商品关键词] [搜索] │ +│ 快捷入口:[商品列表] [购物车] [我的订单] │ +├──────────────────────────────────────┤ +│ 推荐商品/最新商品 │ +│ ┌商品卡片┐ ┌商品卡片┐ ┌商品卡片┐ │ +│ │图片 │ │图片 │ │图片 │ │ +│ │名称 │ │名称 │ │名称 │ │ +│ │价格库存│ │价格库存│ │价格库存│ │ +│ └查看详情┘ └查看详情┘ └查看详情┘ │ +└──────────────────────────────────────┘ +``` + +| 元素 | 说明 | +|------|------| +| 搜索框 | 输入关键词后跳转 `/products?keyword=...` | +| 商品卡片 | 展示图片、名称、价格、库存,点击进入详情 | +| 后台入口 | 未登录或非管理员时隐藏或提示无权限 | + +### 4.2 登录/注册 `/login` + +**页面目标**:完成会员和管理员登录,提供注册入口。 + +```text +┌──────────── 登录/注册 ────────────┐ +│ Tab:[登录] [注册] │ +│ 用户名:[输入用户名] │ +│ 密码: [输入密码] │ +│ 手机号:[输入手机号] 仅注册显示 │ +│ [提交] │ +└───────────────────────────────────┘ +``` + +| 操作 | 接口 | 成功结果 | +|------|------|----------| +| 登录 | `POST /api/auth/login` | 保存 token,跳转首页或原目标页 | +| 注册 | `POST /api/auth/register` | 提示成功,切回登录或自动登录 | +| 退出 | 前端清除登录态 | 回到首页 | + +表单校验:用户名必填,密码至少 6 位,注册时手机号格式正确。 + +### 4.3 商品列表 `/products` + +**页面目标**:支持分类浏览、关键词搜索、分页查看商品。 + +```text +┌──────────────── 商品列表 ────────────────┐ +│ 分类:[全部][数码][服饰][食品] │ +│ 搜索:[输入关键词] [搜索] │ +├──────────────────────────────────────────┤ +│ ┌商品卡片┐ ┌商品卡片┐ ┌商品卡片┐ ┌商品卡片┐ │ +│ │图片 │ │图片 │ │图片 │ │图片 │ │ +│ │名称分类│ │名称分类│ │名称分类│ │名称分类│ │ +│ │¥价格 │ │¥价格 │ │¥价格 │ │¥价格 │ │ +│ │库存 │ │库存 │ │库存 │ │库存 │ │ +│ └详情按钮┘ └详情按钮┘ └详情按钮┘ └详情按钮┘ │ +├──────────────────────────────────────────┤ +│ 分页:上一页 1 2 3 下一页 │ +└──────────────────────────────────────────┘ +``` + +| 查询条件 | API 参数 | +|----------|----------| +| 页码 | `page` | +| 每页数量 | `pageSize` | +| 分类 | `categoryId` | +| 关键词 | `keyword` | + +### 4.4 商品详情 `/products/:id` + +**页面目标**:展示商品完整信息,并提供加入购物车入口。 + +```text +┌────────────── 商品详情 ──────────────┐ +│ 左侧:商品图片 │ +│ 右侧:商品名称 │ +│ 分类 / 价格 / 库存 │ +│ 数量选择 [-] [1] [+] │ +│ [加入购物车] [立即结算] │ +├─────────────────────────────────────┤ +│ 商品描述 │ +└─────────────────────────────────────┘ +``` + +| 状态 | 页面处理 | +|------|----------| +| 库存充足 | 可选择数量并加入购物车 | +| 库存为 0 | 禁用加购按钮,显示“暂时缺货” | +| 未登录加购 | 跳转 `/login` | +| 商品下架/不存在 | 显示错误状态并返回列表 | + +### 4.5 购物车 `/cart` + +**页面目标**:管理购物车并进入结算。 + +```text +┌──────────────── 购物车 ────────────────┐ +│ [全选] 商品信息 | 单价 | 数量 | 小计 | 操作 │ +│ ☑ 图片 名称 | 99 | [-]1[+] | 99 | 删除 │ +│ ☑ 图片 名称 | 49 | [-]2[+] | 98 | 删除 │ +├───────────────────────────────────────┤ +│ 已选 2 件 合计:¥197.00 [去结算] │ +└───────────────────────────────────────┘ +``` + +| 操作 | 接口 | +|------|------| +| 加载购物车 | `GET /api/cart` | +| 修改数量/勾选 | `PUT /api/cart/{id}` | +| 删除商品 | `DELETE /api/cart/{id}` | +| 去结算 | 前端跳转 `/checkout`,携带选中购物车项 | + +异常状态:购物车为空、商品下架、库存不足时显示明确提示,不允许无效商品结算。 + +### 4.6 确认订单 `/checkout` + +**页面目标**:选择地址,确认商品和金额,提交订单。 + +```text +┌────────────── 确认订单 ──────────────┐ +│ 收货地址:[默认地址信息] [更换/新增] │ +├─────────────────────────────────────┤ +│ 商品清单 │ +│ 商品名称 | 单价 | 数量 | 小计 │ +├─────────────────────────────────────┤ +│ 商品合计:¥xxx.xx │ +│ [提交订单] │ +└─────────────────────────────────────┘ +``` + +| 操作 | 接口 | +|------|------| +| 加载地址 | `GET /api/addresses` | +| 提交订单 | `POST /api/orders` | + +提交成功后跳转订单详情或订单列表,并显示支付按钮。 + +### 4.7 订单列表 `/orders` + +**页面目标**:查看订单状态,并对待支付订单执行支付。 + +```text +┌──────────────── 我的订单 ────────────────┐ +│ 状态筛选:[全部][待支付][已支付][已发货][完成] │ +│ 订单卡片:订单号 / 时间 / 金额 / 状态 │ +│ 商品摘要:商品图 + 名称 + 数量 │ +│ 操作:[去支付] [取消订单] [查看详情] │ +└─────────────────────────────────────────┘ +``` + +| 状态 | 可见操作 | +|------|----------| +| 待支付 `10` | 去支付、取消订单 | +| 已支付 `20` | 查看详情 | +| 已发货 `30` | 确认收货(后续实现) | +| 已完成 `40` | 查看详情 | +| 已取消 `90` | 查看详情 | + +### 4.8 订单详情 `/orders/:id` + +**页面目标**:展示订单完整链路。 + +```text +┌────────────── 订单详情 ──────────────┐ +│ 订单号 / 状态 / 创建时间 / 支付时间 │ +│ 收货地址快照 │ +│ 商品明细 │ +│ 金额汇总 │ +│ 操作区:[支付] [取消订单] [返回列表] │ +└─────────────────────────────────────┘ +``` + +## 五、后台页面原型 + +### 5.1 后台布局 `/admin` + +```text +┌────────────── 后台管理 ──────────────┐ +│ 左侧菜单: │ +│ - 数据概览 │ +│ - 商品管理 │ +│ - 订单管理 │ +│ - 用户管理 │ +├─────────────────────────────────────┤ +│ 右侧内容:统计卡片 / 表格 / 表单 │ +└─────────────────────────────────────┘ +``` + +后台页面必须校验管理员角色。普通会员访问时显示无权限。 + +### 5.2 数据概览 `/admin` + +| 卡片 | 字段 | 接口 | +|------|------|------| +| 用户数 | `users` | `GET /api/admin/dashboard` | +| 商品数 | `products` | `GET /api/admin/dashboard` | +| 订单数 | `orders` | `GET /api/admin/dashboard` | +| 销售额 | `salesAmount` | `GET /api/admin/dashboard` | + +### 5.3 后台商品管理 `/admin/products` + +```text +┌──────────── 商品管理 ────────────┐ +│ 搜索:[关键词] 分类:[下拉] 状态:[下拉] [查询] [新增] │ +│ 表格:图片 | 名称 | 分类 | 价格 | 库存 | 状态 | 操作 │ +│ 操作:[编辑] [上架/下架] │ +└─────────────────────────────────┘ +``` + +表单字段:分类、名称、描述、价格、库存、图片地址、状态。 + +### 5.4 后台订单管理 `/admin/orders` + +```text +┌──────────── 订单管理 ────────────┐ +│ 搜索:订单号/用户 状态:[下拉] [查询] │ +│ 表格:订单号 | 用户 | 金额 | 状态 | 时间 | 操作 │ +│ 操作:[查看] [发货] │ +└─────────────────────────────────┘ +``` + +发货规则:只有已支付订单可发货,调用 `POST /api/admin/orders/{id}/ship`。 + +### 5.5 后台用户管理 `/admin/users` + +```text +┌──────────── 用户管理 ────────────┐ +│ 搜索:用户名/手机号 状态:[下拉] [查询] │ +│ 表格:ID | 用户名 | 手机号 | 角色 | 状态 | 操作 │ +│ 操作:[禁用] [启用] │ +└─────────────────────────────────┘ +``` + +禁用规则:禁用后的用户不能登录,已登录用户后续请求应被拦截。 + +## 六、核心用户流程 + +### 6.1 购物下单流程 + +```mermaid +sequenceDiagram + participant U as 会员 + participant FE as 前端页面 + participant API as 后端API + U->>FE: 浏览商品列表/详情 + FE->>API: GET /api/products + U->>FE: 加入购物车 + FE->>API: POST /api/cart + U->>FE: 去结算 + FE->>API: POST /api/orders + API-->>FE: 返回订单号和金额 + U->>FE: 模拟支付 + FE->>API: POST /api/orders/{id}/pay + API-->>FE: 返回支付成功状态 +``` + +### 6.2 后台发货流程 + +```mermaid +sequenceDiagram + participant A as 管理员 + participant FE as 后台页面 + participant API as 后端API + A->>FE: 打开订单管理 + FE->>API: GET /api/admin/orders?status=20 + A->>FE: 点击发货 + FE->>API: POST /api/admin/orders/{id}/ship + API-->>FE: 订单状态变为已发货 +``` + +## 七、成员阅读指引 + +| 成员/模块 | 先读 | 再读 | 实现时重点 | +|-----------|------|------|------------| +| 组长/公共基建 | 系统架构设计 | 接口设计 | 路由守卫、HTTP 封装、统一响应、集成联调 | +| 用户购物车包 | 登录/注册、购物车原型 | 用户/地址/购物车接口 | 登录态、地址权限、购物车金额 | +| 商品包 | 首页、商品列表、商品详情、后台商品原型 | 商品/分类/后台商品接口 | 分页、搜索、上下架、库存展示 | +| 订单支付包 | 确认订单、订单列表、订单详情、后台订单原型 | 订单/支付接口 | 订单状态流转、库存、支付幂等 | +| 测试部署包 | 全部页面流程 | 接口清单和验收标准 | 用例覆盖、演示数据、部署验证 | + +## 八、原型验收清单 + +| 检查项 | 要求 | +|--------|------| +| 页面是否覆盖 P0 功能 | 首页、登录、商品、购物车、订单、后台均覆盖 | +| 页面路由是否明确 | 每个页面都有唯一前端路由 | +| API 路径是否统一 | 所有接口使用 `接口设计.md` 中路径 | +| 异常状态是否考虑 | 空数据、失败、未登录、无权限均有处理 | +| 成员是否能按模块阅读 | 每个模块都有页面、接口、负责范围说明 | diff --git "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\216\245\345\217\243\350\256\276\350\256\241.md" "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\216\245\345\217\243\350\256\276\350\256\241.md" index 72ecc90..32caa6b 100644 --- "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\216\245\345\217\243\350\256\276\350\256\241.md" +++ "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\216\245\345\217\243\350\256\276\350\256\241.md" @@ -1,81 +1,563 @@ # 接口设计文档 -> 组别:____ 编写人:____ 编写日期:____ 版本:v1.0 -> 截止:第 2 周周三(开发过程中持续更新,保持与代码一致) +> 班级:1 班 组号:6 组 编写人:全组维护 编写日期:2026-07-22 版本:v1.0 +> 本文档是前后端联调的唯一接口路径基准。新增或修改接口时,先更新本文档,再改代码。 ## 修订记录 | 版本 | 日期 | 修改人 | 修改说明 | |------|------|--------|----------| -| v1.0 | | | 初稿 | +| v1.0 | 2026-07-22 | 全组 | 统一 API 前缀、响应格式、分页格式、状态码和 P0 接口清单 | ## 一、通用约定 ### 1.1 基础信息 -- 接口前缀:`/api` -- 数据格式:JSON(`Content-Type: application/json`) -- 鉴权方式:(如 Header 携带 `Authorization: Bearer `) +| 项目 | 约定 | +|------|------| +| API 前缀 | `/api` | +| 本地后端地址 | `http://localhost:5081/api` | +| Docker 后端地址 | `http://localhost:8080/api` | +| 前端默认 baseURL | `/api` | +| 请求格式 | `Content-Type: application/json` | +| 响应格式 | JSON | +| 鉴权方式 | `Authorization: Bearer ` | + +前端 `frontend/src/api/http.ts` 已配置 `baseURL=/api`。因此前端封装接口时应写 `http.get('/products')`,不要写 `http.get('/api/products')`,避免出现 `/api/api/...`。 + +### 1.2 路径命名规则 -### 1.2 统一响应格式 +| 场景 | 规则 | 示例 | +|------|------|------| +| 资源集合 | 小写复数名词 | `GET /api/products` | +| 单个资源 | 集合后追加 `{id}` | `GET /api/products/{id}` | +| 创建资源 | `POST` 集合路径 | `POST /api/orders` | +| 更新资源 | `PUT` 或 `PATCH` 单资源路径 | `PUT /api/cart/{id}` | +| 删除资源 | `DELETE` 单资源路径 | `DELETE /api/cart/{id}` | +| 业务动作 | 单资源后追加动作名 | `POST /api/orders/{id}/pay` | +| 后台接口 | 全部放到 `/api/admin` 下 | `GET /api/admin/dashboard` | + +### 1.3 统一响应格式 + +后端统一使用 `ApiResponse`: ```json { - "code": 0, // 0 成功,非 0 失败 - "message": "ok", // 提示信息 - "data": {} // 业务数据,失败时可为 null + "code": 0, + "message": "ok", + "data": {}, + "traceId": "可选链路追踪ID" } ``` -### 1.3 通用错误码 +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `code` | number | 是 | `0` 表示成功,非 `0` 表示失败 | +| `message` | string | 是 | 用户可读提示,成功默认 `ok` | +| `data` | object/array/null | 否 | 业务数据,失败时可为 `null` | +| `traceId` | string/null | 否 | 异常排查用追踪 ID | -| code | 含义 | -|------|------| -| 0 | 成功 | -| 400 | 参数错误 | -| 401 | 未登录或登录过期 | -| 403 | 无权限 | -| 404 | 资源不存在 | -| 500 | 服务器内部错误 | +### 1.4 分页格式 + +请求参数统一: -### 1.4 分页约定 +| 参数 | 类型 | 必填 | 默认值 | 说明 | +|------|------|------|--------|------| +| `page` | number | 否 | `1` | 页码,从 1 开始 | +| `pageSize` | number | 否 | `10` | 每页数量,建议最大 50 | -请求参数:`page`(从 1 开始)、`pageSize`(默认 10) -响应 data 结构:`{ "list": [], "total": 0, "page": 1, "pageSize": 10 }` +分页响应统一放在 `data` 中: + +```json +{ + "code": 0, + "message": "ok", + "data": { + "list": [], + "total": 0, + "page": 1, + "pageSize": 10 + }, + "traceId": null +} +``` + +### 1.5 通用错误码 + +| code | 含义 | 常见场景 | +|------|------|----------| +| 0 | 成功 | 请求处理成功 | +| 400 | 参数错误 | 必填缺失、格式错误、库存不足 | +| 401 | 未登录或登录过期 | 缺少 token、token 无效 | +| 403 | 无权限 | 普通会员访问后台接口 | +| 404 | 资源不存在 | 商品、订单、地址不存在 | +| 409 | 业务冲突 | 重复注册、重复提交、状态不允许 | +| 500 | 服务端错误 | 未捕获异常、数据库异常 | + +### 1.6 状态码字典 + +| 类型 | code | 含义 | +|------|------|------| +| 用户角色 `role` | `1` | 会员 | +| 用户角色 `role` | `9` | 管理员 | +| 用户状态 `status` | `1` | 启用 | +| 用户状态 `status` | `0` | 禁用 | +| 商品状态 `status` | `1` | 上架 | +| 商品状态 `status` | `0` | 下架 | +| 订单状态 `status` | `10` | 待支付 | +| 订单状态 `status` | `20` | 已支付 | +| 订单状态 `status` | `30` | 已发货 | +| 订单状态 `status` | `40` | 已完成 | +| 订单状态 `status` | `90` | 已取消 | +| 支付状态 `status` | `10` | 待支付 | +| 支付状态 `status` | `20` | 支付成功 | +| 支付状态 `status` | `30` | 支付失败 | +| 支付状态 `status` | `40` | 已退款 | ## 二、接口清单 -| 编号 | 模块 | 接口名称 | 方法 | 路径 | 是否需登录 | -|------|------|----------|------|------|-----------| -| A01 | 用户 | 注册 | POST | /api/auth/register | 否 | -| A02 | 用户 | 登录 | POST | /api/auth/login | 否 | -| A03 | 商品 | 商品列表 | GET | /api/products | 否 | -| A04 | 商品 | 商品详情 | GET | /api/products/{id} | 否 | -| A05 | 购物车 | 加入购物车 | POST | /api/cart | 是 | -| ... | | | | | | +| 编号 | 模块 | 接口名称 | 方法 | 路径 | 登录 | 角色 | 状态 | +|------|------|----------|------|------|------|------|------| +| A00 | 基础 | 健康检查 | GET | `/api/health` | 否 | - | 已建 | +| A01 | 用户 | 注册 | POST | `/api/auth/register` | 否 | - | 已建 | +| A02 | 用户 | 登录 | POST | `/api/auth/login` | 否 | - | 已建 | +| A03 | 用户 | 当前用户信息 | GET | `/api/users/me` | 是 | 会员/管理员 | 待建 | +| A04 | 用户 | 修改当前用户 | PUT | `/api/users/me` | 是 | 会员/管理员 | 待建 | +| A05 | 地址 | 地址列表 | GET | `/api/addresses` | 是 | 会员 | 待建 | +| A06 | 地址 | 新增地址 | POST | `/api/addresses` | 是 | 会员 | 待建 | +| A07 | 地址 | 修改地址 | PUT | `/api/addresses/{id}` | 是 | 会员 | 待建 | +| A08 | 地址 | 删除地址 | DELETE | `/api/addresses/{id}` | 是 | 会员 | 待建 | +| A09 | 地址 | 设置默认地址 | POST | `/api/addresses/{id}/default` | 是 | 会员 | 待建 | +| A10 | 商品 | 商品列表 | GET | `/api/products` | 否 | - | 已建 | +| A11 | 商品 | 商品详情 | GET | `/api/products/{id}` | 否 | - | 已建 | +| A12 | 商品 | 分类列表 | GET | `/api/categories` | 否 | - | 待建 | +| A13 | 购物车 | 购物车列表 | GET | `/api/cart` | 是 | 会员 | 已建 | +| A14 | 购物车 | 加入购物车 | POST | `/api/cart` | 是 | 会员 | 已建 | +| A15 | 购物车 | 修改购物车数量/勾选 | PUT | `/api/cart/{id}` | 是 | 会员 | 待建 | +| A16 | 购物车 | 删除购物车项 | DELETE | `/api/cart/{id}` | 是 | 会员 | 待建 | +| A17 | 订单 | 订单列表 | GET | `/api/orders` | 是 | 会员 | 已建 | +| A18 | 订单 | 订单详情 | GET | `/api/orders/{id}` | 是 | 会员 | 待建 | +| A19 | 订单 | 创建订单 | POST | `/api/orders` | 是 | 会员 | 已建 | +| A20 | 订单 | 取消订单 | POST | `/api/orders/{id}/cancel` | 是 | 会员 | 待建 | +| A21 | 支付 | 模拟支付 | POST | `/api/orders/{id}/pay` | 是 | 会员 | 已建 | +| A22 | 后台 | 后台概览 | GET | `/api/admin/dashboard` | 是 | 管理员 | 已建 | +| A23 | 后台商品 | 后台商品列表 | GET | `/api/admin/products` | 是 | 管理员 | 待建 | +| A24 | 后台商品 | 新增商品 | POST | `/api/admin/products` | 是 | 管理员 | 待建 | +| A25 | 后台商品 | 修改商品 | PUT | `/api/admin/products/{id}` | 是 | 管理员 | 待建 | +| A26 | 后台商品 | 上下架商品 | PATCH | `/api/admin/products/{id}/status` | 是 | 管理员 | 待建 | +| A27 | 后台订单 | 后台订单列表 | GET | `/api/admin/orders` | 是 | 管理员 | 待建 | +| A28 | 后台订单 | 发货 | POST | `/api/admin/orders/{id}/ship` | 是 | 管理员 | 待建 | +| A29 | 后台用户 | 后台用户列表 | GET | `/api/admin/users` | 是 | 管理员 | 待建 | +| A30 | 后台用户 | 禁用/启用用户 | PATCH | `/api/admin/users/{id}/status` | 是 | 管理员 | 待建 | ## 三、接口详细定义 -> 每个接口按以下格式编写。 +### A00 健康检查 + +- **路径**:`GET /api/health` +- **请求参数**:无 +- **成功响应示例**: + +```json +{ + "code": 0, + "message": "ok", + "data": { + "status": "Healthy" + }, + "traceId": null +} +``` ### A01 用户注册 - **路径**:`POST /api/auth/register` -- **请求参数**: +- **请求体**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| -| username | string | 是 | 4~20 位字母数字 | -| password | string | 是 | 6~20 位 | -| phone | string | 否 | 手机号 | +| `username` | string | 是 | 4~20 位,唯一 | +| `password` | string | 是 | 6~20 位,后端加密存储 | +| `phone` | string | 否 | 手机号 | + +- **请求示例**: + +```json +{ + "username": "zhangsan", + "password": "123456", + "phone": "13800000000" +} +``` - **成功响应示例**: ```json -{ "code": 0, "message": "ok", "data": { "id": 1, "username": "zhangsan" } } +{ + "code": 0, + "message": "ok", + "data": { + "id": 1, + "username": "zhangsan", + "role": 1, + "status": 1 + }, + "traceId": null +} ``` -- **失败情形**:用户名已存在(code=400,message="用户名已存在") +- **失败情形**:用户名已存在、密码长度不足、手机号格式错误。 ### A02 用户登录 -(同上格式,逐个接口补全…) + +- **路径**:`POST /api/auth/login` +- **请求体**: + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `username` | string | 是 | 用户名 | +| `password` | string | 是 | 密码 | + +- **成功响应示例**: + +```json +{ + "code": 0, + "message": "ok", + "data": { + "token": "JWT_TOKEN", + "user": { + "id": 1, + "username": "zhangsan", + "role": 1 + } + }, + "traceId": null +} +``` + +- **失败情形**:账号不存在、密码错误、账号被禁用。 + +### A03 当前用户信息 + +- **路径**:`GET /api/users/me` +- **请求头**:`Authorization: Bearer ` +- **成功响应 data**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | number | 用户 ID | +| `username` | string | 用户名 | +| `phone` | string/null | 手机号 | +| `role` | number | `1` 会员、`9` 管理员 | +| `status` | number | `1` 启用、`0` 禁用 | + +### A04 修改当前用户 + +- **路径**:`PUT /api/users/me` +- **请求体**: + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `phone` | string | 否 | 手机号 | + +- **成功响应 data**:更新后的用户信息。 + +### A05-A09 地址接口 + +#### 地址对象 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | number | 地址 ID | +| `receiverName` | string | 收货人 | +| `phone` | string | 收货手机号 | +| `province` | string | 省份 | +| `city` | string | 城市 | +| `district` | string | 区县 | +| `detail` | string | 详细地址 | +| `isDefault` | boolean | 是否默认地址 | + +#### 新增/修改地址请求体 + +```json +{ + "receiverName": "张三", + "phone": "13800000000", + "province": "广东省", + "city": "广州市", + "district": "天河区", + "detail": "中山大道 1 号", + "isDefault": true +} +``` + +### A10 商品列表 + +- **路径**:`GET /api/products` +- **查询参数**: + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `page` | number | 否 | 默认 `1` | +| `pageSize` | number | 否 | 默认 `10` | +| `categoryId` | number | 否 | 分类 ID | +| `keyword` | string | 否 | 商品名称/描述关键词 | + +- **成功响应 data.list 项**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | number | 商品 ID | +| `name` | string | 商品名称 | +| `categoryName` | string | 分类名称 | +| `price` | number | 售价 | +| `stock` | number | 库存 | +| `imageUrl` | string/null | 商品图片 | + +### A11 商品详情 + +- **路径**:`GET /api/products/{id}` +- **成功响应 data**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | number | 商品 ID | +| `name` | string | 商品名称 | +| `categoryName` | string | 分类名称 | +| `description` | string | 商品描述 | +| `price` | number | 售价 | +| `stock` | number | 库存 | +| `imageUrl` | string/null | 商品图片 | +| `status` | number | 上下架状态 | + +### A12 分类列表 + +- **路径**:`GET /api/categories` +- **成功响应 data**: + +```json +[ + { "id": 1, "name": "数码配件", "sortOrder": 10 } +] +``` + +### A13 购物车列表 + +- **路径**:`GET /api/cart` +- **成功响应 data**: + +```json +{ + "items": [ + { + "id": 1, + "productId": 1, + "productName": "演示商品", + "price": 99.00, + "quantity": 2, + "stock": 100, + "selected": true, + "imageUrl": "/placeholder-product.png", + "subtotal": 198.00 + } + ], + "totalAmount": 198.00 +} +``` + +### A14 加入购物车 + +- **路径**:`POST /api/cart` +- **请求体**: + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `productId` | number | 是 | 商品 ID | +| `quantity` | number | 是 | 加购数量,必须大于 0 | + +- **失败情形**:未登录、商品不存在、商品下架、库存不足。 + +### A15 修改购物车数量/勾选 + +- **路径**:`PUT /api/cart/{id}` +- **请求体**: + +```json +{ + "quantity": 3, + "selected": true +} +``` + +- **说明**:`quantity` 和 `selected` 可按需传递;数量不能小于 1。 + +### A16 删除购物车项 + +- **路径**:`DELETE /api/cart/{id}` +- **成功响应 data**:`true` + +### A17 订单列表 + +- **路径**:`GET /api/orders` +- **查询参数**: + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `page` | number | 否 | 默认 `1` | +| `pageSize` | number | 否 | 默认 `10` | +| `status` | number | 否 | 订单状态 | + +- **成功响应 data.list 项**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | number | 订单 ID | +| `orderNo` | string | 订单号 | +| `totalAmount` | number | 订单金额 | +| `status` | number | 订单状态 | +| `createdAt` | string | 创建时间 | +| `items` | array | 订单商品摘要 | + +### A18 订单详情 + +- **路径**:`GET /api/orders/{id}` +- **成功响应 data**:订单基础信息、订单项、收货地址、支付记录。 +- **权限**:会员只能查看自己的订单;管理员通过后台订单接口查看全部订单。 + +### A19 创建订单 + +- **路径**:`POST /api/orders` +- **请求体**: + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `addressId` | number | 是 | 收货地址 ID | +| `cartItemIds` | number[] | 是 | 要结算的购物车项 ID | + +- **成功响应示例**: + +```json +{ + "code": 0, + "message": "ok", + "data": { + "id": 1001, + "orderNo": "202607220001", + "totalAmount": 198.00, + "status": 10 + }, + "traceId": null +} +``` + +- **失败情形**:地址不存在、购物车为空、库存不足、重复提交。 + +### A20 取消订单 + +- **路径**:`POST /api/orders/{id}/cancel` +- **业务规则**:只有待支付订单可取消;取消后订单状态为 `90` 并回补库存。 + +### A21 模拟支付 + +- **路径**:`POST /api/orders/{id}/pay` +- **业务规则**: + - 只有待支付订单可支付; + - 重复支付同一订单应幂等返回支付成功; + - 支付成功后订单状态为 `20`,支付状态为 `20`。 +- **成功响应示例**: + +```json +{ + "code": 0, + "message": "ok", + "data": { + "orderId": 1001, + "orderNo": "202607220001", + "paymentNo": "PAY202607220001", + "orderStatus": 20, + "paymentStatus": 20 + }, + "traceId": null +} +``` + +### A22 后台概览 + +- **路径**:`GET /api/admin/dashboard` +- **权限**:管理员 +- **成功响应 data**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `users` | number | 用户数 | +| `products` | number | 商品数 | +| `orders` | number | 订单数 | +| `salesAmount` | number | 销售额 | + +### A23-A26 后台商品接口 + +#### 后台商品查询 + +- **路径**:`GET /api/admin/products` +- **查询参数**:`page`、`pageSize`、`keyword`、`categoryId`、`status` + +#### 新增/修改商品请求体 + +```json +{ + "categoryId": 1, + "name": "无线鼠标", + "description": "轻量办公鼠标", + "price": 99.00, + "stock": 100, + "imageUrl": "/images/mouse.png", + "status": 1 +} +``` + +#### 上下架商品 + +- **路径**:`PATCH /api/admin/products/{id}/status` +- **请求体**: + +```json +{ + "status": 0 +} +``` + +### A27-A28 后台订单接口 + +- **列表路径**:`GET /api/admin/orders` +- **查询参数**:`page`、`pageSize`、`status`、`orderNo`、`username` +- **发货路径**:`POST /api/admin/orders/{id}/ship` +- **发货规则**:只有已支付订单可发货,发货后状态变为 `30`。 + +### A29-A30 后台用户接口 + +- **列表路径**:`GET /api/admin/users` +- **查询参数**:`page`、`pageSize`、`keyword`、`status` +- **禁用/启用路径**:`PATCH /api/admin/users/{id}/status` +- **请求体**: + +```json +{ + "status": 0 +} +``` + +- **业务规则**:禁用用户后,登录接口必须拒绝该用户登录。 + +## 四、联调检查清单 + +| 检查项 | 要求 | +|--------|------| +| 前端是否重复写 `/api` | 不允许,Axios baseURL 已经包含 `/api` | +| 后端 Controller 路径 | 必须与本文档一致 | +| 响应结构 | 必须包含 `code`、`message`、`data`、`traceId` | +| 分页字段 | 必须使用 `list`、`total`、`page`、`pageSize` | +| 登录接口 | 成功必须返回 token 和用户角色 | +| 受保护接口 | 必须校验 token | +| 后台接口 | 必须校验管理员角色 | +| 状态码 | 必须使用本文档状态码字典 | diff --git "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\225\260\346\215\256\345\272\223\350\256\276\350\256\241.md" "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\225\260\346\215\256\345\272\223\350\256\276\350\256\241.md" index 2e77590..ef51110 100644 --- "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\225\260\346\215\256\345\272\223\350\256\276\350\256\241.md" +++ "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\225\260\346\215\256\345\272\223\350\256\276\350\256\241.md" @@ -1,74 +1,289 @@ # 数据库设计 -> 组别:____ 编写人:____ 编写日期:____ 版本:v1.0 -> 截止:第 1 周周五 +> 班级:1 班 组号:6 组 编写人:全组维护 编写日期:2026-07-22 版本:v1.0 +> 本文档与 `backend/sql/schema.sql` 保持一致。数据库变更需先更新本文档和 SQL,再更新实体和接口。 ## 修订记录 | 版本 | 日期 | 修改人 | 修改说明 | |------|------|--------|----------| -| v1.0 | | | 初稿 | +| v1.0 | 2026-07-22 | 全组 | 完成核心 8 张表设计、状态码、索引和 ER 关系 | ## 一、设计说明 -- 数据库类型/版本: -- 字符集:建议 utf8mb4 -- 命名规范:表名小写下划线(如 `order_item`),主键统一 `id`,均含 `created_at` / `updated_at` +| 项目 | 设计 | +|------|------| +| 数据库 | MySQL 8.0 | +| 数据库名 | `eshop` | +| 字符集 | `utf8mb4` | +| 排序规则 | `utf8mb4_unicode_ci` | +| 表命名 | 小写下划线,如 `user_account`、`order_item` | +| 主键 | 每张表统一 `id BIGINT AUTO_INCREMENT` | +| 时间字段 | 主要业务表统一 `created_at`、`updated_at` | +| 金额字段 | `DECIMAL(10,2)`,不使用浮点数 | +| 删除策略 | 商品、用户优先使用状态字段,不轻易物理删除历史数据 | ## 二、ER 图 -(用 Mermaid 或图片描述核心实体关系:用户、地址、分类、商品、购物车、订单、订单项、支付记录) - ```mermaid erDiagram - USER ||--o{ ADDRESS : has - USER ||--o{ CART_ITEM : has - USER ||--o{ ORDER : places + USER_ACCOUNT ||--o{ ADDRESS : has + USER_ACCOUNT ||--o{ CART_ITEM : owns + USER_ACCOUNT ||--o{ ORDERS : places CATEGORY ||--o{ PRODUCT : contains - PRODUCT ||--o{ CART_ITEM : in - ORDER ||--|{ ORDER_ITEM : contains - PRODUCT ||--o{ ORDER_ITEM : in - ORDER ||--o| PAYMENT : has + PRODUCT ||--o{ CART_ITEM : added_to + ORDERS ||--|{ ORDER_ITEM : contains + PRODUCT ||--o{ ORDER_ITEM : snapshot_from + ORDERS ||--o| PAYMENT : paid_by + + USER_ACCOUNT { + bigint id PK + varchar username + varchar password_hash + tinyint role + tinyint status + } + + PRODUCT { + bigint id PK + bigint category_id FK + varchar name + decimal price + int stock + tinyint status + } + + ORDERS { + bigint id PK + varchar order_no + bigint user_id FK + bigint address_id FK + decimal total_amount + tinyint status + } ``` -## 三、表结构设计 +## 三、状态码说明 + +| 类型 | 字段 | code | 含义 | +|------|------|------|------| +| 用户角色 | `user_account.role` | `1` | 会员 | +| 用户角色 | `user_account.role` | `9` | 管理员 | +| 用户状态 | `user_account.status` | `1` | 启用 | +| 用户状态 | `user_account.status` | `0` | 禁用 | +| 商品状态 | `product.status` | `1` | 上架 | +| 商品状态 | `product.status` | `0` | 下架 | +| 订单状态 | `orders.status` | `10` | 待支付 | +| 订单状态 | `orders.status` | `20` | 已支付 | +| 订单状态 | `orders.status` | `30` | 已发货 | +| 订单状态 | `orders.status` | `40` | 已完成 | +| 订单状态 | `orders.status` | `90` | 已取消 | +| 支付状态 | `payment.status` | `10` | 待支付 | +| 支付状态 | `payment.status` | `20` | 支付成功 | +| 支付状态 | `payment.status` | `30` | 支付失败 | +| 支付状态 | `payment.status` | `40` | 已退款 | + +## 四、表结构设计 + +### 4.1 用户表 `user_account` + +| 字段名 | 类型 | 允许空 | 默认值 | 说明 | +|--------|------|--------|--------|------| +| `id` | BIGINT | 否 | 自增 | 主键 | +| `username` | VARCHAR(50) | 否 | - | 用户名,唯一 | +| `password_hash` | VARCHAR(255) | 否 | - | 加密后的密码 | +| `phone` | VARCHAR(20) | 是 | NULL | 手机号 | +| `role` | TINYINT | 否 | 1 | `1` 会员,`9` 管理员 | +| `status` | TINYINT | 否 | 1 | `1` 启用,`0` 禁用 | +| `created_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 创建时间 | +| `updated_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 更新时间,自动更新 | + +索引: + +- 主键:`PRIMARY KEY (id)` +- 唯一索引:`uk_user_account_username(username)` + +设计说明:密码字段只存哈希值,不存明文。后台禁用用户时修改 `status`,不删除用户。 -> 每张表按以下格式编写。参考表数量:8~12 张。 +### 4.2 分类表 `category` -### 3.1 用户表(user) +| 字段名 | 类型 | 允许空 | 默认值 | 说明 | +|--------|------|--------|--------|------| +| `id` | BIGINT | 否 | 自增 | 主键 | +| `name` | VARCHAR(100) | 否 | - | 分类名称 | +| `sort_order` | INT | 否 | 0 | 排序值,越大越靠前或按页面约定 | +| `created_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 创建时间 | +| `updated_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 更新时间 | + +设计说明:商品必须归属一个分类。演示数据至少准备 3 个分类。 + +### 4.3 商品表 `product` | 字段名 | 类型 | 允许空 | 默认值 | 说明 | |--------|------|--------|--------|------| -| id | BIGINT | 否 | 自增 | 主键 | -| username | VARCHAR(50) | 否 | | 用户名,唯一 | -| password | VARCHAR(100) | 否 | | 加密后的密码 | -| phone | VARCHAR(20) | 是 | | 手机号 | -| status | TINYINT | 否 | 1 | 1正常 0禁用 | -| created_at | DATETIME | 否 | 当前时间 | 创建时间 | -| updated_at | DATETIME | 否 | 当前时间 | 更新时间 | +| `id` | BIGINT | 否 | 自增 | 主键 | +| `category_id` | BIGINT | 否 | - | 分类 ID,外键关联 `category.id` | +| `name` | VARCHAR(150) | 否 | - | 商品名称 | +| `description` | TEXT | 是 | NULL | 商品描述 | +| `price` | DECIMAL(10,2) | 否 | - | 售价 | +| `stock` | INT | 否 | 0 | 库存数量 | +| `image_url` | VARCHAR(500) | 是 | NULL | 商品图片地址 | +| `status` | TINYINT | 否 | 1 | `1` 上架,`0` 下架 | +| `created_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 创建时间 | +| `updated_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 更新时间 | + +索引: + +- `idx_product_category_id(category_id)` +- `idx_product_name(name)` +- 外键:`fk_product_category(category_id) -> category(id)` + +设计说明:前台只展示上架商品。已有订单引用的商品不建议物理删除,使用下架保护历史订单。 + +### 4.4 收货地址表 `address` + +| 字段名 | 类型 | 允许空 | 默认值 | 说明 | +|--------|------|--------|--------|------| +| `id` | BIGINT | 否 | 自增 | 主键 | +| `user_id` | BIGINT | 否 | - | 用户 ID | +| `receiver_name` | VARCHAR(50) | 否 | - | 收货人 | +| `phone` | VARCHAR(20) | 否 | - | 收货手机号 | +| `province` | VARCHAR(50) | 否 | - | 省份 | +| `city` | VARCHAR(50) | 否 | - | 城市 | +| `district` | VARCHAR(50) | 否 | - | 区县 | +| `detail` | VARCHAR(255) | 否 | - | 详细地址 | +| `is_default` | TINYINT | 否 | 0 | 是否默认地址,`1` 是,`0` 否 | +| `created_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 创建时间 | +| `updated_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 更新时间 | + +索引: + +- `idx_address_user_id(user_id)` +- 外键:`fk_address_user(user_id) -> user_account(id)` -索引:`uk_username(username)` 唯一索引 +设计说明:默认地址由业务逻辑保证同一用户最多一个。订单创建后建议保存地址快照或保证历史订单能展示当时地址。 + +### 4.5 购物车表 `cart_item` + +| 字段名 | 类型 | 允许空 | 默认值 | 说明 | +|--------|------|--------|--------|------| +| `id` | BIGINT | 否 | 自增 | 主键 | +| `user_id` | BIGINT | 否 | - | 用户 ID | +| `product_id` | BIGINT | 否 | - | 商品 ID | +| `quantity` | INT | 否 | 1 | 商品数量 | +| `selected` | TINYINT | 否 | 1 | 是否勾选结算 | +| `created_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 创建时间 | +| `updated_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 更新时间 | + +索引: + +- 唯一索引:`uk_cart_item_user_product(user_id, product_id)` +- `idx_cart_item_product_id(product_id)` +- 外键:`fk_cart_item_user(user_id) -> user_account(id)` +- 外键:`fk_cart_item_product(product_id) -> product(id)` + +设计说明:同一用户同一商品只保留一行,重复加购时累加 `quantity`。 + +### 4.6 订单表 `orders` + +| 字段名 | 类型 | 允许空 | 默认值 | 说明 | +|--------|------|--------|--------|------| +| `id` | BIGINT | 否 | 自增 | 主键 | +| `order_no` | VARCHAR(64) | 否 | - | 订单号,唯一 | +| `user_id` | BIGINT | 否 | - | 下单用户 ID | +| `address_id` | BIGINT | 否 | - | 收货地址 ID | +| `total_amount` | DECIMAL(10,2) | 否 | - | 订单总金额 | +| `status` | TINYINT | 否 | 10 | 订单状态 | +| `paid_at` | DATETIME | 是 | NULL | 支付时间 | +| `shipped_at` | DATETIME | 是 | NULL | 发货时间 | +| `completed_at` | DATETIME | 是 | NULL | 完成时间 | +| `created_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 创建时间 | +| `updated_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 更新时间 | + +索引: + +- 唯一索引:`uk_orders_order_no(order_no)` +- `idx_orders_user_id(user_id)` +- 外键:`fk_orders_user(user_id) -> user_account(id)` +- 外键:`fk_orders_address(address_id) -> address(id)` + +设计说明:订单状态流转为 `10 待支付 -> 20 已支付 -> 30 已发货 -> 40 已完成`,待支付订单可取消为 `90`。 + +### 4.7 订单项表 `order_item` + +| 字段名 | 类型 | 允许空 | 默认值 | 说明 | +|--------|------|--------|--------|------| +| `id` | BIGINT | 否 | 自增 | 主键 | +| `order_id` | BIGINT | 否 | - | 订单 ID | +| `product_id` | BIGINT | 否 | - | 商品 ID | +| `product_name` | VARCHAR(150) | 否 | - | 下单时商品名称快照 | +| `product_price` | DECIMAL(10,2) | 否 | - | 下单时商品价格快照 | +| `quantity` | INT | 否 | - | 购买数量 | +| `subtotal` | DECIMAL(10,2) | 否 | - | 小计金额 | + +索引: + +- `idx_order_item_order_id(order_id)` +- `idx_order_item_product_id(product_id)` +- 外键:`fk_order_item_order(order_id) -> orders(id)` +- 外键:`fk_order_item_product(product_id) -> product(id)` + +设计说明:订单项必须冗余商品名称和价格,因为商品后续改名、改价、下架都不应影响历史订单展示和金额。 + +### 4.8 支付记录表 `payment` + +| 字段名 | 类型 | 允许空 | 默认值 | 说明 | +|--------|------|--------|--------|------| +| `id` | BIGINT | 否 | 自增 | 主键 | +| `order_id` | BIGINT | 否 | - | 订单 ID | +| `payment_no` | VARCHAR(64) | 否 | - | 支付流水号,唯一 | +| `amount` | DECIMAL(10,2) | 否 | - | 支付金额 | +| `status` | TINYINT | 否 | 10 | 支付状态 | +| `paid_at` | DATETIME | 是 | NULL | 支付成功时间 | +| `created_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 创建时间 | +| `updated_at` | DATETIME | 否 | CURRENT_TIMESTAMP | 更新时间 | -### 3.2 商品分类表(category) -(同上格式…) +索引: -### 3.3 商品表(product) +- 唯一索引:`uk_payment_payment_no(payment_no)` +- 唯一索引:`uk_payment_order_id(order_id)` +- 外键:`fk_payment_order(order_id) -> orders(id)` -### 3.4 收货地址表(address) +设计说明:一个订单最多一条支付记录。模拟支付重复调用时应基于 `order_id` 幂等处理。 -### 3.5 购物车表(cart_item) +## 五、核心业务数据一致性 -### 3.6 订单表(order) +| 场景 | 规则 | +|------|------| +| 加入购物车 | 校验商品存在、上架、库存充足 | +| 创建订单 | 使用事务创建订单和订单项,并扣减或锁定库存 | +| 取消订单 | 仅待支付订单可取消,取消后回补库存 | +| 模拟支付 | 待支付订单才可支付,重复支付幂等返回成功 | +| 后台改价 | 不影响已生成订单项的 `product_price` | +| 后台下架 | 前台不再展示,但历史订单仍可查看商品快照 | +| 禁用用户 | 禁用后登录接口拒绝该用户登录 | -> 提示:订单状态建议字段 `status`:待支付/已支付/已发货/已完成/已取消,在此写明状态码含义。 +## 六、初始化脚本 -### 3.7 订单项表(order_item) +| 脚本 | 位置 | 说明 | +|------|------|------| +| 建库建表 | `backend/sql/schema.sql` | 创建 `eshop` 数据库和 8 张核心表 | +| 演示数据 | `backend/sql/data.sql` | 预置分类、商品、用户等演示数据 | -> 提示:订单项须冗余下单时的商品名称与价格快照,思考为什么。 +演示数据要求: -### 3.8 支付记录表(payment) +- 分类不少于 3 个; +- 商品不少于 30 个; +- 至少包含 1 个会员账号和 1 个管理员账号; +- 至少准备若干购物车、订单和支付数据,便于页面演示。 -## 四、初始化脚本 +## 七、后续扩展表建议 -- 建表脚本存放位置:`backend/sql/schema.sql`(或按各组结构) -- 演示数据脚本:`backend/sql/data.sql`(不少于 30 个商品、3 个分类) +| 功能 | 建议表 | 说明 | +|------|--------|------| +| 商品收藏 | `favorite_product` | 记录用户收藏商品 | +| 浏览历史 | `browse_history` | 记录用户最近浏览商品 | +| 商品评价 | `product_review` | 订单完成后评价商品 | +| 后台操作日志 | `admin_operation_log` | 记录管理员操作人、时间和内容 | +| 优惠券 | `coupon`、`user_coupon` | 支持选做优惠券规则 | +| 数据统计 | 可用 SQL 视图或统计表 | 支持后台看板和挑战模块 | diff --git "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241.md" "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241.md" index 75890df..271f7b8 100644 --- "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241.md" +++ "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241.md" @@ -1,59 +1,193 @@ # 系统架构设计 -> 组别:____ 编写人:____ 编写日期:____ 版本:v1.0 -> 截止:第 1 周周五 +> 班级:1 班 组号:6 组 编写人:全组维护 编写日期:2026-07-22 版本:v1.0 +> 本文档说明电子商城系统的整体架构、技术选型、模块边界、接口路径规范和协作约定。 ## 修订记录 | 版本 | 日期 | 修改人 | 修改说明 | |------|------|--------|----------| -| v1.0 | | | 初稿 | +| v1.0 | 2026-07-22 | 全组 | 完成架构、模块、路径规范和关键设计决策 | ## 一、技术选型及理由 -| 分层 | 选型 | 版本 | 选型理由 | -|------|------|------|----------| -| 前端框架 | | | | -| UI 组件库 | | | | -| 后端框架 | | | | -| 数据库 | | | | -| 缓存(可选) | | | | -| 部署方式 | | | | - -## 二、总体架构图 - -(用 Mermaid 或图片描述,至少体现:浏览器 → 前端应用 → 后端 API → 数据库 的调用关系) +| 分层 | 选型 | 版本/依据 | 选择理由 | +|------|------|-----------|----------| +| 前端框架 | Vue 3 + Vite + TypeScript | 见 `frontend/package.json` | 开发效率高,组件化清晰,适合中小型后台和商城页面 | +| UI 组件 | Element Plus | `^2.9.1` | 表单、表格、分页、弹窗等后台组件完善,减少重复开发 | +| 前端状态 | Pinia | `^3.0.1` | 管理登录态、用户信息、购物车数量等共享状态 | +| HTTP 客户端 | Axios | `^1.7.9` | 支持统一 baseURL、请求拦截、响应拦截和错误处理 | +| 后端框架 | ASP.NET Core Web API | `.NET 10` | Controller 模型清晰,适合构建 REST API 和 OpenAPI 文档 | +| 后端分层 | API / Application / Domain / Infrastructure | 当前仓库结构 | 分离接口、应用逻辑、领域模型和基础设施,便于多人协作 | +| 数据库 | MySQL | 8.0 | 关系型数据明确,适合订单、支付、库存等事务场景 | +| 鉴权 | JWT Bearer Token | 规划接入 | 前后端分离场景下便于无状态鉴权和角色校验 | +| 接口文档 | Markdown + OpenAPI | `/openapi/v1.json` | Markdown 便于团队评审,OpenAPI 便于调试 | +| 部署 | Docker Compose | `docker-compose.yml` | 一键启动 MySQL 和 API,便于验收部署 | + +## 二、总体架构 ```mermaid flowchart TB - U[浏览器] --> FE[前端应用] - FE -->|HTTP API| BE[后端服务] - BE --> DB[(数据库)] + U[浏览器用户] --> FE[Vue 前端应用] + FE -->|Axios /api| API[ASP.NET Core API] + API --> APP[Application 应用服务] + APP --> DOMAIN[Domain 领域实体/枚举] + APP --> INFRA[Infrastructure 数据访问] + INFRA --> DB[(MySQL 8.0)] + API --> OPENAPI[OpenAPI 文档] + ADMIN[管理员浏览器] --> FE ``` -## 三、模块划分 +### 2.1 调用链路 + +1. 用户在 Vue 页面发起操作。 +2. 页面调用 `frontend/src/api/*` 中封装的接口方法。 +3. Axios 实例统一使用 `baseURL=/api`,请求时不重复写 `/api` 前缀。 +4. 后端 Controller 使用 `[Route("api/...")]` 暴露接口。 +5. Controller 返回统一 `ApiResponse`。 +6. 前端根据 `code/message/data` 更新页面状态。 + +### 2.2 本地环境地址 + +| 服务 | 地址 | 说明 | +|------|------|------| +| 前端开发服务 | `http://localhost:5173` | Vite 默认地址 | +| 后端本地 API | `http://localhost:5081/api` | `dotnet run` 本地地址 | +| 后端 Docker API | `http://localhost:8080/api` | Docker Compose 暴露地址 | +| 健康检查 | `/api/health` | 判断后端是否启动 | +| OpenAPI | `/openapi/v1.json` | 后端自动生成接口描述 | + +## 三、统一接口路径规范 + +接口路径以 `docs/02-设计文档/接口设计.md` 为准,所有成员必须遵守以下规则: + +| 规则 | 约定 | 示例 | +|------|------|------| +| API 前缀 | 后端统一以 `/api` 开头 | `/api/products` | +| 前端调用 | Axios 已配置 `baseURL=/api`,调用时不写 `/api` | `http.get('/products')` | +| 资源命名 | 使用小写复数名词 | `/api/orders` | +| 详情路径 | 使用 `{id}` 作为资源标识 | `/api/products/{id}` | +| 动作路径 | 动作放在资源后面 | `/api/orders/{id}/pay` | +| 后台路径 | 统一放在 `/api/admin` 下 | `/api/admin/dashboard` | +| 分页参数 | 统一 `page`、`pageSize` | `/api/products?page=1&pageSize=10` | +| 筛选参数 | 使用清晰业务名 | `keyword`、`categoryId`、`status` | + +前端页面路由和后端 API 路径不是同一类路径。页面路由例如 `/products`,接口路径例如 `/api/products`。成员联调时必须先确认自己改的是页面路由还是 API 路径。 + +## 四、前端模块设计 + +### 4.1 目录结构 + +```text +frontend/src +├── api/ # 接口封装,统一走 http.ts +├── router/ # 页面路由 +├── stores/ # Pinia 状态,如 auth、cart +├── styles/ # 全局样式 +└── views/ # 页面视图,按业务模块分目录 +``` -### 3.1 前端模块结构 -(页面/路由划分、公共组件、状态管理方案) +### 4.2 页面路由规划 + +| 页面 | 路由 | 状态 | 说明 | +|------|------|------|------| +| 首页 | `/` | 已存在 | 展示入口、推荐商品和快捷导航 | +| 登录/注册 | `/login` | 已存在 | 登录与注册表单 | +| 商品列表 | `/products` | 已存在 | 分页、分类、搜索 | +| 商品详情 | `/products/:id` | 待补 | 商品图片、价格、库存、加购 | +| 购物车 | `/cart` | 已存在 | 商品勾选、改数量、删除、结算 | +| 确认订单 | `/checkout` | 待补 | 地址选择、订单确认 | +| 订单列表 | `/orders` | 已存在 | 订单状态筛选和支付入口 | +| 订单详情 | `/orders/:id` | 待补 | 订单项、地址、支付/取消 | +| 后台首页 | `/admin` | 已存在 | 统计概览和管理入口 | +| 后台商品管理 | `/admin/products` | 待补 | 商品/分类维护 | +| 后台订单管理 | `/admin/orders` | 待补 | 订单查询、发货 | +| 后台用户管理 | `/admin/users` | 待补 | 用户查询、禁用/启用 | + +### 4.3 前端状态与权限 + +| 状态 | 存储位置 | 用途 | +|------|----------|------| +| `eshop_token` | LocalStorage | 请求鉴权 | +| 当前用户 | Pinia `auth` store | 显示用户名、角色和路由权限 | +| 购物车数量 | Pinia 或接口实时获取 | 顶部购物车角标 | +| 搜索/分页条件 | 页面局部状态 | 商品列表、订单列表筛选 | + +## 五、后端模块设计 + +### 5.1 分层结构 + +```text +backend/src +├── Eshop.Api # Controller、Program、Middleware、OpenAPI +├── Eshop.Application # DTO、用例服务、统一响应、分页对象 +├── Eshop.Domain # Entity、Enum、领域约束 +└── Eshop.Infrastructure # 数据库连接、Repository、外部服务适配 +``` -### 3.2 后端模块结构 -(分层结构:控制层 / 服务层 / 数据访问层,各业务模块划分) +### 5.2 Controller 边界 -## 四、关键设计决策 +| Controller | 路径前缀 | 当前职责 | +|------------|----------|----------| +| `HealthController` | `/api/health` | 健康检查 | +| `AuthController` | `/api/auth` | 注册、登录 | +| `ProductsController` | `/api/products` | 商品列表、商品详情 | +| `CartController` | `/api/cart` | 购物车列表、加购 | +| `OrdersController` | `/api/orders` | 订单列表、创建订单、模拟支付 | +| `AdminController` | `/api/admin` | 后台概览 | -| 决策点 | 方案 | 备选方案 | 选择理由 | -|--------|------|----------|----------| -| 登录鉴权方式 | 例:Token | Session | | -| 图片存储方式 | | | | -| 支付模拟方案 | | | | +后续实现用户信息、地址、后台商品/订单/用户管理时,继续沿用接口设计文档中的路径,不临时自造路径。 -## 五、环境规划 +### 5.3 领域模型 -| 环境 | 用途 | 地址/说明 | -|------|------|-----------| -| 开发环境 | 本地开发 | | -| 演示/生产环境 | 验收演示 | | +| 实体/枚举 | 说明 | +|-----------|------| +| `UserAccount` | 用户账号,包含角色和状态 | +| `Category` | 商品分类 | +| `Product` | 商品基础信息、价格、库存、上下架状态 | +| `Address` | 用户收货地址 | +| `CartItem` | 用户购物车项 | +| `Order` / `OrderItem` | 订单主表和商品快照 | +| `Payment` | 模拟支付记录 | +| `OrderStatus` | `10` 待支付、`20` 已支付、`30` 已发货、`40` 已完成、`90` 已取消 | +| `PaymentStatus` | `10` 待支付、`20` 成功、`30` 失败、`40` 已退款 | +| `UserRole` | `1` 会员、`9` 管理员 | -## 六、代码规范约定 +## 六、关键设计决策 -(命名规范、目录规范、前后端各自的 Lint/格式化工具约定) +| 决策点 | 方案 | 备选方案 | 选择理由 | +|--------|------|----------|----------| +| 前后端通信 | REST API + JSON | GraphQL、服务端渲染 | 简单清晰,适合课程项目和多人联调 | +| 统一响应 | `ApiResponse` | 直接返回业务对象 | 便于前端统一处理成功、失败、追踪 ID | +| 分页结构 | `PagedResult` | 数组 + Header | 响应体自描述,前端分页组件易接入 | +| 鉴权 | JWT | Session | 前后端分离和 Docker 多实例更友好 | +| 密码存储 | BCrypt/强哈希 | 明文、MD5 | 满足安全验收,避免明文密码 | +| 支付 | 模拟支付接口 | 第三方支付 | 满足课程验收,不引入真实支付复杂度 | +| 商品删除 | 优先下架 | 物理删除 | 避免订单历史引用丢失 | +| 订单金额 | 下单时快照 | 每次实时查商品价格 | 保证历史订单不受后续改价影响 | + +## 七、部署与环境 + +| 环境 | 用途 | 启动方式 | +|------|------|----------| +| 本地开发 | 前后端独立调试 | `npm run dev` + `dotnet run` | +| Docker 本地 | 接近验收环境 | `docker compose up --build` | +| 演示环境 | 第 4 周验收 | 使用 Docker Compose 或服务器部署 | + +### 7.1 配置约定 + +- 后端数据库连接使用 `ConnectionStrings__DefaultConnection`。 +- JWT 配置使用 `Jwt__Issuer`、`Jwt__Audience`、`Jwt__SigningKey`。 +- CORS 允许前端地址 `http://localhost:5173` 和 `http://127.0.0.1:5173`。 +- 前端 API 地址通过 `VITE_API_BASE_URL` 覆盖,默认使用 `/api`。 + +## 八、代码协作规范 + +| 类型 | 规范 | +|------|------| +| 分支 | 功能开发使用 `feature/模块名`,完成后发起 Code Review | +| 提交 | 使用 `feat`、`fix`、`docs`、`test`、`refactor`、`chore` | +| 接口变更 | 先改 `接口设计.md`,再改 Controller 和前端 API 封装 | +| 页面变更 | 先对照 `原型设计.md`,再实现页面 | +| 数据库变更 | 先改 `数据库设计.md` 和 SQL,再改实体/服务 | +| 答辩准备 | 每个成员必须讲清本模块的页面、接口、库表和异常处理 | diff --git "a/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/10\344\270\252\345\267\245\344\275\234\346\227\245\345\274\200\345\217\221\345\210\206\345\267\245\350\256\241\345\210\222.md" "b/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/10\344\270\252\345\267\245\344\275\234\346\227\245\345\274\200\345\217\221\345\210\206\345\267\245\350\256\241\345\210\222.md" new file mode 100644 index 0000000..d580c9e --- /dev/null +++ "b/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/10\344\270\252\345\267\245\344\275\234\346\227\245\345\274\200\345\217\221\345\210\206\345\267\245\350\256\241\345\210\222.md" @@ -0,0 +1,130 @@ +# 10个工作日开发分工计划 + +> 适用场景:6 人小组,10 个工作日内完成电子商城核心功能、选做功能、挑战模块、测试部署和验收答辩准备。 +> 分工原则:按业务模块垂直负责,每位成员负责自己模块的数据库表、后端接口、前端页面、测试和答辩说明。 + +## 一、成员与模块对应 + +| 代号 | 角色 | 负责模块 | 难度 | +|------|------|----------|------| +| 成员A | 组长 / 架构员 | 公共基建、统一响应、鉴权、权限、公共组件、集成联调 | 高 | +| 成员B | 模块负责人 | 用户注册登录、个人信息、收货地址、后台用户管理 | 中高 | +| 成员C | 模块负责人 | 商品分类、商品列表、搜索、详情、后台商品管理 | 中高 | +| 成员D | 模块负责人 | 购物车、数量修改、删除、金额计算、结算入口 | 高 | +| 成员E | 模块负责人 | 订单、支付、后台订单管理、C08 支付回调幂等与对账 | 最高 | +| 成员F | 测试部署 / 质量负责人 | 测试计划、测试报告、Docker 部署、演示数据、X02 后台操作日志、挑战演示脚本 | 高 | + +## 二、10 个工作日任务安排 + +| 工作日 | 成员A:公共基建 | 成员B:用户模块 | 成员C:商品模块 | 成员D:购物车模块 | 成员E:订单支付模块 | 成员F:测试部署 | +|--------|------------------|------------------|------------------|--------------------|----------------------|------------------| +| 第 1 天 | 确认 C# + ASP.NET Core 技术栈;统一分支规范、提交规范、接口返回格式 | 梳理用户、地址、后台用户管理需求和表字段 | 梳理分类、商品、后台商品管理需求和表字段 | 梳理购物车业务流程、结算入口和接口依赖 | 梳理订单状态、支付状态、C08 挑战模块方案 | 建测试计划初稿;整理 F01-F13 验收清单 | +| 第 2 天 | 搭建后端分层结构;配置 CORS、OpenAPI、统一异常处理 | 设计注册、登录、退出、个人信息、地址管理接口 | 设计商品列表、分类筛选、搜索、详情、后台商品接口 | 设计加购、改数量、删除、勾选、结算接口 | 设计下单、订单列表、详情、取消、支付、后台订单接口 | 建测试用例模板;准备演示账号和演示数据清单 | +| 第 3 天 | 完成 JWT 鉴权基础、中间件、管理员权限判断 | 完成注册、登录、退出后端接口 | 完成分类、商品列表、关键词搜索、商品详情接口 | 完成购物车表结构和基础 CRUD 接口 | 完成订单表、订单项表、支付表和创建订单接口 | 补数据库初始化数据,保证 30+ 商品、3+ 分类 | +| 第 4 天 | 完成前端路由守卫、登录态管理、Axios 拦截器 | 完成登录页、注册页、个人信息页 | 完成商品列表页、搜索、分类筛选页面 | 完成购物车页面基础展示和勾选逻辑 | 完成订单列表、订单详情接口和基础页面 | 编写用户、商品模块测试用例 | +| 第 5 天 | 集成用户和商品模块,修复公共阻塞问题 | 完成收货地址增删改查;完成后台用户禁用/启用 | 完成商品详情页;完成后台商品管理页面 | 完成加购、改数量、删除、金额计算 | 完成取消订单、订单状态流转、后台订单查询 | 编写购物车、订单支付测试用例 | +| 第 6 天 | 完成管理端权限控制和后台入口统一 | 联调禁用用户后无法登录;补用户模块异常提示 | 联调商品上下架后前台不可购买 | 完成结算入口,联调购物车到订单创建 | 完成模拟支付、支付后订单状态变更、后台发货 | 完成部署说明初稿;检查 Docker Compose 启动链路 | +| 第 7 天 | 支持选做功能所需公共鉴权和接口规范 | 完成 X01 商品收藏、浏览历史的后端接口和个人中心入口 | 配合 X01 在商品详情页加入收藏、浏览记录入口 | 修复库存不足、商品下架、数量越界等边界问题 | 开始 C08 支付回调幂等接口和支付回调记录 | 完成 X02 后台操作日志表、日志写入和查询页面 | +| 第 8 天 | 组织全链路联调:登录、商品、购物车、下单、支付 | 修复用户模块 Bug;补用户模块实现说明 | 修复商品模块 Bug;补商品模块实现说明 | 修复购物车 Bug;补购物车模块实现说明 | 完成 C08 重复回调、乱序回调、对账 SQL | 准备 C08 边界演示脚本、接口调用记录和截图 | +| 第 9 天 | 组织全系统集成测试;统一处理阻塞 Bug | 跑用户相关测试,补日报和模块说明 | 跑商品相关测试,补日报和模块说明 | 跑购物车相关测试,补日报和模块说明 | 跑订单支付和 C08 测试,补模块说明 | 完成测试报告、缺陷记录、部署验证 | +| 第 10 天 | 控制最终演示流程;统一答辩口径;检查 Git 记录 | 准备用户模块答辩问题和演示步骤 | 准备商品模块答辩问题和演示步骤 | 准备购物车模块答辩问题和演示步骤 | 准备订单支付 + C08 原理答辩 | 汇总 PPT、演示脚本、验收材料、截图和压测记录 | + +## 三、每日交付物 + +| 工作日 | 当天必须产出 | +|--------|--------------| +| 第 1 天 | 分工确认、技术栈确认、模块字段草稿、验收清单拆解 | +| 第 2 天 | 接口设计初稿、页面入口设计、测试用例模板 | +| 第 3 天 | 鉴权基础、核心表结构、用户/商品/购物车/订单基础接口 | +| 第 4 天 | 登录、商品列表、购物车、订单页面雏形 | +| 第 5 天 | 用户、商品、购物车、订单主功能基本完成 | +| 第 6 天 | 后台入口、权限控制、模拟支付、部署说明初稿 | +| 第 7 天 | 选做功能 X01、X02 可演示版本;C08 初版接口 | +| 第 8 天 | 主流程完整跑通;C08 边界演示可运行 | +| 第 9 天 | 测试报告、缺陷闭环、部署验证记录 | +| 第 10 天 | 演示脚本、答辩问题、PPT 素材、最终验收材料 | + +## 四、关键里程碑 + +| 时间点 | 必须达到的状态 | 如果未完成的处理方式 | +|--------|----------------|----------------------| +| 第 2 天结束 | 数据库表、接口文档、页面入口基本定稿 | 暂停新增功能,优先统一接口,避免后续联调返工 | +| 第 5 天结束 | 登录、商品、购物车、下单、支付主链路基本可跑 | 所有人优先修主链路,选做功能顺延但不能超过第 7 天 | +| 第 7 天结束 | 至少 2 个选做功能有可演示版本 | 砍掉复杂交互,只保留最小可验收功能 | +| 第 8 天结束 | C08 能演示重复回调、乱序回调、对账差异 | 挑战模块只保留 C08,不再增加其他挑战 | +| 第 9 天结束 | 测试、部署、Bug 修复基本完成 | 第 10 天禁止大改功能,只做演示稳定性修复 | +| 第 10 天结束 | 全员能讲清自己模块的库表、接口、页面、边界问题 | 演示前按模块轮流抽问,答不上来的问题立刻补口径 | + +## 五、模块依赖关系 + +| 依赖方 | 被依赖方 | 依赖内容 | 最晚完成时间 | +|--------|----------|----------|--------------| +| 用户模块 | 公共基建 | 鉴权、统一响应、异常处理 | 第 3 天 | +| 商品模块 | 公共基建 | 统一接口格式、后台权限 | 第 3 天 | +| 购物车模块 | 用户模块、商品模块 | 当前用户、商品价格、库存、上下架状态 | 第 5 天 | +| 订单支付模块 | 用户模块、商品模块、购物车模块 | 地址、商品快照、库存、购物车结算数据 | 第 6 天 | +| 后台操作日志 | 公共基建、后台管理接口 | 管理员身份、操作对象、操作结果 | 第 7 天 | +| C08 挑战模块 | 订单支付模块 | 订单状态、支付记录、回调流水、对账数据 | 第 8 天 | +| 测试部署 | 全部模块 | 接口、页面、演示账号、演示数据 | 第 9 天 | + +## 六、验收风险控制 + +| 风险 | 影响 | 预防措施 | +|------|------|----------| +| 接口迟迟不定 | 前后端联调延期,购物车和订单被卡住 | 第 2 天必须冻结第一版接口,后续只允许小范围补充字段 | +| 只做前端或只做后端 | 不符合模块负责制,个人答辩容易扣分 | 每人都要提交自己模块的库表、接口、页面和说明文档 | +| 订单支付太晚完成 | 主流程无法演示,挑战模块无法开展 | 成员E 第 3 天开始写订单表和下单接口,第 6 天前完成模拟支付 | +| 选做功能过复杂 | 挤压测试和答辩时间 | 首选 X01 收藏/浏览历史、X02 后台操作日志,避免 SKU、售后等高复杂度功能 | +| 挑战模块讲不清 | C08 无法通过原理抽问 | 准备幂等、唯一约束、事务、状态机、对账 SQL 五个固定答辩点 | +| 部署环境不稳定 | 现场演示失败 | 第 6 天开始部署验证,第 9 天冻结部署环境,保留本地备份演示方案 | + +## 七、每日站会模板 + +每天下班前每位成员按下面格式同步一次: + +```text +姓名: +负责模块: +今天完成: +遇到问题: +明天计划: +需要谁配合: +今日提交记录: +``` + +示例: + +```text +姓名:成员D +负责模块:购物车模块 +今天完成:完成购物车列表接口和数量修改接口,前端页面已能展示商品名称、单价、数量、小计。 +遇到问题:结算时需要订单模块确认提交订单接口字段。 +明天计划:完成删除、勾选和金额汇总,并联调商品库存不足提示。 +需要谁配合:成员E 确认下单接口,成员C 确认商品下架字段。 +今日提交记录:feat: 添加购物车列表和数量修改接口 +``` + +## 八、最终演示顺序 + +1. 展示首页、商品列表、分类筛选、关键词搜索、商品详情。 +2. 注册新用户,登录,维护个人信息和收货地址。 +3. 收藏商品,查看浏览历史。 +4. 加入购物车,修改数量,删除商品,勾选结算。 +5. 提交订单,展示订单详情和订单项价格快照。 +6. 模拟支付,订单状态变为已支付。 +7. 后台登录,管理商品、发货订单、禁用用户,查看后台操作日志。 +8. 演示 C08:重复支付回调、乱序回调、对账差异。 +9. 展示测试报告、部署方式、Git 提交记录和 Code Review 记录。 + +## 九、正式验收前检查清单 + +- [ ] README 已填写真实姓名、学号、角色和负责模块。 +- [ ] 技术栈统一为 C# + ASP.NET Core Web API + MySQL,数据访问按 EF Core 接入计划说明。 +- [ ] F01-F13 必做功能全部有演示路径。 +- [ ] X01 商品收藏/浏览历史、X02 后台操作日志至少两个选做功能可演示。 +- [ ] C08 支付回调幂等与对账有边界演示脚本和原理说明。 +- [ ] docs/06-模块实现说明 下每个模块说明文档已补全。 +- [ ] 测试计划、测试报告、缺陷记录、部署说明已完成。 +- [ ] 每人至少准备 3 个自己模块问题和 1 个其他模块基础问题。 +- [ ] 每人 Git 提交记录和负责模块一致。 +- [ ] 第 10 天只修复演示稳定性问题,不再新增高风险功能。 diff --git "a/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/6\344\272\272\345\210\206\345\267\245\344\270\216\351\252\214\346\224\266\345\207\206\345\244\207.md" "b/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/6\344\272\272\345\210\206\345\267\245\344\270\216\351\252\214\346\224\266\345\207\206\345\244\207.md" new file mode 100644 index 0000000..763656d --- /dev/null +++ "b/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/6\344\272\272\345\210\206\345\267\245\344\270\216\351\252\214\346\224\266\345\207\206\345\244\207.md" @@ -0,0 +1,236 @@ +# 6人分工与验收准备 + +> 适用场景:6 人小组,按业务模块垂直负责。每个人都要能讲清自己模块的库表、接口、页面、关键代码和边界处理。 + +## 一、推荐分工总表 + +| 成员 | 角色 | 负责模块 | 必须交付 | 答辩重点 | +|------|------|----------|----------|----------| +| 成员A | 组长 / 架构员 | 公共基建、技术栈统一、鉴权、统一响应、公共组件、集成联调 | 项目启动、路由/权限、统一异常、接口规范、联调清单、最终演示脚本 | 为什么前后端分离、JWT/Session 如何选型、统一响应和异常如何设计 | +| 成员B | 用户模块负责人 | 用户注册、登录、退出、个人信息、收货地址、后台用户管理 | 用户表、地址表、登录接口、地址 CRUD、禁用/启用用户 | 密码为什么不能明文、登录态怎么保持、禁用用户为什么不能登录 | +| 成员C | 商品模块负责人 | 分类、商品列表、关键词搜索、商品详情、后台商品管理 | 分类/商品表、分页搜索接口、详情页、后台增删改查、上下架 | 分页参数怎么防异常、商品下架后前台如何处理、搜索如何实现 | +| 成员D | 购物车模块负责人 | 加购、修改数量、删除、勾选、结算页 | 购物车表、购物车接口、金额计算、库存校验、结算入口 | 为什么购物车按 user_id 隔离、数量修改如何防越界、金额为什么以后端为准 | +| 成员E | 订单支付负责人 | 提交订单、订单列表/详情/取消、模拟支付、后台订单管理、挑战模块 C08 | 订单表、订单项快照、支付表、状态流转、支付回调幂等、对账查询 | 为什么订单项要保存商品快照、库存扣减事务怎么保证一致、支付回调为什么要幂等 | +| 成员F | 测试部署 / 质量负责人 | 测试计划、测试用例、测试报告、Docker 部署、演示数据、选做功能 X02 | 40+ 测试用例、缺陷闭环、部署说明、压测/边界演示脚本、后台操作日志 | 测试如何覆盖正常/异常场景、部署链路怎么跑、日志如何追责 | + +> 如果成员能力有明显差异:订单支付和公共基建给较强成员;购物车和测试部署成员要参与联调,不要只写文档。 + +## 二、模块边界约定 + +| 边界 | 负责人 | 约定 | +|------|--------|------| +| 鉴权与当前用户 | 成员A | 统一提供登录态解析,其他模块只读取当前 user_id,不重复写鉴权逻辑 | +| 用户与地址 | 成员B | 订单只能引用有效 address_id,不能直接修改用户地址数据 | +| 商品价格与库存 | 成员C | 商品模块负责维护当前价格、库存、上下架;订单下单时保存价格快照 | +| 购物车结算 | 成员D | 购物车只负责选择商品和数量校验,真正生成订单由订单模块负责 | +| 订单状态 | 成员E | 订单状态只能通过订单服务流转,后台发货、支付回调都走统一状态机 | +| 测试与部署数据 | 成员F | 统一维护演示账号、30+ 商品、测试用例、压测脚本和验收演示顺序 | + +## 三、推荐选做功能 + +### X01 商品收藏、浏览历史 + +**推荐负责人:成员B 主责,成员C 配合。** + +推荐原因: + +- 实现成本低,只需要围绕用户和商品增加关系表。 +- 演示效果直观:商品详情页点击收藏,个人中心查看收藏;浏览商品后出现历史记录。 +- 容易答辩:能讲清唯一约束、分页、登录用户隔离。 +- 后续可以给“猜你喜欢”挑战模块复用行为数据,但不强依赖推荐算法。 + +建议范围: + +| 功能 | 最小可验收实现 | +|------|----------------| +| 收藏商品 | 登录用户可收藏/取消收藏,重复收藏不会产生重复数据 | +| 我的收藏 | 按时间倒序分页展示收藏商品 | +| 浏览历史 | 用户访问商品详情时记录最近浏览 | +| 历史清理 | 支持删除单条或清空历史 | + +建议表: + +- `user_favorite(id, user_id, product_id, created_at)`,加唯一约束 `(user_id, product_id)`。 +- `browse_history(id, user_id, product_id, viewed_at)`,按 `(user_id, viewed_at)` 建索引。 + +答辩速答: + +| 可能问题 | 推荐回答 | +|----------|----------| +| 如何避免重复收藏? | 在数据库加 `UNIQUE(user_id, product_id)`,接口层也先查询;即使并发重复请求,数据库约束仍能兜底。 | +| 未登录用户能收藏吗? | 不允许,收藏属于用户资产,必须通过登录态拿到 user_id。 | +| 浏览历史越来越多怎么办? | 查询时分页;也可以只保留最近 N 条,或定期清理超过指定时间的数据。 | +| 商品下架后收藏里怎么显示? | 可以保留记录,但前端标记“已下架”,点击时不允许购买。 | + +### X02 后台操作日志 + +**推荐负责人:成员F 主责,成员A 配合公共中间件,B/C/E 接入关键后台操作。** + +推荐原因: + +- 和后台管理天然相关,能增强材料质量和答辩可信度。 +- 不影响核心购物链路,即使时间紧也能渐进接入。 +- 验收问题好回答:谁、什么时间、对什么资源、做了什么、结果如何。 + +建议范围: + +| 功能 | 最小可验收实现 | +|------|----------------| +| 商品管理日志 | 新增、修改、上下架商品时记录 | +| 订单管理日志 | 发货、取消、状态变更时记录 | +| 用户管理日志 | 禁用/启用用户时记录 | +| 日志查询 | 后台按操作人、模块、时间分页查询 | + +建议表: + +- `admin_operation_log(id, admin_id, action, resource_type, resource_id, before_json, after_json, result, ip, created_at)`。 + +答辩速答: + +| 可能问题 | 推荐回答 | +|----------|----------| +| 怎么知道是谁操作的? | 后台接口必须登录,服务端从 JWT/登录态中解析 admin_id,不能相信前端传来的用户名。 | +| 怎么知道改了什么? | 修改前读取旧数据作为 before,修改后保存 after,核心字段用 JSON 快照记录。 | +| 日志失败会不会影响主流程? | 核心操作成功后写日志;若日志是强审计要求可放同一事务,课程项目可先记录失败并告警。 | +| 普通用户能看日志吗? | 不能,日志查询接口只允许管理员角色访问。 | + +### 备选选做功能 + +| 功能 | 适合情况 | 风险 | +|------|----------|------| +| 商品评价与晒图 | 前端展示时间充足,想让演示更像真实商城 | 需要校验“已购买/已完成订单后才能评价”,图片上传会增加部署复杂度 | +| 站内消息通知 | 订单状态变更已经做好 | 如果做实时推送会升级到 WebSocket,复杂度上升 | +| 售后流程 | 订单模块完成较早 | 退款状态和库存/支付关系容易讲不清 | +| 多规格 SKU | 商品、购物车、订单都很强 | 会影响库存模型、下单逻辑、页面交互,不建议作为首选 | + +## 四、推荐挑战模块 + +### 首选:C08 支付回调幂等与对账 + +**推荐负责人:成员E 主责,成员F 负责边界脚本和验收记录。** + +推荐原因: + +- 和订单支付模块强相关,不需要额外引入复杂中间件。 +- 现场边界演示非常清楚:重复回调、乱序回调、异常对账。 +- 原理抽问容易讲清:唯一约束、状态机、事务、条件更新、对账 SQL。 + +最小可验收范围: + +| 能力 | 做法 | +|------|------| +| 重复回调幂等 | `payment_no` 或 `callback_no` 加唯一约束,重复请求返回同一处理结果 | +| 乱序回调保护 | 订单已支付后,失败回调不能把订单改回失败 | +| 状态机控制 | 订单只允许 `待支付 -> 已支付`、`待支付 -> 已取消` 等合法流转 | +| 事务一致性 | 更新支付记录和订单状态放在同一事务中 | +| 对账检查 | 查询“支付成功但订单未支付 / 金额不一致 / 有支付无订单”等差异 | + +建议补充字段: + +- `payment.callback_no`:模拟第三方回调流水号,唯一。 +- `payment.raw_payload`:保存回调原文,方便排查。 +- `payment.processed_at`:回调处理时间。 + +现场边界演示脚本: + +| 步骤 | 演示内容 | 预期结果 | +|------|----------|----------| +| 1 | 创建一笔待支付订单 | 订单状态为待支付,生成 payment_no | +| 2 | 连续发送 5 次相同“支付成功回调” | 第一次更新订单为已支付,后 4 次返回“已处理”,订单金额和状态不重复变化 | +| 3 | 在已支付后发送“支付失败回调” | 系统拒绝回退状态,订单仍为已支付 | +| 4 | 构造一条支付成功但订单未更新的数据 | 对账接口/SQL 能查出差异 | +| 5 | 展示支付表、订单表、对账结果 | 能证明数据没有重复入账,异常能被发现 | + +答辩速答: + +| 可能问题 | 推荐回答 | +|----------|----------| +| 什么是幂等? | 同一个请求执行一次或多次,最终业务结果一致。支付回调可能重复到达,所以必须保证重复成功回调不会重复改订单、重复扣库存或重复记账。 | +| 为什么只靠前端按钮防重复不够? | 回调来自第三方或模拟支付服务,网络重试、用户刷新、服务超时都会导致重复请求,必须在服务端和数据库层兜底。 | +| 如何保证不会重复处理? | 用唯一约束识别同一回调或同一支付单;处理时在事务中查询当前状态,只允许合法状态流转。 | +| 乱序回调怎么处理? | 状态机只允许状态向前流转,例如已支付不能被失败回调改成失败;非法流转记录日志并返回当前状态。 | +| 为什么要对账? | 即使接口有事务,也可能出现人工改库、异常中断、消息丢失等问题;对账能发现支付记录和订单状态不一致。 | +| 对账 SQL 查什么? | 查支付成功但订单未支付、订单已支付但无成功支付记录、支付金额和订单金额不一致这三类核心差异。 | + +### 备选挑战:C10 容器化部署与负载均衡 + +**适合成员A/F 有时间时做加分项。** + +可演示内容: + +- Docker Compose 启动 MySQL、前端、两个后端实例、Nginx。 +- 连续请求 `/api/health`,响应中返回实例名,证明轮询到不同后端。 +- 用简单压测工具请求 Nginx 入口,展示成功率和平均响应时间。 + +答辩核心: + +- 登录态使用 JWT 时,后端实例无状态,请求打到任意实例都能验签。 +- 多实例共享同一个 MySQL,业务数据一致。 +- Nginx 负责反向代理和负载均衡,不负责业务逻辑。 + +## 五、不建议作为首选的挑战模块 + +| 挑战 | 不建议原因 | +|------|------------| +| C01 秒杀与防超卖 | 很容易被追问事务隔离级别、锁粒度、库存回滚、压测数据,做不好会被看出问题 | +| C02 优惠券规则引擎 | 分摊、退款、叠加规则细节多,验收边界复杂 | +| C04 商品搜索进阶 | 如果不用搜索引擎,自实现倒排索引要讲清分词和性能;如果用搜索引擎,部署成本上升 | +| C05 猜你喜欢 | 容易被问算法依据和冷启动,若只是硬编码推荐会显得薄 | +| C07 缓存与性能优化 | 必须讲清缓存一致性,改价后何时生效、如何失效,容易和 Redis 部署耦合 | + +## 六、每人必须准备的答辩口径 + +| 成员 | 必背问题 | 答题要点 | +|------|----------|----------| +| 成员A | 项目整体架构是什么? | 前端 Vue 调 API,后端分层处理业务,数据库持久化;公共鉴权、统一响应、异常处理由基建统一提供。 | +| 成员A | 为什么要统一接口返回格式? | 前端可以统一处理成功、失败、未登录、无权限;也便于测试和错误定位。 | +| 成员A | 当前技术栈到底是什么? | 前端 Vue 3 + Vite + TypeScript + Element Plus,后端 C# + ASP.NET Core Web API,数据库 MySQL,数据访问后续接入 EF Core;答辩时以代码和 README 的 C# 口径为准。 | +| 成员B | 密码如何存储? | 不存明文,使用 BCrypt/PBKDF2 等不可逆哈希,并说明登录时是“输入密码哈希后比对”。 | +| 成员B | 地址默认值如何保证只有一个? | 设置新默认地址时,在事务中先把该用户其他地址改为非默认,再把当前地址设为默认。 | +| 成员B | 禁用用户后如何阻止登录? | 登录时校验 status;已登录用户访问接口时也可在鉴权中二次校验用户状态。 | +| 成员C | 商品分页怎么做? | 接收 page/pageSize,校验范围,SQL 使用 LIMIT/OFFSET 或 ORM 分页,并返回 total。 | +| 成员C | 商品下架后购物车/订单怎么处理? | 购物车可展示但结算时禁止下单;历史订单保留商品快照,不受下架影响。 | +| 成员C | 搜索怎么实现? | 最小实现用名称 LIKE 和分类筛选;如后续优化可加索引或搜索引擎。 | +| 成员D | 购物车金额为什么不能只信前端? | 前端价格可被篡改,后端结算时必须重新查询商品价格和库存。 | +| 成员D | 数量修改有哪些边界? | 数量必须大于 0,不能超过库存;商品下架或不存在时不能结算。 | +| 成员D | 购物车如何避免重复商品行? | 表上加 `UNIQUE(user_id, product_id)`,重复加购时增加数量或覆盖数量。 | +| 成员E | 为什么订单项要保存商品名称和价格快照? | 商品之后可能改名、改价或下架,但历史订单必须保持下单当时的数据。 | +| 成员E | 库存扣减怎么保证一致? | 下单时在事务内校验库存并扣减,失败则回滚;支付和订单状态也走状态机。 | +| 成员E | 支付回调重复怎么办? | 用支付单号/回调流水唯一约束 + 事务 + 状态机,重复请求返回当前状态,不重复处理。 | +| 成员F | 测试覆盖了什么? | 覆盖 F01-F13 必做功能、2 个选做、C08 挑战模块;每个模块都有正常和异常用例。 | +| 成员F | 部署如何保证可复现? | 使用 Docker Compose 或明确部署步骤,数据库初始化脚本提供 30+ 商品和测试账号。 | +| 成员F | 压测/边界数据怎么证明? | 保存脚本、请求参数、执行结果、数据库截图或日志,证明重复回调/乱序回调后状态仍正确。 | + +## 七、交叉 Code Review 安排 + +| 被审查模块 | 审查人 | 重点检查 | +|------------|--------|----------| +| 公共基建 | 成员F | 启动流程、部署配置、统一错误返回 | +| 用户模块 | 成员A | 鉴权接入、密码安全、权限边界 | +| 商品模块 | 成员B | 商品接口是否符合前台/后台权限 | +| 购物车模块 | 成员C | 商品状态、库存、价格引用是否正确 | +| 订单支付模块 | 成员D | 购物车到订单的数据传递、金额计算 | +| 测试部署 / 日志 | 成员E | 订单支付挑战模块测试是否可复现 | + +## 八、验收演示顺序 + +1. 展示首页、商品列表、分类筛选、关键词搜索、商品详情。 +2. 注册新用户,登录,维护个人信息和地址。 +3. 收藏商品、查看浏览历史。 +4. 加入购物车,修改数量,删除商品,勾选结算。 +5. 提交订单,展示订单详情和订单项价格快照。 +6. 模拟支付,订单状态变为已支付。 +7. 后台登录,管理商品、发货订单、禁用用户,查看后台操作日志。 +8. 演示 C08:重复回调、乱序回调、对账差异。 +9. 展示测试报告、部署方式、Git 提交和 Code Review 记录。 + +## 九、正式验收前检查清单 + +- [ ] README 分工表填入真实姓名、学号和模块。 +- [ ] 后端技术栈说明与实际代码一致:C# + ASP.NET Core Web API + MySQL,数据访问按 EF Core 接入计划说明。 +- [ ] 必做 F01-F13 每项都有演示路径。 +- [ ] 选做 X01、X02 有页面、接口、数据库记录。 +- [ ] 挑战 C08 有脚本、边界数据、截图/日志、原理说明。 +- [ ] 每人至少准备 3 个自己模块问题和 1 个其他模块问题。 +- [ ] 每人 Git 提交记录和负责模块一致。 +- [ ] docs/06-模块实现说明 下每个模块负责人都填写完成。 diff --git "a/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/\346\210\220\345\221\230\345\274\200\345\217\221\345\210\206\346\224\257\350\247\204\345\210\222.md" "b/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/\346\210\220\345\221\230\345\274\200\345\217\221\345\210\206\346\224\257\350\247\204\345\210\222.md" new file mode 100644 index 0000000..4627f0f --- /dev/null +++ "b/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/\346\210\220\345\221\230\345\274\200\345\217\221\345\210\206\346\224\257\350\247\204\345\210\222.md" @@ -0,0 +1,161 @@ +# 成员开发分支规划 + +> 适用场景:6 人小组按模块负责制开发。 +> 当前仓库已有分支:`main`、`develop`、`feature/base-setup`。建议所有成员从 `develop` 拉取自己的功能分支,完成后合并回 `develop`,最终由组长统一合并到 `main`。 + +## 一、每个成员需要创建的分支 + +| 成员 | 负责模块 | 建议分支名 | 从哪个分支创建 | 合并目标 | 说明 | +|------|----------|------------|----------------|----------|------| +| 成员A | 公共基建 / 组长 / 鉴权 / 集成联调 | `feature/base-setup` | `develop` | `develop` | 当前已存在,可继续用于项目骨架、统一响应、鉴权、权限、公共配置 | +| 成员B | 用户模块 | `feature/user-module` | `develop` | `develop` | 用户注册、登录、退出、个人信息、收货地址、后台用户管理 | +| 成员C | 商品模块 | `feature/product-module` | `develop` | `develop` | 商品分类、列表、搜索、详情、后台商品管理、上下架 | +| 成员D | 购物车模块 | `feature/cart-module` | `develop` | `develop` | 加购、数量修改、删除、勾选、金额计算、结算入口 | +| 成员E | 订单支付模块 / C08 挑战 | `feature/order-payment-module` | `develop` | `develop` | 提交订单、订单状态、模拟支付、后台订单管理、支付回调幂等与对账 | +| 成员F | 测试部署 / 操作日志 | `feature/test-deploy-log` | `develop` | `develop` | 测试计划、测试报告、部署、演示数据、后台操作日志、挑战演示脚本 | + +## 二、可选的子分支 + +如果某个模块工作量太大,可以在自己的主功能分支下再拆子分支。子分支完成后先合并回自己的主功能分支,再合并到 `develop`。 + +| 成员 | 可选子分支 | 用途 | +|------|------------|------| +| 成员A | `feature/auth` | JWT 鉴权、中间件、权限控制 | +| 成员A | `feature/common-response` | 统一响应格式、统一异常处理 | +| 成员B | `feature/user-auth` | 注册、登录、退出 | +| 成员B | `feature/user-address` | 个人信息、收货地址 | +| 成员C | `feature/product-catalog` | 分类、商品列表、搜索、详情 | +| 成员C | `feature/admin-product` | 后台商品管理、上下架 | +| 成员D | `feature/cart-crud` | 加购、修改数量、删除 | +| 成员D | `feature/cart-checkout` | 勾选、金额计算、结算入口 | +| 成员E | `feature/order-core` | 下单、订单列表、订单详情、取消订单 | +| 成员E | `feature/payment-idempotency` | 模拟支付、C08 支付回调幂等、对账 | +| 成员F | `feature/test-report` | 测试计划、测试用例、测试报告 | +| 成员F | `feature/admin-operation-log` | 后台操作日志 | +| 成员F | `feature/deploy` | Docker Compose、部署说明、演示数据 | + +## 三、创建分支命令 + +所有成员首次创建分支前,先同步远端分支: + +```bash +git checkout develop +git pull origin develop +``` + +成员B创建用户模块分支: + +```bash +git checkout -b feature/user-module +git push -u origin feature/user-module +``` + +成员C创建商品模块分支: + +```bash +git checkout -b feature/product-module +git push -u origin feature/product-module +``` + +成员D创建购物车模块分支: + +```bash +git checkout -b feature/cart-module +git push -u origin feature/cart-module +``` + +成员E创建订单支付模块分支: + +```bash +git checkout -b feature/order-payment-module +git push -u origin feature/order-payment-module +``` + +成员F创建测试部署和操作日志分支: + +```bash +git checkout -b feature/test-deploy-log +git push -u origin feature/test-deploy-log +``` + +成员A如果继续使用当前分支: + +```bash +git checkout feature/base-setup +git push -u origin feature/base-setup +``` + +## 四、每日同步规则 + +每天开始开发前,每个成员在自己的分支执行: + +```bash +git fetch origin +git merge origin/develop +``` + +如果出现冲突,在自己的功能分支解决,不要把冲突带到 `develop`。 + +## 五、提交信息建议 + +| 模块 | 提交示例 | +|------|----------| +| 公共基建 | `feat: add jwt authentication middleware` | +| 公共基建 | `feat: add unified api response model` | +| 用户模块 | `feat: add user login api` | +| 用户模块 | `feat: add address management page` | +| 商品模块 | `feat: add product list api with pagination` | +| 商品模块 | `feat: add admin product management page` | +| 购物车模块 | `feat: add cart item quantity update` | +| 购物车模块 | `fix: validate product stock before checkout` | +| 订单支付模块 | `feat: add order creation workflow` | +| 订单支付模块 | `feat: add idempotent payment callback` | +| 测试部署 | `test: add user and product test cases` | +| 测试部署 | `docs: add deployment verification record` | +| 操作日志 | `feat: add admin operation log query` | + +## 六、合并顺序建议 + +| 顺序 | 分支 | 合并原因 | +|------|------|----------| +| 1 | `feature/base-setup` | 公共基建先合并,其他模块依赖鉴权、统一响应、公共配置 | +| 2 | `feature/user-module` | 购物车、订单都需要登录用户和地址 | +| 3 | `feature/product-module` | 购物车、订单都需要商品价格、库存、上下架状态 | +| 4 | `feature/cart-module` | 订单创建依赖购物车结算数据 | +| 5 | `feature/order-payment-module` | 主购物流程最后闭环,包含支付和挑战模块 | +| 6 | `feature/test-deploy-log` | 最后统一补测试报告、部署验证、日志和演示脚本 | + +## 七、冲突责任人 + +| 冲突文件类型 | 决策负责人 | +|--------------|------------| +| `backend/src/Eshop.Api/Program.cs`、鉴权、统一响应 | 成员A | +| 用户、地址、后台用户相关文件 | 成员B | +| 商品、分类、后台商品相关文件 | 成员C | +| 购物车相关文件 | 成员D | +| 订单、支付、C08 对账相关文件 | 成员E | +| 测试、部署、日志、演示脚本相关文件 | 成员F | +| `README.md`、接口文档、数据库设计、`schema.sql` | 相关模块负责人 + 成员A 最终确认 | + +## 八、最终分支策略 + +```text +main + ↑ 第 10 天验收前由组长从 develop 合并 +develop + ↑ 每个模块 Code Review 后合并 +feature/base-setup +feature/user-module +feature/product-module +feature/cart-module +feature/order-payment-module +feature/test-deploy-log +``` + +要求: + +- `main` 只保留稳定可演示版本。 +- `develop` 用于集成联调。 +- 每个成员只在自己的 `feature/*` 分支开发。 +- 公共文件变更先通知组长和相关模块负责人。 +- 合并到 `develop` 前必须先把 `origin/develop` 合入自己的分支并解决冲突。 diff --git "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/README.md" "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/README.md" new file mode 100644 index 0000000..98d984a --- /dev/null +++ "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/README.md" @@ -0,0 +1,30 @@ +# 模块实现说明 + +每个成员制作模块时,必须在本目录新增一份模块说明文档。 + +## 命名规范 + +```text +模块名-实现说明.md +``` + +示例: + +```text +用户模块-实现说明.md +商品模块-实现说明.md +购物车模块-实现说明.md +订单支付模块-实现说明.md +后台管理模块-实现说明.md +测试部署模块-实现说明.md +``` + +## 必答问题 + +每份模块说明文档必须回答: + +1. 这个模块用了什么技术栈? +2. 为什么使用这个技术栈? +3. 是否还有其他实现方式? + +可以复制 `模块实现说明模板.md` 后填写。 diff --git "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\345\220\216\345\217\260\347\256\241\347\220\206\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\345\220\216\345\217\260\347\256\241\347\220\206\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" new file mode 100644 index 0000000..c4386a0 --- /dev/null +++ "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\345\220\216\345\217\260\347\256\241\347\220\206\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" @@ -0,0 +1,25 @@ +# 后台管理模块-实现说明 + +> 模块负责人:待填写 +> 编写日期:待填写 +> 对应分支:`feature/admin` + +## 1. 这个模块用了什么技术栈? + +- 前端:Vue 3 + Element Plus +- 后端:ASP.NET Core Web API +- 数据库:MySQL 8 +- ORM:EF Core(后续接入) +- 其他工具或中间件:JWT 管理员权限、Swagger / OpenAPI + +## 2. 为什么使用这个技术栈? + +- Element Plus 适合快速开发表格、表单、分页、弹窗等后台管理页面。 +- ASP.NET Core 中间件可以对后台接口统一做管理员权限校验。 +- MySQL 可以保存商品、订单、用户等后台管理数据。 + +## 3. 是否还有其他实现方式? + +- 方案 A:后台和前台放在同一个 Vue 应用,通过路由区分。 +- 方案 B:后台单独创建一个前端应用,权限和部署更清晰但成本更高。 +- 当前方案与其他方案的对比:当前统一前端应用更适合 4 周课程项目。 diff --git "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\345\225\206\345\223\201\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\345\225\206\345\223\201\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" new file mode 100644 index 0000000..7ea9410 --- /dev/null +++ "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\345\225\206\345\223\201\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" @@ -0,0 +1,26 @@ +# 商品模块-实现说明 + +> 模块负责人:待填写 +> 编写日期:待填写 +> 对应分支:`feature/product` + +## 1. 这个模块用了什么技术栈? + +- 前端:Vue 3 + Vite + TypeScript + Element Plus + Axios +- 后端:ASP.NET Core Web API +- 数据库:MySQL 8 +- ORM:EF Core(后续接入) +- 其他工具或中间件:Swagger / OpenAPI + +## 2. 为什么使用这个技术栈? + +- Vue 3 适合实现商品列表、分类筛选、搜索和详情页。 +- ASP.NET Core Web API 适合提供分页查询、详情查询和后台管理接口。 +- MySQL 适合保存分类、商品、库存、上下架状态等结构化数据。 +- EF Core 适合完成商品列表分页、分类筛选和详情查询。 + +## 3. 是否还有其他实现方式? + +- 方案 A:商品搜索用 MySQL LIKE,简单但性能一般。 +- 方案 B:后续可接入搜索引擎或倒排索引,实现进阶搜索挑战模块。 +- 当前方案与其他方案的对比:当前先用 MySQL 查询保证主流程完成,后续再优化搜索能力。 diff --git "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216\346\250\241\346\235\277.md" "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216\346\250\241\346\235\277.md" new file mode 100644 index 0000000..164bce2 --- /dev/null +++ "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216\346\250\241\346\235\277.md" @@ -0,0 +1,59 @@ +# 模块名-实现说明 + +> 模块负责人: +> 编写日期: +> 对应分支: + +## 1. 这个模块用了什么技术栈? + +- 前端: +- 后端: +- 数据库: +- 其他工具或中间件: + +## 2. 为什么使用这个技术栈? + +- 选择原因 1: +- 选择原因 2: +- 选择原因 3: + +## 3. 是否还有其他实现方式? + +- 方案 A: +- 方案 B: +- 当前方案与其他方案的对比: + +## 4. 模块功能清单 + +| 功能点 | 状态 | 说明 | +|---|---|---| +| | 未开始/进行中/完成 | | + +## 5. 涉及的数据表 + +| 表名 | 用途 | +|---|---| +| | | + +## 6. 涉及的接口 + +| 方法 | 路径 | 用途 | +|---|---|---| +| | | | + +## 7. 涉及的前端页面 + +| 页面 | 路由 | 用途 | +|---|---|---| +| | | | + +## 8. AI 使用说明 + +- 使用 AI 辅助完成的内容: +- 自己理解并修改的内容: +- 已验证的内容: + +## 9. 风险与待办 + +- 风险: +- 待办: diff --git "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\346\265\213\350\257\225\351\203\250\347\275\262\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\346\265\213\350\257\225\351\203\250\347\275\262\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" new file mode 100644 index 0000000..7f4cf99 --- /dev/null +++ "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\346\265\213\350\257\225\351\203\250\347\275\262\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" @@ -0,0 +1,25 @@ +# 测试部署模块-实现说明 + +> 模块负责人:待填写 +> 编写日期:待填写 +> 对应分支:`feature/test-deploy` + +## 1. 这个模块用了什么技术栈? + +- 前端:Vite 构建产物 +- 后端:ASP.NET Core Web API +- 数据库:MySQL 8 +- ORM:EF Core(后续接入) +- 其他工具或中间件:Docker Compose、Apifox/Postman、Swagger / OpenAPI + +## 2. 为什么使用这个技术栈? + +- Docker Compose 适合课程项目一键启动前端、后端和数据库。 +- Swagger / OpenAPI 与 Apifox/Postman 方便做接口测试和联调。 +- MySQL 初始化脚本方便准备验收演示数据。 + +## 3. 是否还有其他实现方式? + +- 方案 A:手工部署到服务器,学习成本低但容易遗漏配置。 +- 方案 B:CI/CD 自动部署,规范但前期配置成本更高。 +- 当前方案与其他方案的对比:当前先用 Docker Compose 或手工部署保证验收,后续可补 CI/CD 作为加分项。 diff --git "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\347\224\250\346\210\267\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\347\224\250\346\210\267\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" new file mode 100644 index 0000000..8bf4a43 --- /dev/null +++ "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\347\224\250\346\210\267\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" @@ -0,0 +1,26 @@ +# 用户模块-实现说明 + +> 模块负责人:待填写 +> 编写日期:待填写 +> 对应分支:`feature/user` + +## 1. 这个模块用了什么技术栈? + +- 前端:Vue 3 + Vite + TypeScript + Element Plus + Pinia + Axios +- 后端:ASP.NET Core Web API +- 数据库:MySQL 8 +- ORM:EF Core(后续接入) +- 其他工具或中间件:JWT、BCrypt、Swagger / OpenAPI + +## 2. 为什么使用这个技术栈? + +- Vue 3 适合快速开发登录、资料、地址管理等表单页面。 +- ASP.NET Core Web API 适合提供清晰的用户认证和管理接口。 +- MySQL 适合保存用户、地址、角色和状态等关系型数据。 +- EF Core 可以快速完成用户、地址等表的 CRUD 和条件查询。 + +## 3. 是否还有其他实现方式? + +- 方案 A:Spring Boot + Vue,也适合电商项目,但本组后端统一采用 C#。 +- 方案 B:ASP.NET Core MVC 单体页面,开发简单但不符合前后端分离要求。 +- 当前方案与其他方案的对比:当前方案更符合课程验收中的前后端分离和 HTTP API 要求。 diff --git "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\350\256\242\345\215\225\346\224\257\344\273\230\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\350\256\242\345\215\225\346\224\257\344\273\230\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" new file mode 100644 index 0000000..7667ac9 --- /dev/null +++ "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\350\256\242\345\215\225\346\224\257\344\273\230\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" @@ -0,0 +1,25 @@ +# 订单支付模块-实现说明 + +> 模块负责人:待填写 +> 编写日期:待填写 +> 对应分支:`feature/order-payment` + +## 1. 这个模块用了什么技术栈? + +- 前端:Vue 3 + Element Plus + Axios +- 后端:ASP.NET Core Web API +- 数据库:MySQL 8 +- ORM:EF Core(后续接入) +- 其他工具或中间件:事务、JWT、Swagger / OpenAPI + +## 2. 为什么使用这个技术栈? + +- Vue 3 适合实现下单确认、订单列表、订单详情和模拟支付页面。 +- ASP.NET Core Web API 适合提供订单和支付接口,EF Core 事务适合处理订单状态流转、库存扣减和支付状态更新。 +- MySQL 事务适合保证订单创建、订单项快照和库存扣减的一致性。 + +## 3. 是否还有其他实现方式? + +- 方案 A:订单超时取消用定时扫描,实现简单。 +- 方案 B:订单超时取消用延迟队列,实时性更好但需要额外中间件。 +- 当前方案与其他方案的对比:课程项目可先用定时扫描完成挑战模块,再说明延迟队列作为扩展方案。 diff --git "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\350\264\255\347\211\251\350\275\246\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\350\264\255\347\211\251\350\275\246\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" new file mode 100644 index 0000000..f40ca9f --- /dev/null +++ "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\350\264\255\347\211\251\350\275\246\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" @@ -0,0 +1,25 @@ +# 购物车模块-实现说明 + +> 模块负责人:待填写 +> 编写日期:待填写 +> 对应分支:`feature/cart` + +## 1. 这个模块用了什么技术栈? + +- 前端:Vue 3 + Pinia + Element Plus +- 后端:ASP.NET Core Web API +- 数据库:MySQL 8 +- ORM:EF Core(后续接入) +- 其他工具或中间件:Axios、JWT、Swagger / OpenAPI + +## 2. 为什么使用这个技术栈? + +- Vue 3 适合实现购物车勾选、数量修改和金额实时展示。 +- ASP.NET Core 中间件可以统一校验用户登录态,EF Core 可以校验库存和购物车归属。 +- MySQL 可以持久化购物车数据,保证换设备后仍可查看。 + +## 3. 是否还有其他实现方式? + +- 方案 A:购物车只存在浏览器 localStorage,简单但无法跨设备。 +- 方案 B:购物车存在 Redis,性能好但增加中间件复杂度。 +- 当前方案与其他方案的对比:当前 MySQL 方案更稳,适合课程项目第一版。 diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..77b6212 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://localhost:5081/api diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..06bd303 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + E-Shop + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..dfba082 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2192 @@ +{ + "name": "eshop-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "eshop-frontend", + "version": "0.1.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "axios": "^1.7.9", + "element-plus": "^2.9.1", + "pinia": "^3.0.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.1", + "typescript": "^5.8.3", + "vite": "^7.0.0", + "vue-tsc": "^2.2.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.40.tgz", + "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.40", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", + "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", + "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.40", + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-ssr": "3.5.40", + "@vue/shared": "3.5.40", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", + "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.10", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.10" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.10", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.10", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.10", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.40.tgz", + "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.40.tgz", + "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", + "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/runtime-core": "3.5.40", + "@vue/shared": "3.5.40", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.40.tgz", + "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.40.tgz", + "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "14.3.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-14.3.0.tgz", + "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.3.0", + "@vueuse/shared": "14.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.3.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-14.3.0.tgz", + "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.3.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-14.3.0.tgz", + "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/element-plus": { + "version": "2.14.3", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.14.3.tgz", + "integrity": "sha512-pJcvxcpZjYruNzuJhAeVwnbYjfNgzBKnWHwSVEhwzM2/kcLI3brzmtIBxtPqd4hQWJfD1PRnjoc1WipLw2eBGg==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.2.0", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.7.6", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8", + "@types/lodash": "^4.17.24", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "14.3.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.20", + "lodash": "^4.18.1", + "lodash-es": "^4.18.1", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0", + "vue-component-type-helpers": "^3.3.5" + }, + "peerDependencies": { + "vue": "^3.3.7" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.21", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.21.tgz", + "integrity": "sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.40", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.40.tgz", + "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-sfc": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/server-renderer": "3.5.40", + "@vue/shared": "3.5.40" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.7", + "resolved": "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-3.3.7.tgz", + "integrity": "sha512-Skkhw9agYSgsWqv7bxSOGJZa9SaiJbZVGdXuFWnrzKaQYHnw9qbjD630rw6RyMqDbp54nfLCLw5SZA55if7JLg==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..ae24dc0 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,25 @@ +{ + "name": "eshop-frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "axios": "^1.7.9", + "element-plus": "^2.9.1", + "pinia": "^3.0.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.1", + "typescript": "^5.8.3", + "vite": "^7.0.0", + "vue-tsc": "^2.2.0" + } +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..53a86b1 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,17 @@ + diff --git a/frontend/src/api/http.ts b/frontend/src/api/http.ts new file mode 100644 index 0000000..0ceb85a --- /dev/null +++ b/frontend/src/api/http.ts @@ -0,0 +1,23 @@ +import axios from 'axios' + +export const http = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL ?? '/api', + timeout: 10000, +}) + +http.interceptors.request.use((config) => { + const token = localStorage.getItem('eshop_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + + return config +}) + +http.interceptors.response.use( + (response) => response.data, + (error) => { + const message = error.response?.data?.message ?? error.message ?? '请求失败' + return Promise.reject(new Error(message)) + }, +) diff --git a/frontend/src/api/products.ts b/frontend/src/api/products.ts new file mode 100644 index 0000000..ce01e4b --- /dev/null +++ b/frontend/src/api/products.ts @@ -0,0 +1,14 @@ +import { http } from './http' + +export interface ProductListItem { + id: number + name: string + categoryName: string + price: number + stock: number + imageUrl?: string +} + +export function getProducts() { + return http.get('/products') +} diff --git a/frontend/src/env.d.ts b/frontend/src/env.d.ts new file mode 100644 index 0000000..3b4ae69 --- /dev/null +++ b/frontend/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + const component: DefineComponent + export default component +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..40614f7 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,13 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import './styles/main.css' +import App from './App.vue' +import router from './router' + +createApp(App) + .use(createPinia()) + .use(router) + .use(ElementPlus) + .mount('#app') diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..06ed550 --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,21 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomeView from '../views/HomeView.vue' +import LoginView from '../views/user/LoginView.vue' +import ProductListView from '../views/product/ProductListView.vue' +import CartView from '../views/cart/CartView.vue' +import OrderListView from '../views/order/OrderListView.vue' +import AdminDashboardView from '../views/admin/AdminDashboardView.vue' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { path: '/', name: 'home', component: HomeView }, + { path: '/login', name: 'login', component: LoginView }, + { path: '/products', name: 'products', component: ProductListView }, + { path: '/cart', name: 'cart', component: CartView }, + { path: '/orders', name: 'orders', component: OrderListView }, + { path: '/admin', name: 'admin', component: AdminDashboardView }, + ], +}) + +export default router diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts new file mode 100644 index 0000000..a5e5674 --- /dev/null +++ b/frontend/src/stores/auth.ts @@ -0,0 +1,19 @@ +import { defineStore } from 'pinia' + +export const useAuthStore = defineStore('auth', { + state: () => ({ + token: localStorage.getItem('eshop_token') ?? '', + username: '', + }), + actions: { + setToken(token: string) { + this.token = token + localStorage.setItem('eshop_token', token) + }, + logout() { + this.token = '' + this.username = '' + localStorage.removeItem('eshop_token') + }, + }, +}) diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css new file mode 100644 index 0000000..41be893 --- /dev/null +++ b/frontend/src/styles/main.css @@ -0,0 +1,72 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + color: #1f2937; + background: #f5f7fb; + font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +.app-shell { + min-height: 100vh; +} + +.app-header { + display: flex; + align-items: center; + justify-content: space-between; + height: 64px; + color: #fff; + background: #1f2937; +} + +.brand { + font-size: 20px; + font-weight: 700; +} + +.nav { + display: flex; + gap: 20px; +} + +.nav a.router-link-active { + color: #93c5fd; +} + +.app-main { + width: min(1180px, calc(100vw - 32px)); + margin: 0 auto; + padding: 24px 0; +} + +.page { + min-height: 420px; + padding: 24px; + background: #fff; + border: 1px solid #e5e7eb; + border-radius: 8px; +} + +.narrow-page { + max-width: 420px; + margin: 0 auto; +} + +.page-title-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.search-input { + max-width: 320px; +} diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue new file mode 100644 index 0000000..117415c --- /dev/null +++ b/frontend/src/views/HomeView.vue @@ -0,0 +1,10 @@ + diff --git a/frontend/src/views/admin/AdminDashboardView.vue b/frontend/src/views/admin/AdminDashboardView.vue new file mode 100644 index 0000000..1618d6e --- /dev/null +++ b/frontend/src/views/admin/AdminDashboardView.vue @@ -0,0 +1,16 @@ + diff --git a/frontend/src/views/cart/CartView.vue b/frontend/src/views/cart/CartView.vue new file mode 100644 index 0000000..4903079 --- /dev/null +++ b/frontend/src/views/cart/CartView.vue @@ -0,0 +1,6 @@ + diff --git a/frontend/src/views/order/OrderListView.vue b/frontend/src/views/order/OrderListView.vue new file mode 100644 index 0000000..59c0686 --- /dev/null +++ b/frontend/src/views/order/OrderListView.vue @@ -0,0 +1,6 @@ + diff --git a/frontend/src/views/product/ProductListView.vue b/frontend/src/views/product/ProductListView.vue new file mode 100644 index 0000000..419f7cf --- /dev/null +++ b/frontend/src/views/product/ProductListView.vue @@ -0,0 +1,15 @@ + + + diff --git a/frontend/src/views/user/LoginView.vue b/frontend/src/views/user/LoginView.vue new file mode 100644 index 0000000..dc5cd42 --- /dev/null +++ b/frontend/src/views/user/LoginView.vue @@ -0,0 +1,26 @@ + + + diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..f17d805 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "types": ["vite/client"] + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..f2fa564 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:5081', + changeOrigin: true, + }, + }, + }, +}) diff --git a/global.json b/global.json new file mode 100644 index 0000000..f365c8c --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.301", + "rollForward": "latestFeature" + } +} -- Gitee From a5bc6d63c417976411726b0ba8ac8bcfdd46388c Mon Sep 17 00:00:00 2001 From: wuh <3422047492@qq.com> Date: Wed, 22 Jul 2026 17:19:56 +0800 Subject: [PATCH 05/15] docs: add daily report for wei luanhong --- ...2-\351\255\217\351\212\256\346\263\223.md" | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 "reports/daily/\351\255\217\351\212\256\346\263\223/20260722-\351\255\217\351\212\256\346\263\223.md" diff --git "a/reports/daily/\351\255\217\351\212\256\346\263\223/20260722-\351\255\217\351\212\256\346\263\223.md" "b/reports/daily/\351\255\217\351\212\256\346\263\223/20260722-\351\255\217\351\212\256\346\263\223.md" new file mode 100644 index 0000000..a4fc83c --- /dev/null +++ "b/reports/daily/\351\255\217\351\212\256\346\263\223/20260722-\351\255\217\351\212\256\346\263\223.md" @@ -0,0 +1,29 @@ +# 日报 - 魏銮泓 - 2026-07-22 + +## 今日完成 + +1. 完成项目技术栈口径统一,将项目说明调整为 `C# + ASP.NET Core Web API + MySQL`,并补充前端 Vue 3、Element Plus、Pinia、Axios 等基础技术说明。 +2. 完成后端基础工程规划与骨架提交,包括 `Eshop.Api`、`Eshop.Application`、`Eshop.Domain`、`Eshop.Infrastructure` 分层结构,以及用户、商品、购物车、订单、支付等领域实体和状态枚举。 +3. 补充数据库初始化脚本,包含用户、分类、商品、地址、购物车、订单、订单项、支付记录等核心表结构,并准备不少于 30 个商品的演示数据。 +4. 补充前端基础工程结构,包括路由、登录态 Store、Axios 封装、首页、登录页、商品列表、购物车、订单列表、后台管理等页面骨架。 +5. 完成 6 人模块分工、模块难度拆分、10 个工作日开发计划、成员开发分支规划等项目管理文档,便于后续按模块负责制推进。 +6. 将今日项目基础建设与文档更新提交并推送到远端分支 `feature/base-setup`,提交记录为 `a9c838b chore: add csharp project setup and planning docs`。 + +## 遇到的问题 + +| 问题描述 | 解决状态 | 解决方式/求助对象 | +|----------|----------|-------------------| +| README 和部分文档原先技术栈口径与当前 C# 后端方向不一致,可能影响验收答辩说明 | 已解决 | 将 README、模块说明和答辩准备文档统一为 C# + ASP.NET Core Web API + MySQL 口径 | +| 6 人并行开发时容易同时修改公共文件,后续合并可能出现冲突 | 已解决 | 制定 `feature/base-setup`、`feature/user-module`、`feature/product-module`、`feature/cart-module`、`feature/order-payment-module`、`feature/test-deploy-log` 分支规划,并明确合并顺序和冲突责任人 | +| 选做功能和挑战模块范围如果过大,可能挤压核心功能开发时间 | 已解决 | 推荐优先完成商品收藏/浏览历史、后台操作日志两个选做功能,挑战模块首选 C08 支付回调幂等与对账 | + +## 明日计划 + +1. 继续完善公共基建模块,优先实现统一响应模型、全局异常处理、JWT 登录鉴权和管理员权限判断。 +2. 推动成员从 `develop` 创建各自功能分支,并按照用户、商品、购物车、订单支付、测试部署模块拆分任务。 +3. 补充接口文档中各模块的请求参数、响应字段和错误码,减少后续前后端联调歧义。 +4. 验证后端项目能正常 `dotnet build`,前端项目能正常安装依赖并启动基础页面。 + +## 今日工时 + +约 6 小时 -- Gitee From 2b772975741da8165d1eeb47c7e38a0ca8b5d414 Mon Sep 17 00:00:00 2001 From: wuh <3422047492@qq.com> Date: Wed, 22 Jul 2026 17:23:37 +0800 Subject: [PATCH 06/15] =?UTF-8?q?docs:=E6=9B=B4=E6=96=B0docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...06\345\267\245\350\256\241\345\210\222.md" | 130 ---------- ...14\346\224\266\345\207\206\345\244\207.md" | 236 ------------------ ...06\346\224\257\350\247\204\345\210\222.md" | 161 ------------ 3 files changed, 527 deletions(-) delete mode 100644 "docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/10\344\270\252\345\267\245\344\275\234\346\227\245\345\274\200\345\217\221\345\210\206\345\267\245\350\256\241\345\210\222.md" delete mode 100644 "docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/6\344\272\272\345\210\206\345\267\245\344\270\216\351\252\214\346\224\266\345\207\206\345\244\207.md" delete mode 100644 "docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/\346\210\220\345\221\230\345\274\200\345\217\221\345\210\206\346\224\257\350\247\204\345\210\222.md" diff --git "a/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/10\344\270\252\345\267\245\344\275\234\346\227\245\345\274\200\345\217\221\345\210\206\345\267\245\350\256\241\345\210\222.md" "b/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/10\344\270\252\345\267\245\344\275\234\346\227\245\345\274\200\345\217\221\345\210\206\345\267\245\350\256\241\345\210\222.md" deleted file mode 100644 index d580c9e..0000000 --- "a/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/10\344\270\252\345\267\245\344\275\234\346\227\245\345\274\200\345\217\221\345\210\206\345\267\245\350\256\241\345\210\222.md" +++ /dev/null @@ -1,130 +0,0 @@ -# 10个工作日开发分工计划 - -> 适用场景:6 人小组,10 个工作日内完成电子商城核心功能、选做功能、挑战模块、测试部署和验收答辩准备。 -> 分工原则:按业务模块垂直负责,每位成员负责自己模块的数据库表、后端接口、前端页面、测试和答辩说明。 - -## 一、成员与模块对应 - -| 代号 | 角色 | 负责模块 | 难度 | -|------|------|----------|------| -| 成员A | 组长 / 架构员 | 公共基建、统一响应、鉴权、权限、公共组件、集成联调 | 高 | -| 成员B | 模块负责人 | 用户注册登录、个人信息、收货地址、后台用户管理 | 中高 | -| 成员C | 模块负责人 | 商品分类、商品列表、搜索、详情、后台商品管理 | 中高 | -| 成员D | 模块负责人 | 购物车、数量修改、删除、金额计算、结算入口 | 高 | -| 成员E | 模块负责人 | 订单、支付、后台订单管理、C08 支付回调幂等与对账 | 最高 | -| 成员F | 测试部署 / 质量负责人 | 测试计划、测试报告、Docker 部署、演示数据、X02 后台操作日志、挑战演示脚本 | 高 | - -## 二、10 个工作日任务安排 - -| 工作日 | 成员A:公共基建 | 成员B:用户模块 | 成员C:商品模块 | 成员D:购物车模块 | 成员E:订单支付模块 | 成员F:测试部署 | -|--------|------------------|------------------|------------------|--------------------|----------------------|------------------| -| 第 1 天 | 确认 C# + ASP.NET Core 技术栈;统一分支规范、提交规范、接口返回格式 | 梳理用户、地址、后台用户管理需求和表字段 | 梳理分类、商品、后台商品管理需求和表字段 | 梳理购物车业务流程、结算入口和接口依赖 | 梳理订单状态、支付状态、C08 挑战模块方案 | 建测试计划初稿;整理 F01-F13 验收清单 | -| 第 2 天 | 搭建后端分层结构;配置 CORS、OpenAPI、统一异常处理 | 设计注册、登录、退出、个人信息、地址管理接口 | 设计商品列表、分类筛选、搜索、详情、后台商品接口 | 设计加购、改数量、删除、勾选、结算接口 | 设计下单、订单列表、详情、取消、支付、后台订单接口 | 建测试用例模板;准备演示账号和演示数据清单 | -| 第 3 天 | 完成 JWT 鉴权基础、中间件、管理员权限判断 | 完成注册、登录、退出后端接口 | 完成分类、商品列表、关键词搜索、商品详情接口 | 完成购物车表结构和基础 CRUD 接口 | 完成订单表、订单项表、支付表和创建订单接口 | 补数据库初始化数据,保证 30+ 商品、3+ 分类 | -| 第 4 天 | 完成前端路由守卫、登录态管理、Axios 拦截器 | 完成登录页、注册页、个人信息页 | 完成商品列表页、搜索、分类筛选页面 | 完成购物车页面基础展示和勾选逻辑 | 完成订单列表、订单详情接口和基础页面 | 编写用户、商品模块测试用例 | -| 第 5 天 | 集成用户和商品模块,修复公共阻塞问题 | 完成收货地址增删改查;完成后台用户禁用/启用 | 完成商品详情页;完成后台商品管理页面 | 完成加购、改数量、删除、金额计算 | 完成取消订单、订单状态流转、后台订单查询 | 编写购物车、订单支付测试用例 | -| 第 6 天 | 完成管理端权限控制和后台入口统一 | 联调禁用用户后无法登录;补用户模块异常提示 | 联调商品上下架后前台不可购买 | 完成结算入口,联调购物车到订单创建 | 完成模拟支付、支付后订单状态变更、后台发货 | 完成部署说明初稿;检查 Docker Compose 启动链路 | -| 第 7 天 | 支持选做功能所需公共鉴权和接口规范 | 完成 X01 商品收藏、浏览历史的后端接口和个人中心入口 | 配合 X01 在商品详情页加入收藏、浏览记录入口 | 修复库存不足、商品下架、数量越界等边界问题 | 开始 C08 支付回调幂等接口和支付回调记录 | 完成 X02 后台操作日志表、日志写入和查询页面 | -| 第 8 天 | 组织全链路联调:登录、商品、购物车、下单、支付 | 修复用户模块 Bug;补用户模块实现说明 | 修复商品模块 Bug;补商品模块实现说明 | 修复购物车 Bug;补购物车模块实现说明 | 完成 C08 重复回调、乱序回调、对账 SQL | 准备 C08 边界演示脚本、接口调用记录和截图 | -| 第 9 天 | 组织全系统集成测试;统一处理阻塞 Bug | 跑用户相关测试,补日报和模块说明 | 跑商品相关测试,补日报和模块说明 | 跑购物车相关测试,补日报和模块说明 | 跑订单支付和 C08 测试,补模块说明 | 完成测试报告、缺陷记录、部署验证 | -| 第 10 天 | 控制最终演示流程;统一答辩口径;检查 Git 记录 | 准备用户模块答辩问题和演示步骤 | 准备商品模块答辩问题和演示步骤 | 准备购物车模块答辩问题和演示步骤 | 准备订单支付 + C08 原理答辩 | 汇总 PPT、演示脚本、验收材料、截图和压测记录 | - -## 三、每日交付物 - -| 工作日 | 当天必须产出 | -|--------|--------------| -| 第 1 天 | 分工确认、技术栈确认、模块字段草稿、验收清单拆解 | -| 第 2 天 | 接口设计初稿、页面入口设计、测试用例模板 | -| 第 3 天 | 鉴权基础、核心表结构、用户/商品/购物车/订单基础接口 | -| 第 4 天 | 登录、商品列表、购物车、订单页面雏形 | -| 第 5 天 | 用户、商品、购物车、订单主功能基本完成 | -| 第 6 天 | 后台入口、权限控制、模拟支付、部署说明初稿 | -| 第 7 天 | 选做功能 X01、X02 可演示版本;C08 初版接口 | -| 第 8 天 | 主流程完整跑通;C08 边界演示可运行 | -| 第 9 天 | 测试报告、缺陷闭环、部署验证记录 | -| 第 10 天 | 演示脚本、答辩问题、PPT 素材、最终验收材料 | - -## 四、关键里程碑 - -| 时间点 | 必须达到的状态 | 如果未完成的处理方式 | -|--------|----------------|----------------------| -| 第 2 天结束 | 数据库表、接口文档、页面入口基本定稿 | 暂停新增功能,优先统一接口,避免后续联调返工 | -| 第 5 天结束 | 登录、商品、购物车、下单、支付主链路基本可跑 | 所有人优先修主链路,选做功能顺延但不能超过第 7 天 | -| 第 7 天结束 | 至少 2 个选做功能有可演示版本 | 砍掉复杂交互,只保留最小可验收功能 | -| 第 8 天结束 | C08 能演示重复回调、乱序回调、对账差异 | 挑战模块只保留 C08,不再增加其他挑战 | -| 第 9 天结束 | 测试、部署、Bug 修复基本完成 | 第 10 天禁止大改功能,只做演示稳定性修复 | -| 第 10 天结束 | 全员能讲清自己模块的库表、接口、页面、边界问题 | 演示前按模块轮流抽问,答不上来的问题立刻补口径 | - -## 五、模块依赖关系 - -| 依赖方 | 被依赖方 | 依赖内容 | 最晚完成时间 | -|--------|----------|----------|--------------| -| 用户模块 | 公共基建 | 鉴权、统一响应、异常处理 | 第 3 天 | -| 商品模块 | 公共基建 | 统一接口格式、后台权限 | 第 3 天 | -| 购物车模块 | 用户模块、商品模块 | 当前用户、商品价格、库存、上下架状态 | 第 5 天 | -| 订单支付模块 | 用户模块、商品模块、购物车模块 | 地址、商品快照、库存、购物车结算数据 | 第 6 天 | -| 后台操作日志 | 公共基建、后台管理接口 | 管理员身份、操作对象、操作结果 | 第 7 天 | -| C08 挑战模块 | 订单支付模块 | 订单状态、支付记录、回调流水、对账数据 | 第 8 天 | -| 测试部署 | 全部模块 | 接口、页面、演示账号、演示数据 | 第 9 天 | - -## 六、验收风险控制 - -| 风险 | 影响 | 预防措施 | -|------|------|----------| -| 接口迟迟不定 | 前后端联调延期,购物车和订单被卡住 | 第 2 天必须冻结第一版接口,后续只允许小范围补充字段 | -| 只做前端或只做后端 | 不符合模块负责制,个人答辩容易扣分 | 每人都要提交自己模块的库表、接口、页面和说明文档 | -| 订单支付太晚完成 | 主流程无法演示,挑战模块无法开展 | 成员E 第 3 天开始写订单表和下单接口,第 6 天前完成模拟支付 | -| 选做功能过复杂 | 挤压测试和答辩时间 | 首选 X01 收藏/浏览历史、X02 后台操作日志,避免 SKU、售后等高复杂度功能 | -| 挑战模块讲不清 | C08 无法通过原理抽问 | 准备幂等、唯一约束、事务、状态机、对账 SQL 五个固定答辩点 | -| 部署环境不稳定 | 现场演示失败 | 第 6 天开始部署验证,第 9 天冻结部署环境,保留本地备份演示方案 | - -## 七、每日站会模板 - -每天下班前每位成员按下面格式同步一次: - -```text -姓名: -负责模块: -今天完成: -遇到问题: -明天计划: -需要谁配合: -今日提交记录: -``` - -示例: - -```text -姓名:成员D -负责模块:购物车模块 -今天完成:完成购物车列表接口和数量修改接口,前端页面已能展示商品名称、单价、数量、小计。 -遇到问题:结算时需要订单模块确认提交订单接口字段。 -明天计划:完成删除、勾选和金额汇总,并联调商品库存不足提示。 -需要谁配合:成员E 确认下单接口,成员C 确认商品下架字段。 -今日提交记录:feat: 添加购物车列表和数量修改接口 -``` - -## 八、最终演示顺序 - -1. 展示首页、商品列表、分类筛选、关键词搜索、商品详情。 -2. 注册新用户,登录,维护个人信息和收货地址。 -3. 收藏商品,查看浏览历史。 -4. 加入购物车,修改数量,删除商品,勾选结算。 -5. 提交订单,展示订单详情和订单项价格快照。 -6. 模拟支付,订单状态变为已支付。 -7. 后台登录,管理商品、发货订单、禁用用户,查看后台操作日志。 -8. 演示 C08:重复支付回调、乱序回调、对账差异。 -9. 展示测试报告、部署方式、Git 提交记录和 Code Review 记录。 - -## 九、正式验收前检查清单 - -- [ ] README 已填写真实姓名、学号、角色和负责模块。 -- [ ] 技术栈统一为 C# + ASP.NET Core Web API + MySQL,数据访问按 EF Core 接入计划说明。 -- [ ] F01-F13 必做功能全部有演示路径。 -- [ ] X01 商品收藏/浏览历史、X02 后台操作日志至少两个选做功能可演示。 -- [ ] C08 支付回调幂等与对账有边界演示脚本和原理说明。 -- [ ] docs/06-模块实现说明 下每个模块说明文档已补全。 -- [ ] 测试计划、测试报告、缺陷记录、部署说明已完成。 -- [ ] 每人至少准备 3 个自己模块问题和 1 个其他模块基础问题。 -- [ ] 每人 Git 提交记录和负责模块一致。 -- [ ] 第 10 天只修复演示稳定性问题,不再新增高风险功能。 diff --git "a/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/6\344\272\272\345\210\206\345\267\245\344\270\216\351\252\214\346\224\266\345\207\206\345\244\207.md" "b/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/6\344\272\272\345\210\206\345\267\245\344\270\216\351\252\214\346\224\266\345\207\206\345\244\207.md" deleted file mode 100644 index 763656d..0000000 --- "a/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/6\344\272\272\345\210\206\345\267\245\344\270\216\351\252\214\346\224\266\345\207\206\345\244\207.md" +++ /dev/null @@ -1,236 +0,0 @@ -# 6人分工与验收准备 - -> 适用场景:6 人小组,按业务模块垂直负责。每个人都要能讲清自己模块的库表、接口、页面、关键代码和边界处理。 - -## 一、推荐分工总表 - -| 成员 | 角色 | 负责模块 | 必须交付 | 答辩重点 | -|------|------|----------|----------|----------| -| 成员A | 组长 / 架构员 | 公共基建、技术栈统一、鉴权、统一响应、公共组件、集成联调 | 项目启动、路由/权限、统一异常、接口规范、联调清单、最终演示脚本 | 为什么前后端分离、JWT/Session 如何选型、统一响应和异常如何设计 | -| 成员B | 用户模块负责人 | 用户注册、登录、退出、个人信息、收货地址、后台用户管理 | 用户表、地址表、登录接口、地址 CRUD、禁用/启用用户 | 密码为什么不能明文、登录态怎么保持、禁用用户为什么不能登录 | -| 成员C | 商品模块负责人 | 分类、商品列表、关键词搜索、商品详情、后台商品管理 | 分类/商品表、分页搜索接口、详情页、后台增删改查、上下架 | 分页参数怎么防异常、商品下架后前台如何处理、搜索如何实现 | -| 成员D | 购物车模块负责人 | 加购、修改数量、删除、勾选、结算页 | 购物车表、购物车接口、金额计算、库存校验、结算入口 | 为什么购物车按 user_id 隔离、数量修改如何防越界、金额为什么以后端为准 | -| 成员E | 订单支付负责人 | 提交订单、订单列表/详情/取消、模拟支付、后台订单管理、挑战模块 C08 | 订单表、订单项快照、支付表、状态流转、支付回调幂等、对账查询 | 为什么订单项要保存商品快照、库存扣减事务怎么保证一致、支付回调为什么要幂等 | -| 成员F | 测试部署 / 质量负责人 | 测试计划、测试用例、测试报告、Docker 部署、演示数据、选做功能 X02 | 40+ 测试用例、缺陷闭环、部署说明、压测/边界演示脚本、后台操作日志 | 测试如何覆盖正常/异常场景、部署链路怎么跑、日志如何追责 | - -> 如果成员能力有明显差异:订单支付和公共基建给较强成员;购物车和测试部署成员要参与联调,不要只写文档。 - -## 二、模块边界约定 - -| 边界 | 负责人 | 约定 | -|------|--------|------| -| 鉴权与当前用户 | 成员A | 统一提供登录态解析,其他模块只读取当前 user_id,不重复写鉴权逻辑 | -| 用户与地址 | 成员B | 订单只能引用有效 address_id,不能直接修改用户地址数据 | -| 商品价格与库存 | 成员C | 商品模块负责维护当前价格、库存、上下架;订单下单时保存价格快照 | -| 购物车结算 | 成员D | 购物车只负责选择商品和数量校验,真正生成订单由订单模块负责 | -| 订单状态 | 成员E | 订单状态只能通过订单服务流转,后台发货、支付回调都走统一状态机 | -| 测试与部署数据 | 成员F | 统一维护演示账号、30+ 商品、测试用例、压测脚本和验收演示顺序 | - -## 三、推荐选做功能 - -### X01 商品收藏、浏览历史 - -**推荐负责人:成员B 主责,成员C 配合。** - -推荐原因: - -- 实现成本低,只需要围绕用户和商品增加关系表。 -- 演示效果直观:商品详情页点击收藏,个人中心查看收藏;浏览商品后出现历史记录。 -- 容易答辩:能讲清唯一约束、分页、登录用户隔离。 -- 后续可以给“猜你喜欢”挑战模块复用行为数据,但不强依赖推荐算法。 - -建议范围: - -| 功能 | 最小可验收实现 | -|------|----------------| -| 收藏商品 | 登录用户可收藏/取消收藏,重复收藏不会产生重复数据 | -| 我的收藏 | 按时间倒序分页展示收藏商品 | -| 浏览历史 | 用户访问商品详情时记录最近浏览 | -| 历史清理 | 支持删除单条或清空历史 | - -建议表: - -- `user_favorite(id, user_id, product_id, created_at)`,加唯一约束 `(user_id, product_id)`。 -- `browse_history(id, user_id, product_id, viewed_at)`,按 `(user_id, viewed_at)` 建索引。 - -答辩速答: - -| 可能问题 | 推荐回答 | -|----------|----------| -| 如何避免重复收藏? | 在数据库加 `UNIQUE(user_id, product_id)`,接口层也先查询;即使并发重复请求,数据库约束仍能兜底。 | -| 未登录用户能收藏吗? | 不允许,收藏属于用户资产,必须通过登录态拿到 user_id。 | -| 浏览历史越来越多怎么办? | 查询时分页;也可以只保留最近 N 条,或定期清理超过指定时间的数据。 | -| 商品下架后收藏里怎么显示? | 可以保留记录,但前端标记“已下架”,点击时不允许购买。 | - -### X02 后台操作日志 - -**推荐负责人:成员F 主责,成员A 配合公共中间件,B/C/E 接入关键后台操作。** - -推荐原因: - -- 和后台管理天然相关,能增强材料质量和答辩可信度。 -- 不影响核心购物链路,即使时间紧也能渐进接入。 -- 验收问题好回答:谁、什么时间、对什么资源、做了什么、结果如何。 - -建议范围: - -| 功能 | 最小可验收实现 | -|------|----------------| -| 商品管理日志 | 新增、修改、上下架商品时记录 | -| 订单管理日志 | 发货、取消、状态变更时记录 | -| 用户管理日志 | 禁用/启用用户时记录 | -| 日志查询 | 后台按操作人、模块、时间分页查询 | - -建议表: - -- `admin_operation_log(id, admin_id, action, resource_type, resource_id, before_json, after_json, result, ip, created_at)`。 - -答辩速答: - -| 可能问题 | 推荐回答 | -|----------|----------| -| 怎么知道是谁操作的? | 后台接口必须登录,服务端从 JWT/登录态中解析 admin_id,不能相信前端传来的用户名。 | -| 怎么知道改了什么? | 修改前读取旧数据作为 before,修改后保存 after,核心字段用 JSON 快照记录。 | -| 日志失败会不会影响主流程? | 核心操作成功后写日志;若日志是强审计要求可放同一事务,课程项目可先记录失败并告警。 | -| 普通用户能看日志吗? | 不能,日志查询接口只允许管理员角色访问。 | - -### 备选选做功能 - -| 功能 | 适合情况 | 风险 | -|------|----------|------| -| 商品评价与晒图 | 前端展示时间充足,想让演示更像真实商城 | 需要校验“已购买/已完成订单后才能评价”,图片上传会增加部署复杂度 | -| 站内消息通知 | 订单状态变更已经做好 | 如果做实时推送会升级到 WebSocket,复杂度上升 | -| 售后流程 | 订单模块完成较早 | 退款状态和库存/支付关系容易讲不清 | -| 多规格 SKU | 商品、购物车、订单都很强 | 会影响库存模型、下单逻辑、页面交互,不建议作为首选 | - -## 四、推荐挑战模块 - -### 首选:C08 支付回调幂等与对账 - -**推荐负责人:成员E 主责,成员F 负责边界脚本和验收记录。** - -推荐原因: - -- 和订单支付模块强相关,不需要额外引入复杂中间件。 -- 现场边界演示非常清楚:重复回调、乱序回调、异常对账。 -- 原理抽问容易讲清:唯一约束、状态机、事务、条件更新、对账 SQL。 - -最小可验收范围: - -| 能力 | 做法 | -|------|------| -| 重复回调幂等 | `payment_no` 或 `callback_no` 加唯一约束,重复请求返回同一处理结果 | -| 乱序回调保护 | 订单已支付后,失败回调不能把订单改回失败 | -| 状态机控制 | 订单只允许 `待支付 -> 已支付`、`待支付 -> 已取消` 等合法流转 | -| 事务一致性 | 更新支付记录和订单状态放在同一事务中 | -| 对账检查 | 查询“支付成功但订单未支付 / 金额不一致 / 有支付无订单”等差异 | - -建议补充字段: - -- `payment.callback_no`:模拟第三方回调流水号,唯一。 -- `payment.raw_payload`:保存回调原文,方便排查。 -- `payment.processed_at`:回调处理时间。 - -现场边界演示脚本: - -| 步骤 | 演示内容 | 预期结果 | -|------|----------|----------| -| 1 | 创建一笔待支付订单 | 订单状态为待支付,生成 payment_no | -| 2 | 连续发送 5 次相同“支付成功回调” | 第一次更新订单为已支付,后 4 次返回“已处理”,订单金额和状态不重复变化 | -| 3 | 在已支付后发送“支付失败回调” | 系统拒绝回退状态,订单仍为已支付 | -| 4 | 构造一条支付成功但订单未更新的数据 | 对账接口/SQL 能查出差异 | -| 5 | 展示支付表、订单表、对账结果 | 能证明数据没有重复入账,异常能被发现 | - -答辩速答: - -| 可能问题 | 推荐回答 | -|----------|----------| -| 什么是幂等? | 同一个请求执行一次或多次,最终业务结果一致。支付回调可能重复到达,所以必须保证重复成功回调不会重复改订单、重复扣库存或重复记账。 | -| 为什么只靠前端按钮防重复不够? | 回调来自第三方或模拟支付服务,网络重试、用户刷新、服务超时都会导致重复请求,必须在服务端和数据库层兜底。 | -| 如何保证不会重复处理? | 用唯一约束识别同一回调或同一支付单;处理时在事务中查询当前状态,只允许合法状态流转。 | -| 乱序回调怎么处理? | 状态机只允许状态向前流转,例如已支付不能被失败回调改成失败;非法流转记录日志并返回当前状态。 | -| 为什么要对账? | 即使接口有事务,也可能出现人工改库、异常中断、消息丢失等问题;对账能发现支付记录和订单状态不一致。 | -| 对账 SQL 查什么? | 查支付成功但订单未支付、订单已支付但无成功支付记录、支付金额和订单金额不一致这三类核心差异。 | - -### 备选挑战:C10 容器化部署与负载均衡 - -**适合成员A/F 有时间时做加分项。** - -可演示内容: - -- Docker Compose 启动 MySQL、前端、两个后端实例、Nginx。 -- 连续请求 `/api/health`,响应中返回实例名,证明轮询到不同后端。 -- 用简单压测工具请求 Nginx 入口,展示成功率和平均响应时间。 - -答辩核心: - -- 登录态使用 JWT 时,后端实例无状态,请求打到任意实例都能验签。 -- 多实例共享同一个 MySQL,业务数据一致。 -- Nginx 负责反向代理和负载均衡,不负责业务逻辑。 - -## 五、不建议作为首选的挑战模块 - -| 挑战 | 不建议原因 | -|------|------------| -| C01 秒杀与防超卖 | 很容易被追问事务隔离级别、锁粒度、库存回滚、压测数据,做不好会被看出问题 | -| C02 优惠券规则引擎 | 分摊、退款、叠加规则细节多,验收边界复杂 | -| C04 商品搜索进阶 | 如果不用搜索引擎,自实现倒排索引要讲清分词和性能;如果用搜索引擎,部署成本上升 | -| C05 猜你喜欢 | 容易被问算法依据和冷启动,若只是硬编码推荐会显得薄 | -| C07 缓存与性能优化 | 必须讲清缓存一致性,改价后何时生效、如何失效,容易和 Redis 部署耦合 | - -## 六、每人必须准备的答辩口径 - -| 成员 | 必背问题 | 答题要点 | -|------|----------|----------| -| 成员A | 项目整体架构是什么? | 前端 Vue 调 API,后端分层处理业务,数据库持久化;公共鉴权、统一响应、异常处理由基建统一提供。 | -| 成员A | 为什么要统一接口返回格式? | 前端可以统一处理成功、失败、未登录、无权限;也便于测试和错误定位。 | -| 成员A | 当前技术栈到底是什么? | 前端 Vue 3 + Vite + TypeScript + Element Plus,后端 C# + ASP.NET Core Web API,数据库 MySQL,数据访问后续接入 EF Core;答辩时以代码和 README 的 C# 口径为准。 | -| 成员B | 密码如何存储? | 不存明文,使用 BCrypt/PBKDF2 等不可逆哈希,并说明登录时是“输入密码哈希后比对”。 | -| 成员B | 地址默认值如何保证只有一个? | 设置新默认地址时,在事务中先把该用户其他地址改为非默认,再把当前地址设为默认。 | -| 成员B | 禁用用户后如何阻止登录? | 登录时校验 status;已登录用户访问接口时也可在鉴权中二次校验用户状态。 | -| 成员C | 商品分页怎么做? | 接收 page/pageSize,校验范围,SQL 使用 LIMIT/OFFSET 或 ORM 分页,并返回 total。 | -| 成员C | 商品下架后购物车/订单怎么处理? | 购物车可展示但结算时禁止下单;历史订单保留商品快照,不受下架影响。 | -| 成员C | 搜索怎么实现? | 最小实现用名称 LIKE 和分类筛选;如后续优化可加索引或搜索引擎。 | -| 成员D | 购物车金额为什么不能只信前端? | 前端价格可被篡改,后端结算时必须重新查询商品价格和库存。 | -| 成员D | 数量修改有哪些边界? | 数量必须大于 0,不能超过库存;商品下架或不存在时不能结算。 | -| 成员D | 购物车如何避免重复商品行? | 表上加 `UNIQUE(user_id, product_id)`,重复加购时增加数量或覆盖数量。 | -| 成员E | 为什么订单项要保存商品名称和价格快照? | 商品之后可能改名、改价或下架,但历史订单必须保持下单当时的数据。 | -| 成员E | 库存扣减怎么保证一致? | 下单时在事务内校验库存并扣减,失败则回滚;支付和订单状态也走状态机。 | -| 成员E | 支付回调重复怎么办? | 用支付单号/回调流水唯一约束 + 事务 + 状态机,重复请求返回当前状态,不重复处理。 | -| 成员F | 测试覆盖了什么? | 覆盖 F01-F13 必做功能、2 个选做、C08 挑战模块;每个模块都有正常和异常用例。 | -| 成员F | 部署如何保证可复现? | 使用 Docker Compose 或明确部署步骤,数据库初始化脚本提供 30+ 商品和测试账号。 | -| 成员F | 压测/边界数据怎么证明? | 保存脚本、请求参数、执行结果、数据库截图或日志,证明重复回调/乱序回调后状态仍正确。 | - -## 七、交叉 Code Review 安排 - -| 被审查模块 | 审查人 | 重点检查 | -|------------|--------|----------| -| 公共基建 | 成员F | 启动流程、部署配置、统一错误返回 | -| 用户模块 | 成员A | 鉴权接入、密码安全、权限边界 | -| 商品模块 | 成员B | 商品接口是否符合前台/后台权限 | -| 购物车模块 | 成员C | 商品状态、库存、价格引用是否正确 | -| 订单支付模块 | 成员D | 购物车到订单的数据传递、金额计算 | -| 测试部署 / 日志 | 成员E | 订单支付挑战模块测试是否可复现 | - -## 八、验收演示顺序 - -1. 展示首页、商品列表、分类筛选、关键词搜索、商品详情。 -2. 注册新用户,登录,维护个人信息和地址。 -3. 收藏商品、查看浏览历史。 -4. 加入购物车,修改数量,删除商品,勾选结算。 -5. 提交订单,展示订单详情和订单项价格快照。 -6. 模拟支付,订单状态变为已支付。 -7. 后台登录,管理商品、发货订单、禁用用户,查看后台操作日志。 -8. 演示 C08:重复回调、乱序回调、对账差异。 -9. 展示测试报告、部署方式、Git 提交和 Code Review 记录。 - -## 九、正式验收前检查清单 - -- [ ] README 分工表填入真实姓名、学号和模块。 -- [ ] 后端技术栈说明与实际代码一致:C# + ASP.NET Core Web API + MySQL,数据访问按 EF Core 接入计划说明。 -- [ ] 必做 F01-F13 每项都有演示路径。 -- [ ] 选做 X01、X02 有页面、接口、数据库记录。 -- [ ] 挑战 C08 有脚本、边界数据、截图/日志、原理说明。 -- [ ] 每人至少准备 3 个自己模块问题和 1 个其他模块问题。 -- [ ] 每人 Git 提交记录和负责模块一致。 -- [ ] docs/06-模块实现说明 下每个模块负责人都填写完成。 diff --git "a/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/\346\210\220\345\221\230\345\274\200\345\217\221\345\210\206\346\224\257\350\247\204\345\210\222.md" "b/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/\346\210\220\345\221\230\345\274\200\345\217\221\345\210\206\346\224\257\350\247\204\345\210\222.md" deleted file mode 100644 index 4627f0f..0000000 --- "a/docs/05-\346\200\273\347\273\223\347\255\224\350\276\251/\346\210\220\345\221\230\345\274\200\345\217\221\345\210\206\346\224\257\350\247\204\345\210\222.md" +++ /dev/null @@ -1,161 +0,0 @@ -# 成员开发分支规划 - -> 适用场景:6 人小组按模块负责制开发。 -> 当前仓库已有分支:`main`、`develop`、`feature/base-setup`。建议所有成员从 `develop` 拉取自己的功能分支,完成后合并回 `develop`,最终由组长统一合并到 `main`。 - -## 一、每个成员需要创建的分支 - -| 成员 | 负责模块 | 建议分支名 | 从哪个分支创建 | 合并目标 | 说明 | -|------|----------|------------|----------------|----------|------| -| 成员A | 公共基建 / 组长 / 鉴权 / 集成联调 | `feature/base-setup` | `develop` | `develop` | 当前已存在,可继续用于项目骨架、统一响应、鉴权、权限、公共配置 | -| 成员B | 用户模块 | `feature/user-module` | `develop` | `develop` | 用户注册、登录、退出、个人信息、收货地址、后台用户管理 | -| 成员C | 商品模块 | `feature/product-module` | `develop` | `develop` | 商品分类、列表、搜索、详情、后台商品管理、上下架 | -| 成员D | 购物车模块 | `feature/cart-module` | `develop` | `develop` | 加购、数量修改、删除、勾选、金额计算、结算入口 | -| 成员E | 订单支付模块 / C08 挑战 | `feature/order-payment-module` | `develop` | `develop` | 提交订单、订单状态、模拟支付、后台订单管理、支付回调幂等与对账 | -| 成员F | 测试部署 / 操作日志 | `feature/test-deploy-log` | `develop` | `develop` | 测试计划、测试报告、部署、演示数据、后台操作日志、挑战演示脚本 | - -## 二、可选的子分支 - -如果某个模块工作量太大,可以在自己的主功能分支下再拆子分支。子分支完成后先合并回自己的主功能分支,再合并到 `develop`。 - -| 成员 | 可选子分支 | 用途 | -|------|------------|------| -| 成员A | `feature/auth` | JWT 鉴权、中间件、权限控制 | -| 成员A | `feature/common-response` | 统一响应格式、统一异常处理 | -| 成员B | `feature/user-auth` | 注册、登录、退出 | -| 成员B | `feature/user-address` | 个人信息、收货地址 | -| 成员C | `feature/product-catalog` | 分类、商品列表、搜索、详情 | -| 成员C | `feature/admin-product` | 后台商品管理、上下架 | -| 成员D | `feature/cart-crud` | 加购、修改数量、删除 | -| 成员D | `feature/cart-checkout` | 勾选、金额计算、结算入口 | -| 成员E | `feature/order-core` | 下单、订单列表、订单详情、取消订单 | -| 成员E | `feature/payment-idempotency` | 模拟支付、C08 支付回调幂等、对账 | -| 成员F | `feature/test-report` | 测试计划、测试用例、测试报告 | -| 成员F | `feature/admin-operation-log` | 后台操作日志 | -| 成员F | `feature/deploy` | Docker Compose、部署说明、演示数据 | - -## 三、创建分支命令 - -所有成员首次创建分支前,先同步远端分支: - -```bash -git checkout develop -git pull origin develop -``` - -成员B创建用户模块分支: - -```bash -git checkout -b feature/user-module -git push -u origin feature/user-module -``` - -成员C创建商品模块分支: - -```bash -git checkout -b feature/product-module -git push -u origin feature/product-module -``` - -成员D创建购物车模块分支: - -```bash -git checkout -b feature/cart-module -git push -u origin feature/cart-module -``` - -成员E创建订单支付模块分支: - -```bash -git checkout -b feature/order-payment-module -git push -u origin feature/order-payment-module -``` - -成员F创建测试部署和操作日志分支: - -```bash -git checkout -b feature/test-deploy-log -git push -u origin feature/test-deploy-log -``` - -成员A如果继续使用当前分支: - -```bash -git checkout feature/base-setup -git push -u origin feature/base-setup -``` - -## 四、每日同步规则 - -每天开始开发前,每个成员在自己的分支执行: - -```bash -git fetch origin -git merge origin/develop -``` - -如果出现冲突,在自己的功能分支解决,不要把冲突带到 `develop`。 - -## 五、提交信息建议 - -| 模块 | 提交示例 | -|------|----------| -| 公共基建 | `feat: add jwt authentication middleware` | -| 公共基建 | `feat: add unified api response model` | -| 用户模块 | `feat: add user login api` | -| 用户模块 | `feat: add address management page` | -| 商品模块 | `feat: add product list api with pagination` | -| 商品模块 | `feat: add admin product management page` | -| 购物车模块 | `feat: add cart item quantity update` | -| 购物车模块 | `fix: validate product stock before checkout` | -| 订单支付模块 | `feat: add order creation workflow` | -| 订单支付模块 | `feat: add idempotent payment callback` | -| 测试部署 | `test: add user and product test cases` | -| 测试部署 | `docs: add deployment verification record` | -| 操作日志 | `feat: add admin operation log query` | - -## 六、合并顺序建议 - -| 顺序 | 分支 | 合并原因 | -|------|------|----------| -| 1 | `feature/base-setup` | 公共基建先合并,其他模块依赖鉴权、统一响应、公共配置 | -| 2 | `feature/user-module` | 购物车、订单都需要登录用户和地址 | -| 3 | `feature/product-module` | 购物车、订单都需要商品价格、库存、上下架状态 | -| 4 | `feature/cart-module` | 订单创建依赖购物车结算数据 | -| 5 | `feature/order-payment-module` | 主购物流程最后闭环,包含支付和挑战模块 | -| 6 | `feature/test-deploy-log` | 最后统一补测试报告、部署验证、日志和演示脚本 | - -## 七、冲突责任人 - -| 冲突文件类型 | 决策负责人 | -|--------------|------------| -| `backend/src/Eshop.Api/Program.cs`、鉴权、统一响应 | 成员A | -| 用户、地址、后台用户相关文件 | 成员B | -| 商品、分类、后台商品相关文件 | 成员C | -| 购物车相关文件 | 成员D | -| 订单、支付、C08 对账相关文件 | 成员E | -| 测试、部署、日志、演示脚本相关文件 | 成员F | -| `README.md`、接口文档、数据库设计、`schema.sql` | 相关模块负责人 + 成员A 最终确认 | - -## 八、最终分支策略 - -```text -main - ↑ 第 10 天验收前由组长从 develop 合并 -develop - ↑ 每个模块 Code Review 后合并 -feature/base-setup -feature/user-module -feature/product-module -feature/cart-module -feature/order-payment-module -feature/test-deploy-log -``` - -要求: - -- `main` 只保留稳定可演示版本。 -- `develop` 用于集成联调。 -- 每个成员只在自己的 `feature/*` 分支开发。 -- 公共文件变更先通知组长和相关模块负责人。 -- 合并到 `develop` 前必须先把 `origin/develop` 合入自己的分支并解决冲突。 -- Gitee From bc1e97718920df0cd446e02cfa96d2512dccf146 Mon Sep 17 00:00:00 2001 From: Lo <2940191578@qq.com> Date: Wed, 22 Jul 2026 17:24:31 +0800 Subject: [PATCH 07/15] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=97=A5=E6=8A=A5?= =?UTF-8?q?=E4=B8=8E=E4=BC=9A=E8=AE=AE=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...32\350\256\256\344\270\273\351\242\230.md" | 33 +++++++++++++++++++ ...04\350\264\235\347\204\266- 2026-07-22.md" | 13 ++++++++ 2 files changed, 46 insertions(+) create mode 100644 "docs/04-\344\274\232\350\256\256\350\256\260\345\275\225/2026-07-22-\344\274\232\350\256\256\344\270\273\351\242\230.md" create mode 100644 "reports/daily/\351\273\204\350\264\235\347\204\266/\346\227\245\346\212\245 - \351\273\204\350\264\235\347\204\266- 2026-07-22.md" diff --git "a/docs/04-\344\274\232\350\256\256\350\256\260\345\275\225/2026-07-22-\344\274\232\350\256\256\344\270\273\351\242\230.md" "b/docs/04-\344\274\232\350\256\256\350\256\260\345\275\225/2026-07-22-\344\274\232\350\256\256\344\270\273\351\242\230.md" new file mode 100644 index 0000000..01f7116 --- /dev/null +++ "b/docs/04-\344\274\232\350\256\256\350\256\260\345\275\225/2026-07-22-\344\274\232\350\256\256\344\270\273\351\242\230.md" @@ -0,0 +1,33 @@ +# 会议纪要:(主题) + +| 项目 | 内容 | +| -------------- | ---------------------------------------------- | +| 会议时间 | 2026-07-22 15:50 | +| 参会人员 | 魏銮泓,黄贝然,方佳纯,吴渊博,杨光沣,林剑锋 | +| 缺席人员及原因 | 无 | +| 记录人 | 黄贝然 | + +## 一、会议议题 + +1.主仓库fork与分支创建 +2.组内工作分配 + +## 二、讨论内容与结论 + +| 议题 | 讨论要点 | 结论/决策 | +| -------------------- | -------------------------------- | -------------------------------------- | +| 主仓库fork与分支创建 | 分支命名 | 按各组员负责的功能模块创建并且规范命名 | +| 组内成员工作分配 | 各成员负责的模块分配工作 | 已确定各成员所负责的模块 | +| 原型与产品需求设计 | 功能模块是否要增加,选做功能确立 | 待定 | + +## 三、行动项 + +| 事项 | 负责人 | 截止时间 | 状态 | +| ------------------ | -------------- | ---------- | ---- | +| 监督仓库分支创建 | 魏銮泓 | 2026.07.22 | 完成 | +| 组内工作分工确立 | 魏銮泓 | 2026.07.22 | 完成 | +| 原型与产品需求设计 | 黄贝然,魏銮泓 | 2026.07.24 | 待办 | + +## 四、遗留问题 + +选做功能尚未确定 \ No newline at end of file diff --git "a/reports/daily/\351\273\204\350\264\235\347\204\266/\346\227\245\346\212\245 - \351\273\204\350\264\235\347\204\266- 2026-07-22.md" "b/reports/daily/\351\273\204\350\264\235\347\204\266/\346\227\245\346\212\245 - \351\273\204\350\264\235\347\204\266- 2026-07-22.md" new file mode 100644 index 0000000..ac85f86 --- /dev/null +++ "b/reports/daily/\351\273\204\350\264\235\347\204\266/\346\227\245\346\212\245 - \351\273\204\350\264\235\347\204\266- 2026-07-22.md" @@ -0,0 +1,13 @@ +# 日报 - 黄贝然- 2026-07-22 + +## 今日完成 + +- 创建新的分支 +- 开始组内分配工作 +- 编写计划文档 + +## 明日计划 + +- 研究需求设计 +- 原型设计 + -- Gitee From f3ae022bbd9794facc6392f1838b1abbbbf14e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=85=89=E6=B2=A3?= <895499445@qq.com> Date: Wed, 22 Jul 2026 17:22:03 +0800 Subject: [PATCH 08/15] =?UTF-8?q?=E6=8F=90=E4=BA=A42026=E5=B9=B47=E6=9C=88?= =?UTF-8?q?22=E6=97=A5=E6=97=A5=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20260722-\346\227\245\346\212\245.md" | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 "reports/\346\227\245\346\212\245/20260722-\346\227\245\346\212\245.md" diff --git "a/reports/\346\227\245\346\212\245/20260722-\346\227\245\346\212\245.md" "b/reports/\346\227\245\346\212\245/20260722-\346\227\245\346\212\245.md" new file mode 100644 index 0000000..8441beb --- /dev/null +++ "b/reports/\346\227\245\346\212\245/20260722-\346\227\245\346\212\245.md" @@ -0,0 +1,25 @@ +姓名:杨光沣 +负责模块:测试 + 部署 + 选做功能 +今天完成: + 1. 创建 feature/test-deploy-log 开发分支,搭建测试与部署模块的 Git 工作流。 + 2. 通读验收标准文档,梳理 F01-F13 共 13 项必做功能验收要点,明确每项的前端/后端/数据库验证路径。 + 3. 编写测试计划初稿(docs/03-测试文档/测试计划.md),按模块划分编写 51 条测试用例,覆盖全部必做功能(F01~F13)的正常与异常场景,用例数满足 ≥40 条要求。 + 4. 建立验收项-用例对照表,确保 F01 用户注册到 F13 后台用户管理逐项可追溯。 +遇到问题: + - 问题:项目技术栈(前端框架、后端语言、数据库)尚未确定,测试环境配置暂时无法填写。 + 解决状态:未解决 + 解决方式:需等组长确认技术栈后补充测试计划"测试环境"章节。 + - 问题:F10 模拟支付的具体实现方式不明确(前端模拟 or 后端写死回调),影响支付异常用例设计。 + 解决状态:未解决 + 解决方式:需等订单+支付模块负责人确认方案后补充 TC-ORD-09/10 的异常分支。 +明天计划: + 1. 和各模块负责人对齐接口设计文档,按接口逐一补充接口测试用例(正常 + 异常参数)。 + 2. 搭建 Postman/Apifox 接口测试集合骨架,按模块分目录。 + 3. 编写缺陷记录模板(docs/03-测试文档/缺陷记录.md)。 + 4. 如技术栈已确定,着手准备本地测试环境搭建。 +需要谁配合: + 1. 组长确认项目技术栈(前端/后端/数据库),方便搭建测试环境。 + 2. 各模块负责人提供接口设计初稿,方便对齐接口测试用例。 + 3. 订单+支付模块负责人确认模拟支付方案(前端模拟 or 后端接口),以便补充支付异常场景用例。 +今日提交记录: + docs: 编写测试计划初稿,覆盖 F01-F13 验收清单共 51 条用例 -- Gitee From 80578621e817a4a7801ba807813bab8addd23d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=89=91=E9=94=8B?= <1079197720@qq.com> Date: Wed, 22 Jul 2026 17:18:26 +0800 Subject: [PATCH 09/15] =?UTF-8?q?feat:=E4=BB=8A=E5=A4=A9=E6=97=A5=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...345\271\2647\346\234\21022\346\227\245.md" | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 "reports/\346\236\227\345\211\221\351\224\213/2026\345\271\2647\346\234\21022\346\227\245.md" diff --git "a/reports/\346\236\227\345\211\221\351\224\213/2026\345\271\2647\346\234\21022\346\227\245.md" "b/reports/\346\236\227\345\211\221\351\224\213/2026\345\271\2647\346\234\21022\346\227\245.md" new file mode 100644 index 0000000..cf689cc --- /dev/null +++ "b/reports/\346\236\227\345\211\221\351\224\213/2026\345\271\2647\346\234\21022\346\227\245.md" @@ -0,0 +1,46 @@ +# - 第1天 + +## 今日工作 + +| 任务 | 状态 | 说明 | +|------|------|------| +| 梳理购物车业务流程 | ✅ 完成 | 确认加购→改数量→删除→勾选→结算完整流程 | +| 梳理结算入口和接口依赖 | ✅ 完成 | 结算需依赖用户模块(登录态)、商品模块(价格/库存) | + +## 购物车业务流程梳理 + +### 核心流程 + +``` +用户登录 → 商品详情页加购 → 购物车页面 → 勾选商品 → 结算 → 创建订单 → 跳转支付 +``` + +### 关键接口依赖 + +| 依赖模块 | 接口 | 用途 | +|----------|------|------| +| 用户模块 | `GET /api/user/info` | 获取用户信息,校验登录态 | +| 商品模块 | `GET /api/product/{id}` | 获取商品价格、库存、上下架状态 | +| 订单模块 | `POST /api/order/create` | 结算时创建订单 | + +### 购物车表结构设计(初稿) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| user_id | bigint | 用户ID | +| product_id | bigint | 商品ID | +| quantity | int | 数量 | +| selected | bit | 是否勾选 | +| created_at | datetime | 创建时间 | +| updated_at | datetime | 更新时间 | + +## 明日计划 + +- 设计购物车增删改查接口 +- 设计勾选、金额计算接口 +- 与成员A确认统一返回格式 + +--- + + -- Gitee From 6c70eec0871c67b060aa89c4bc0ae04360792677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AD=8F=E9=8A=AE=E6=B3=93?= <3422047492@qq.com> Date: Wed, 22 Jul 2026 09:29:44 +0000 Subject: [PATCH 10/15] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20re?= =?UTF-8?q?ports/=E6=9E=97=E5=89=91=E9=94=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...345\271\2647\346\234\21022\346\227\245.md" | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 "reports/\346\236\227\345\211\221\351\224\213/2026\345\271\2647\346\234\21022\346\227\245.md" diff --git "a/reports/\346\236\227\345\211\221\351\224\213/2026\345\271\2647\346\234\21022\346\227\245.md" "b/reports/\346\236\227\345\211\221\351\224\213/2026\345\271\2647\346\234\21022\346\227\245.md" deleted file mode 100644 index cf689cc..0000000 --- "a/reports/\346\236\227\345\211\221\351\224\213/2026\345\271\2647\346\234\21022\346\227\245.md" +++ /dev/null @@ -1,46 +0,0 @@ -# - 第1天 - -## 今日工作 - -| 任务 | 状态 | 说明 | -|------|------|------| -| 梳理购物车业务流程 | ✅ 完成 | 确认加购→改数量→删除→勾选→结算完整流程 | -| 梳理结算入口和接口依赖 | ✅ 完成 | 结算需依赖用户模块(登录态)、商品模块(价格/库存) | - -## 购物车业务流程梳理 - -### 核心流程 - -``` -用户登录 → 商品详情页加购 → 购物车页面 → 勾选商品 → 结算 → 创建订单 → 跳转支付 -``` - -### 关键接口依赖 - -| 依赖模块 | 接口 | 用途 | -|----------|------|------| -| 用户模块 | `GET /api/user/info` | 获取用户信息,校验登录态 | -| 商品模块 | `GET /api/product/{id}` | 获取商品价格、库存、上下架状态 | -| 订单模块 | `POST /api/order/create` | 结算时创建订单 | - -### 购物车表结构设计(初稿) - -| 字段 | 类型 | 说明 | -|------|------|------| -| id | bigint | 主键 | -| user_id | bigint | 用户ID | -| product_id | bigint | 商品ID | -| quantity | int | 数量 | -| selected | bit | 是否勾选 | -| created_at | datetime | 创建时间 | -| updated_at | datetime | 更新时间 | - -## 明日计划 - -- 设计购物车增删改查接口 -- 设计勾选、金额计算接口 -- 与成员A确认统一返回格式 - ---- - - -- Gitee From b8102c0456b54e615372439c15edcb32cf1ab822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AD=8F=E9=8A=AE=E6=B3=93?= <3422047492@qq.com> Date: Wed, 22 Jul 2026 09:30:21 +0000 Subject: [PATCH 11/15] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20re?= =?UTF-8?q?ports/=E6=97=A5=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20260722-\346\227\245\346\212\245.md" | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 "reports/\346\227\245\346\212\245/20260722-\346\227\245\346\212\245.md" diff --git "a/reports/\346\227\245\346\212\245/20260722-\346\227\245\346\212\245.md" "b/reports/\346\227\245\346\212\245/20260722-\346\227\245\346\212\245.md" deleted file mode 100644 index 8441beb..0000000 --- "a/reports/\346\227\245\346\212\245/20260722-\346\227\245\346\212\245.md" +++ /dev/null @@ -1,25 +0,0 @@ -姓名:杨光沣 -负责模块:测试 + 部署 + 选做功能 -今天完成: - 1. 创建 feature/test-deploy-log 开发分支,搭建测试与部署模块的 Git 工作流。 - 2. 通读验收标准文档,梳理 F01-F13 共 13 项必做功能验收要点,明确每项的前端/后端/数据库验证路径。 - 3. 编写测试计划初稿(docs/03-测试文档/测试计划.md),按模块划分编写 51 条测试用例,覆盖全部必做功能(F01~F13)的正常与异常场景,用例数满足 ≥40 条要求。 - 4. 建立验收项-用例对照表,确保 F01 用户注册到 F13 后台用户管理逐项可追溯。 -遇到问题: - - 问题:项目技术栈(前端框架、后端语言、数据库)尚未确定,测试环境配置暂时无法填写。 - 解决状态:未解决 - 解决方式:需等组长确认技术栈后补充测试计划"测试环境"章节。 - - 问题:F10 模拟支付的具体实现方式不明确(前端模拟 or 后端写死回调),影响支付异常用例设计。 - 解决状态:未解决 - 解决方式:需等订单+支付模块负责人确认方案后补充 TC-ORD-09/10 的异常分支。 -明天计划: - 1. 和各模块负责人对齐接口设计文档,按接口逐一补充接口测试用例(正常 + 异常参数)。 - 2. 搭建 Postman/Apifox 接口测试集合骨架,按模块分目录。 - 3. 编写缺陷记录模板(docs/03-测试文档/缺陷记录.md)。 - 4. 如技术栈已确定,着手准备本地测试环境搭建。 -需要谁配合: - 1. 组长确认项目技术栈(前端/后端/数据库),方便搭建测试环境。 - 2. 各模块负责人提供接口设计初稿,方便对齐接口测试用例。 - 3. 订单+支付模块负责人确认模拟支付方案(前端模拟 or 后端接口),以便补充支付异常场景用例。 -今日提交记录: - docs: 编写测试计划初稿,覆盖 F01-F13 验收清单共 51 条用例 -- Gitee From f96e94ec40e151a47c5060d82312fb0863c1b0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=B8=8A=E5=8D=9A?= <1914734353@qq.com> Date: Wed, 22 Jul 2026 17:38:50 +0800 Subject: [PATCH 12/15] =?UTF-8?q?=E2=80=9C=E6=97=A5=E6=8A=A5=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\346\227\245\346\212\245-2026-07-22.md" | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 "reports/daily/\345\220\264\346\270\212\345\215\232/\346\227\245\346\212\245-2026-07-22.md" diff --git "a/reports/daily/\345\220\264\346\270\212\345\215\232/\346\227\245\346\212\245-2026-07-22.md" "b/reports/daily/\345\220\264\346\270\212\345\215\232/\346\227\245\346\212\245-2026-07-22.md" new file mode 100644 index 0000000..cfaee0d --- /dev/null +++ "b/reports/daily/\345\220\264\346\270\212\345\215\232/\346\227\245\346\212\245-2026-07-22.md" @@ -0,0 +1,46 @@ +# 日报 - 2026-07-22 + +**成员**:吴渊博(成员E / 订单支付模块负责人) + +## 今日完成 + +- [x] 从 `develop` 拉分支 `feature/order-payment-module` 并 `push -u origin`,后续订单 / 支付 / C08 全部在此分支开发 +- [x] 梳理订单状态机:枚举 6 个核心状态(`待支付 → 已支付 → 已发货 → 已收货` / `待支付 → 已取消` / `已支付 → 退款中 → 已退款`),把所有非法流转写进 `docs/02-设计文档/02-状态机.md` 的禁用流转表 +- [x] 梳理支付状态机:`待支付 / 支付成功 / 支付失败 / 已关闭`,明确"已支付订单不能被失败回调改回失败"作为状态机硬约束 +- [x] 输出 C08 挑战模块方案稿(`docs/02-设计文档/02-C08支付回调幂等方案.md`):`payment.callback_no` 加唯一约束 + 事务内查状态 + 状态机判定 + 对账 SQL 四件套 +- [x] 设计三张核心表初稿: + - `order(id, order_no, user_id, address_id, total_amount, status, created_at, updated_at)` + - `order_item(id, order_id, product_id, product_name, product_price, quantity, subtotal)` —— 名称 / 价格快照字段明确 + - `payment(id, order_id, payment_no, callback_no, amount, status, raw_payload, processed_at, created_at)` —— `callback_no` 加唯一索引 +- [x] 起草订单模块接口文档(`docs/02-设计文档/02-订单接口.md`):`POST /api/orders`(结算下单)、`GET /api/orders`(分页列表)、`GET /api/orders/{id}`(含订单项快照)、`POST /api/orders/{id}/cancel`、`POST /api/payments/callback`(幂等)、`POST /api/admin/orders/{id}/ship` +- [x] 与成员A对齐鉴权约定:所有订单接口从登录态拿 `user_id`,不接前端传的 userId;管理端订单发货走 `admin` 角色 +- [x] 与成员D对齐购物车 → 订单数据交接:结算页只传 `cart_item_id` 列表和 `address_id`,金额 / 商品信息以后端重查为准,不信前端 +- [x] 与成员C对齐商品快照字段:`product_name` / `product_price` 直接从 `product` 表读,下单时落快照,不依赖后续商品不变 +- [x] 创建模块说明骨架:`docs/06-模块实现说明/06-订单支付模块.md`,先放表结构、状态机、接口清单三节,正文等开发中补 + +## 今日计划 + +- [x] 创建 feature 分支 +- [x] 梳理订单 / 支付状态机 +- [x] C08 挑战方案初稿 +- [x] 接口设计初稿 + +## 阻塞与求助 + +- 阻塞:无 +- 待跟进:等成员A 第 3 天前把 JWT 鉴权和统一响应搭出来,下单接口要在那之后才能联调登录态;提前在接口文档里写明依赖,避免打回返工 + +## 明日计划 + +- [ ] 等成员A 第 2 天交付统一响应格式后,把今日接口文档里的错误码(`订单不存在` / `库存不足` / `幂等已处理`)对齐到全局定义 +- [ ] 起 `backend/src/Eshop.Api/Entities/Order.cs`、`OrderItem.cs`、`Payment.cs` 三个实体类,对应今日设计的表 +- [ ] 起 EF Core `DbContext` 中三张表的 `IEntityTypeConfiguration` 或 `OnModelCreating` 配置 +- [ ] 起草 `OrderService` 接口(`CreateAsync` / `ListAsync` / `GetAsync` / `CancelAsync`),先写签名和注释,不实现 +- [ ] 准备 C08 幂等的"防重唯一键 + 事务内状态查询"代码骨架(伪代码即可) +- [ ] 在 PR 里只提交表结构 + 实体 + 接口签名草案,不包含 Controller 实现,便于成员D 提前对齐数据约定 + +## 进度截图 + +待补:今日画的订单状态机和支付状态机流程图(用 mermaid 渲染后存到 `docs/02-设计文档/02-状态机.png`)。 + + -- Gitee From 984a67e4810655cb64c414b1531b46f0b5426ed6 Mon Sep 17 00:00:00 2001 From: FXR-c <427808324@qq.com> Date: Wed, 22 Jul 2026 18:43:13 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20260722-\346\226\271\344\275\263\347\272\257.md" | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 "reports/daily/\346\226\271\344\275\263\347\272\257/20260722-\346\226\271\344\275\263\347\272\257.md" diff --git "a/reports/daily/\346\226\271\344\275\263\347\272\257/20260722-\346\226\271\344\275\263\347\272\257.md" "b/reports/daily/\346\226\271\344\275\263\347\272\257/20260722-\346\226\271\344\275\263\347\272\257.md" new file mode 100644 index 0000000..6a727d9 --- /dev/null +++ "b/reports/daily/\346\226\271\344\275\263\347\272\257/20260722-\346\226\271\344\275\263\347\272\257.md" @@ -0,0 +1,2 @@ +今日创建相关新分支; +了解并确定自己负责的功能模块 \ No newline at end of file -- Gitee From 282939cae4536dfa01152949e0ecd71c4a626869 Mon Sep 17 00:00:00 2001 From: Lo <2940191578@qq.com> Date: Thu, 23 Jul 2026 09:06:30 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=AE=9E=E7=8E=B0=E8=AF=B4=E6=98=8E=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E6=8E=A5=E5=8F=A3=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...45\345\217\243\350\256\276\350\256\241.md" | 25 ++++ ...36\347\216\260\350\257\264\346\230\216.md" | 109 +++++++++++++++++- 2 files changed, 131 insertions(+), 3 deletions(-) diff --git "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\216\245\345\217\243\350\256\276\350\256\241.md" "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\216\245\345\217\243\350\256\276\350\256\241.md" index 32caa6b..f2331d6 100644 --- "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\216\245\345\217\243\350\256\276\350\256\241.md" +++ "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/\346\216\245\345\217\243\350\256\276\350\256\241.md" @@ -8,6 +8,7 @@ | 版本 | 日期 | 修改人 | 修改说明 | |------|------|--------|----------| | v1.0 | 2026-07-22 | 全组 | 统一 API 前缀、响应格式、分页格式、状态码和 P0 接口清单 | +| v1.1 | 2026-07-23 | 黄贝然 | 补充用户退出登录接口 | ## 一、通用约定 @@ -149,6 +150,7 @@ | A28 | 后台订单 | 发货 | POST | `/api/admin/orders/{id}/ship` | 是 | 管理员 | 待建 | | A29 | 后台用户 | 后台用户列表 | GET | `/api/admin/users` | 是 | 管理员 | 待建 | | A30 | 后台用户 | 禁用/启用用户 | PATCH | `/api/admin/users/{id}/status` | 是 | 管理员 | 待建 | +| A31 | 用户 | 退出登录 | POST | `/api/auth/logout` | 是 | 会员/管理员 | 待建 | ## 三、接口详细定义 @@ -549,6 +551,29 @@ - **业务规则**:禁用用户后,登录接口必须拒绝该用户登录。 +### A31 退出登录 + +- **路径**:`POST /api/auth/logout` +- **请求头**:`Authorization: Bearer ` +- **请求参数**:无 +- **成功响应示例**: + +```json +{ + "code": 0, + "message": "ok", + "data": true, + "traceId": null +} +``` + +- **业务规则**: + - 前端退出时删除 LocalStorage 中的 `eshop_token`; + - 清空 Pinia 中保存的用户信息; + - 跳转到登录页面; + - 后续请求不再携带 Token。 + - 如果后续增加服务端 Token 黑名单,需要同时使当前 Token 失效。 + ## 四、联调检查清单 | 检查项 | 要求 | diff --git "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\347\224\250\346\210\267\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\347\224\250\346\210\267\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" index 8bf4a43..e0e8a14 100644 --- "a/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\347\224\250\346\210\267\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" +++ "b/docs/06-\346\250\241\345\235\227\345\256\236\347\216\260\350\257\264\346\230\216/\347\224\250\346\210\267\346\250\241\345\235\227-\345\256\236\347\216\260\350\257\264\346\230\216.md" @@ -1,8 +1,8 @@ # 用户模块-实现说明 -> 模块负责人:待填写 -> 编写日期:待填写 -> 对应分支:`feature/user` +> 模块负责人:黄贝然 +> 编写日期:2026/07/23 +> 对应分支:`feature/user-module` ## 1. 这个模块用了什么技术栈? @@ -24,3 +24,106 @@ - 方案 A:Spring Boot + Vue,也适合电商项目,但本组后端统一采用 C#。 - 方案 B:ASP.NET Core MVC 单体页面,开发简单但不符合前后端分离要求。 - 当前方案与其他方案的对比:当前方案更符合课程验收中的前后端分离和 HTTP API 要求。 + +## 4. 模块功能清单 + +- 用户注册:校验用户名、密码和手机号,用户名不能重复。 +- 用户登录:校验账号和密码,登录成功后返回登录 Token 和用户角色。 +- 用户退出:清理前端保存的 Token 和用户信息,返回登录页面。 +- 个人信息:查询和修改当前登录用户的可编辑信息。 +- 收货地址:新增、查询、修改和删除当前用户的收货地址。 +- 默认地址:设置当前用户的默认收货地址,并保证同一用户最多只有一个默认地址。 +- 后台用户管理:管理员分页查询用户,并启用或禁用用户。 + +## 5. 涉及的数据表 + +### 5.1 用户表 `user_account` + +保存用户账号、密码哈希、手机号、角色、启用状态以及创建和更新时间。 + +- `username` 使用唯一索引,防止重复注册。 +- `password_hash` 只保存不可逆哈希值,不保存明文密码。 +- `role` 使用 `1` 表示会员,`9` 表示管理员。 +- `status` 使用 `1` 表示启用,`0` 表示禁用。 + +### 5.2 收货地址表 `address` + +保存用户的收货人、联系电话、省市区、详细地址和默认地址状态。 + +- `user_id` 关联 `user_account.id`。 +- `idx_address_user_id(user_id)` 用于查询用户地址列表。 +- `is_default` 使用 `1` 表示默认地址,`0` 表示普通地址。 + +详细字段以 [数据库设计.md](../02-设计文档/数据库设计.md) 和 [schema.sql](../../backend/sql/schema.sql) 为准。 + +## 6. 涉及的接口 + +### 6.1 用户端接口 + +- `POST /api/auth/register`:用户注册 +- `POST /api/auth/login`:用户登录 +- `POST /api/auth/logout`:用户退出登录 +- `GET /api/users/me`:查询当前用户信息 +- `PUT /api/users/me`:修改当前用户信息 +- `GET /api/addresses`:查询地址列表 +- `POST /api/addresses`:新增地址 +- `PUT /api/addresses/{id}`:修改地址 +- `DELETE /api/addresses/{id}`:删除地址 +- `POST /api/addresses/{id}/default`:设置默认地址 + +### 6.2 后台接口 + +- `GET /api/admin/users`:分页查询用户 +- `PATCH /api/admin/users/{id}/status`:启用或禁用用户 + +所有接口的路径、请求参数和统一响应格式以 [接口设计.md](../02-设计文档/接口设计.md) 为准。 + +## 7. 涉及的前端页面 + +- 登录页:`frontend/src/views/user/LoginView.vue` +- 注册页:计划放在 `frontend/src/views/user/` 目录。 +- 个人信息页:计划放在 `frontend/src/views/user/` 目录。 +- 收货地址页:计划放在 `frontend/src/views/user/` 目录。 +- 后台用户管理页:计划放在 `frontend/src/views/admin/` 目录。 +- 登录状态:由 `frontend/src/stores/auth.ts` 中的 Pinia Store 管理。 + +## 8. 核心业务流程 + +### 8.1 注册和登录流程 + +1. 前端提交注册或登录表单。 +2. 后端校验必填字段、格式和业务规则。 +3. 注册时使用 BCrypt 等安全哈希算法保存密码。 +4. 登录时校验输入密码与数据库哈希是否匹配。 +5. 登录接口检查用户 `status`,禁用用户直接拒绝登录。 +6. 登录成功后签发 JWT,前端保存 Token 并在后续请求中携带。 +7. 退出登录时,前端删除 Token 和用户信息。 + +### 8.2 默认地址流程 + +1. 用户提交设置默认地址请求。 +2. 后端校验地址属于当前登录用户。 +3. 在同一个事务中取消该用户原有默认地址。 +4. 将当前地址设置为默认地址。 +5. 返回更新后的地址信息。 + +## 9. 关键业务规则和安全要求 + +- 用户只能查询、修改和删除属于自己的地址,不能通过修改地址 ID 操作其他用户数据。 +- 后端必须再次校验前端参数,不能只依赖前端表单校验。 +- 密码不能明文存储、记录到日志或返回给前端。 +- 受保护接口必须校验 JWT Token。 +- 后台用户接口必须校验管理员角色。 +- 禁用用户后,登录接口必须拒绝其登录。 +- 错误响应不能暴露 SQL、连接字符串、服务器路径或堆栈信息。 + +## 10. 当前实现状态与后续计划 + +- 用户表和地址表设计:已完成。 +- 用户实体和地址实体:已创建。 +- 注册、登录接口:已有基础 Controller 骨架,真实校验、密码哈希、数据库保存和 JWT 签发待实现。 +- 登录页面:已有基础页面,待接入真实登录接口。 +- 注册、个人信息、地址和后台用户管理页面:待实现。 +- 退出登录接口:接口约定待补充,前端 Token 清理逻辑已有基础实现。 +- 用户模块测试:待补充重复注册、密码错误、禁用用户和默认地址等用例。 + -- Gitee From eeed2938934c29c145adcdcf3b7917379282099a Mon Sep 17 00:00:00 2001 From: FXR-c <427808324@qq.com> Date: Thu, 23 Jul 2026 17:19:38 +0800 Subject: [PATCH 15/15] =?UTF-8?q?=E6=8F=90=E4=BA=A47=E6=9C=8823=E6=97=A5?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...23-\346\226\271\344\275\263\347\272\257.md" | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 "reports/daily/\346\226\271\344\275\263\347\272\257/20260723-\346\226\271\344\275\263\347\272\257.md" diff --git "a/reports/daily/\346\226\271\344\275\263\347\272\257/20260723-\346\226\271\344\275\263\347\272\257.md" "b/reports/daily/\346\226\271\344\275\263\347\272\257/20260723-\346\226\271\344\275\263\347\272\257.md" new file mode 100644 index 0000000..b27bcbe --- /dev/null +++ "b/reports/daily/\346\226\271\344\275\263\347\272\257/20260723-\346\226\271\344\275\263\347\272\257.md" @@ -0,0 +1,18 @@ +**梳理分类表和商品表字段** + - 分类表(category):id、name、sort_order、created_at、updated_at + - 商品表(product):id、category_id、name、description、price、stock、image_url、status、created_at、updated_at + +**确认商品状态、库存、价格字段** + - 商品状态(status):0=下架,1=上架 + - 库存(stock):INT类型,默认0 + - 价格(price):DECIMAL(10,2),精确到分 + +**更新商品模块实现说明文档** + - 填写了技术栈、功能清单、涉及数据表和接口 + +**整理商品接口清单** + - 分类列表接口(GET /api/categories) + - 商品列表接口(GET /api/products) + - 商品详情接口(GET /api/products/{id}) + - 后台商品管理接口(CRUD) + - 商品上下架接口 -- Gitee