You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/docs/superpowers/plans/2026-08-07-product-developm...

264 lines
16 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 产品开发关键因素第 1 步 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为学生端“产品开发关键因素”第 1 步提供原型对应的三维对比表、服务端非空校验,以及仅保存并进入第 2 步的流程。
**Architecture:** 前端把第 1 步维度收敛为固定常量,以 `step1Answer` 持久化三行成功/失败分析;点击推进时先调用专用校验接口,再使用已有学生答案保存接口保存 `SAVE` 草稿和第 2 步进度。后端新增轻量 DTO、校验服务和控制器路由复用已有 JWT 学生身份检查与 `StudentTrainingAnswer` 持久化机制,不新建表。
**Tech Stack:** Vue 3 Composition API、Element Plus、Vite、Spring Boot、JUnit 5、Mockito、Maven。
## Global Constraints
- 只修改学生端 `product-development-factors` 的第 1 步和其服务端校验;不修改教师端、管理员端、数据库结构、材料下载或第 2 步的业务内容。
- 页面仅保留原型的四列表头与三个固定维度,不显示成功/失败产品名称输入项。
- 六个分析输入去除首尾空白后必须非空,服务端必须拒绝缺失、重复、额外或伪造维度。
- 第 1 步推进只能使用 `saveAction: "SAVE"`,不得触发 `SUBMIT` 或成绩结算。
- 使用已有隔离 worktree仅提交本计划列出的文件避免纳入主工作区已有的无关未跟踪文件。
---
## 文件结构
### 前端仓库 `E:/workspace/dianshang/e-commerce-internet`
- 修改:`src/views/foundation/product-development-factors.vue` — 固定第 1 步表格、恢复/保存进度和“保存并进入第 2 步”交互。
- 修改:`src/api/studentTrainingAnswer.js` — 增加 Demo 与真实环境共用的第 1 步校验 API 封装。
- 新建:`tests/product-development-factors-step-one.static.test.cjs` — 对页面结构、保存行为与 API 路由做静态契约测试。
### 后端仓库 `E:/workspace/dianshang/link_commerce`
- 新建:`src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepOneValidationRequest.java` — 接收固定维度及两列分析文本。
- 新建:`src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepOneValidationResult.java` — 返回 `valid``message`
- 新建:`src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentFactorsStepOneService.java` — 声明校验入口。
- 新建:`src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepOneServiceImpl.java` — 学生身份、三维度和六项非空校验。
- 修改:`src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java` — 注入服务并暴露校验路由。
- 新建:`src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepOneServiceImplTest.java` — 服务层行为测试。
- 修改:`src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java` — 控制器传递认证学生和返回校验结果的测试。
## Task 1: 后端第 1 步校验契约
**Files:**
- Create: `link_commerce/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepOneValidationRequest.java`
- Create: `link_commerce/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepOneValidationResult.java`
- Create: `link_commerce/src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentFactorsStepOneService.java`
- Create: `link_commerce/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepOneServiceImpl.java`
- Create: `link_commerce/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepOneServiceImplTest.java`
**Interfaces:**
- Consumes: `JwtUser`、`ServiceException`、`StringUtils`、`HttpStatus`,以及 `NewProductSurveyStepThreeServiceImpl` 的角色校验风格。
- Produces: `ProductDevelopmentFactorsStepOneService#validate(ProductDevelopmentFactorsStepOneValidationRequest request, JwtUser user)`,供控制器调用;成功时返回 `{ valid: true, message: "校验通过" }`
- [ ] **Step 1: 写服务层失败测试**
`ProductDevelopmentFactorsStepOneServiceImplTest` 建立三个测试:完整的三个固定维度均有成功/失败文本时返回有效;任一分析为空、维度重复或维度不在白名单时抛出 `ServiceException`;角色不是 `4` 时抛出 `ServiceException`
```java
ProductDevelopmentFactorsStepOneValidationResult result = service.validate(validRequest(), student());
assertTrue(result.isValid());
assertThrows(ServiceException.class, () -> service.validate(requestWithBlankFailure(), student()));
assertThrows(ServiceException.class, () -> service.validate(validRequest(), teacher()));
```
- [ ] **Step 2: 运行测试确认失败**
Run: `mvn -B '-Dtest=ProductDevelopmentFactorsStepOneServiceImplTest' test`
Expected: FAIL因为校验 DTO、服务接口及实现尚不存在。
- [ ] **Step 3: 添加请求、结果和服务实现**
请求 DTO 定义 `List<Row> factors``Row` 定义 `String dimension`、`String successAnalysis`、`String failedAnalysis` 及标准 getter/setter。实现类固定使用以下维度集合
```java
private static final Set<String> DIMENSIONS = new LinkedHashSet<>(Arrays.asList(
"用户需求真实性", "产品体验闭环", "生态与兼容"));
```
`validate` 必须先验证登录用户存在且 `roleId == 4`,再验证 `factors` 数量恰为 3、每个维度属于 `DIMENSIONS` 且仅出现一次、`successAnalysis` 和 `failedAnalysis` 都满足 `StringUtils.isNotBlank`,最后验证提交维度集合与 `DIMENSIONS` 完全一致。所有请求内容问题用 `new ServiceException(HttpStatus.BAD_REQUEST, message)` 拒绝;身份问题分别用 `UNAUTHORIZED``FORBIDDEN`
- [ ] **Step 4: 运行服务测试确认通过**
Run: `mvn -B '-Dtest=ProductDevelopmentFactorsStepOneServiceImplTest' test`
Expected: PASS三个测试全部通过。
- [ ] **Step 5: 提交后端服务层变更**
```powershell
git add src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepOneValidationRequest.java src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepOneValidationResult.java src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentFactorsStepOneService.java src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepOneServiceImpl.java src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepOneServiceImplTest.java
git commit -m "feat: validate product factors step one"
```
## Task 2: 暴露后端校验路由
**Files:**
- Modify: `link_commerce/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java`
- Modify: `link_commerce/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java`
**Interfaces:**
- Consumes: Task 1 的 `ProductDevelopmentFactorsStepOneService#validate(request, user)`
- Produces: `POST /api/student-training-answers/product-development-factors/step-1/validate`,请求体为 `{ "factors": [{ "dimension": "...", "successAnalysis": "...", "failedAnalysis": "..." }] }`
- [ ] **Step 1: 写控制器失败测试**
`StudentTrainingAnswerControllerTest` 添加一个测试mock `ProductDevelopmentFactorsStepOneService`,通过 `ReflectionTestUtils` 注入控制器,调用 `validateProductDevelopmentFactorsStepOne`,断言状态为 `HttpStatus.OK`、返回体是 mock 的结果,并验证 `service.validate(any(ProductDevelopmentFactorsStepOneValidationRequest.class), any(JwtUser.class))` 被调用。
```java
when(service.validate(any(ProductDevelopmentFactorsStepOneValidationRequest.class), any(JwtUser.class))).thenReturn(validation);
ResultEntity<ProductDevelopmentFactorsStepOneValidationResult> result = controller
.validateProductDevelopmentFactorsStepOne(answer, authenticatedStudentRequest());
assertEquals(HttpStatus.OK, result.getStatusCode());
verify(service).validate(any(ProductDevelopmentFactorsStepOneValidationRequest.class), any(JwtUser.class));
```
- [ ] **Step 2: 运行控制器测试确认失败**
Run: `mvn -B '-Dtest=StudentTrainingAnswerControllerTest' test`
Expected: FAIL因为控制器尚未注入服务或不存在该方法。
- [ ] **Step 3: 添加控制器依赖与路由**
在控制器添加 DTO 和服务 import、`@Autowired ProductDevelopmentFactorsStepOneService productDevelopmentFactorsStepOneService`,并添加以下方法。方法沿用已有四个新产品调查校验接口的 `try/catch ServiceException` 响应方式。
```java
@PostMapping("/product-development-factors/step-1/validate")
@ApiOperation("产品开发关键因素第一步校验")
public ResultEntity<ProductDevelopmentFactorsStepOneValidationResult> validateProductDevelopmentFactorsStepOne(
@RequestBody ProductDevelopmentFactorsStepOneValidationRequest answer, HttpServletRequest request) {
try {
JwtUser user = TokenProvider.getJWTUser(request);
return new ResultEntity<>(HttpStatus.OK, "校验通过", productDevelopmentFactorsStepOneService.validate(answer, user));
} catch (ServiceException e) {
return new ResultEntity<>(e.getCode(), e.getMessage());
}
}
```
- [ ] **Step 4: 运行控制器和服务测试确认通过**
Run: `mvn -B '-Dtest=ProductDevelopmentFactorsStepOneServiceImplTest,StudentTrainingAnswerControllerTest' test`
Expected: PASS新增路由通过认证用户调用服务已有控制器测试保持通过。
- [ ] **Step 5: 提交后端控制器变更**
```powershell
git add src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java
git commit -m "feat: expose product factors step one validation"
```
## Task 3: 前端 API 与页面第 1 步交互
**Files:**
- Modify: `e-commerce-internet/src/api/studentTrainingAnswer.js`
- Modify: `e-commerce-internet/src/views/foundation/product-development-factors.vue`
- Create: `e-commerce-internet/tests/product-development-factors-step-one.static.test.cjs`
**Interfaces:**
- Consumes: Task 2 的 `POST /api/student-training-answers/product-development-factors/step-1/validate`,以及现有 `saveStudentTrainingAnswer(taskKey, data)`
- Produces: `checkProductDevelopmentFactorsStepOne(payload)`;页面第 1 步调用其校验并保存 `{ step1Answer, currentStep: 2, submitted: false, saveAction: "SAVE" }` 后切到索引 `1`
- [ ] **Step 1: 写前端静态契约失败测试**
新建 `tests/product-development-factors-step-one.static.test.cjs`,读取 API 和 Vue 源码,断言固定三维度常量与三条中文评估项存在,旧的 `successProduct`/`failedProduct` 输入绑定不存在,存在 `checkProductDevelopmentFactorsStepOne` 和真实路由字符串,推进方法先校验后 `saveStudentTrainingAnswer`,并且保存动作字符串为 `"SAVE"` 而非 `"SUBMIT"`
```js
assert.match(view, /const STEP_ONE_FACTOR_ROWS = \[/);
assert.doesNotMatch(view, /v-model="form\.successProduct"/);
assert.match(api, /product-development-factors\/step-1\/validate/);
assert.match(view, /await checkProductDevelopmentFactorsStepOne\(buildStepOneValidationPayload\(\)\)/);
```
- [ ] **Step 2: 运行静态测试确认失败**
Run: `node tests/product-development-factors-step-one.static.test.cjs`
Expected: FAIL因为页面尚未声明固定三行常量或调用新校验 API。
- [ ] **Step 3: 实现 API 封装和 Demo 校验**
`studentTrainingAnswer.js` 增加 `checkProductDevelopmentFactorsStepOne(payload)`。Demo 模式以相同固定维度、恰好三行和每行两列非空规则校验,失败时 `Promise.reject(new Error(message))`;真实模式发起:
```js
return request({
url: "/api/student-training-answers/product-development-factors/step-1/validate",
method: "post",
data: payload,
headers: { repeatSubmit: false },
});
```
- [ ] **Step 4: 重构第 1 步页面**
`product-development-factors.vue``STEP_ONE_FACTOR_ROWS` 取代八行工厂,且只包含原型指定的三行和评估项。移除 `form`、`.product-entry`、`.product-field`、`.input-single` 及模板中的产品名称输入区域;`buildOutcome()` 仅保存 `title` 与三行 `factors`
将表格和第 1 步操作包在 `v-if="currentStep === 0"` 中,非第 1 步渲染不复用第 1 步表格的轻量占位块。新增 `buildStepOneValidationPayload()`,其返回值仅含 `{ factors: factorRows.value.map(({ dimension, successAnalysis, failedAnalysis }) => ({ dimension, successAnalysis, failedAnalysis })) }`。新增 `saveAndGoToStepTwo()`:先等待校验、再调用 `saveStudentTrainingAnswer(TASK_KEY, buildAnswerPayload("SAVE", 2))`、清理本地草稿;当 `trainingSteps.value.length >= 2` 时设置 `currentStep.value = 1`,否则提示任务未配置下一步。替换原“提交任务”按钮为“保存并进入第 2 步”,不再保留第 1 步的 `SUBMIT` 路径。
`loadSavedAnswer()` 在恢复 `step1Answer` 后,将服务端 `answer.currentStep` 的 1 基步骤值转换成合法 0 基索引;历史 JSON 的产品名称字段不读取。`resetTraining()` 重建三行数据、保存 `RESET`,并复位 `currentStep.value = 0`
- [ ] **Step 5: 运行前端静态测试确认通过**
Run: `node tests/product-development-factors-step-one.static.test.cjs`
Expected: PASS页面和 API 合同全部满足。
- [ ] **Step 6: 构建前端**
Run: `npm run build`
Expected: PASSVite 生产构建完成且没有 Vue 编译错误。
- [ ] **Step 7: 提交前端实现**
```powershell
git add src/api/studentTrainingAnswer.js src/views/foundation/product-development-factors.vue tests/product-development-factors-step-one.static.test.cjs
git commit -m "feat: add product factors step one analysis"
```
## Task 4: 跨端回归验证与交付准备
**Files:**
- Modify: 无;只验证 Task 13 已提交的文件。
**Interfaces:**
- Consumes: 新增前端校验封装、后端校验路由和已有答案保存接口。
- Produces: 可以交付的前后端分支,且不启动本地后端进程。
- [ ] **Step 1: 运行后端完整回归**
Run: `mvn -B test`
Expected: PASS所有 Maven 测试通过;既有系统依赖警告不视为测试失败。
- [ ] **Step 2: 运行前端相关及完整构建验证**
Run: `node tests/product-development-factors-step-one.static.test.cjs; npm run build`
Expected: PASS静态契约与生产构建均完成。
- [ ] **Step 3: 检查提交范围**
Run: `git status --short`(分别在两个 worktree 执行)
Expected: 无本功能未提交改动;不暂存或删除主工作区既有未跟踪文件。
- [ ] **Step 4: 记录验证结果并等待集成指令**
报告前后端提交号、测试结果与用户可见的“保存并进入第 2 步”行为。除非用户明确要求,不合并、不推送、不发布,也不启动本地后端。
## 自检结果
- 规格覆盖Task 12 实现身份与六项非空的服务端校验Task 3 实现原型三行页面、Demo 校验、持久化和仅保存推进Task 4 覆盖跨端验证与不发布约束。
- 占位扫描:计划没有未定义的后续实现项;第 1 步以外的页面仅在已有步骤导航中显示明确的待配置占位,未扩展第 2 步业务。
- 类型一致性:前端 `factors[].dimension/successAnalysis/failedAnalysis` 与后端请求 DTO 的 `Row` 字段一致;控制器路由与 API 封装字符串一致。