277 lines
9.0 KiB
Markdown
277 lines
9.0 KiB
Markdown
# Qing Business Action Runtime 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:** Generate runnable Qing backend endpoints and transactional DSL execution for portal business-action buttons.
|
|
|
|
**Architecture:** Keep actions table-owned, render their absolute blueprint paths in the owning controller, and execute validated rules/effects in the owning MyBatis-Plus service. Resolve the authenticated portal user in the controller and collect unresolved request fields in the Vue template.
|
|
|
|
**Tech Stack:** Java 8, Spring Boot 2.5, MyBatis-Plus, JdbcTemplate, Velocity, Vue 2, Element UI, JUnit 4.
|
|
|
|
---
|
|
|
|
### Task 1: Add Failing Qing Template Tests
|
|
|
|
**Files:**
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java`
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java`
|
|
|
|
- [ ] **Step 1: Add a rendered backend-action test**
|
|
|
|
Create a project with a `borrow_book` action owned by `demo_product`, render the Qing controller, service interface, and service implementation, and assert:
|
|
|
|
```java
|
|
assertTrue(controller.contains("@PostMapping(\"/library/borrow/borrow\")"));
|
|
assertTrue(controller.contains("params.put(\"__currentUserId\", currentUser.getUserId())"));
|
|
assertTrue(service.contains("int borrow_book(Map<String, Object> params)"));
|
|
assertTrue(implementation.contains("@Transactional(rollbackFor = Exception.class)"));
|
|
assertTrue(implementation.contains("resolveBusinessValue"));
|
|
assertTrue(implementation.contains("\"${current_user.id}\""));
|
|
```
|
|
|
|
- [ ] **Step 2: Add a rendered frontend parameter-collection test**
|
|
|
|
Assert the rendered Vue page contains:
|
|
|
|
```java
|
|
assertTrue(page.contains("collectBusinessActionPayload"));
|
|
assertTrue(page.contains("this.$prompt"));
|
|
assertTrue(page.contains("missingFields"));
|
|
assertTrue(page.contains("action.requestFields"));
|
|
```
|
|
|
|
- [ ] **Step 3: Add stale-template fallback tests**
|
|
|
|
Provide template ID `9201` records whose controller/service/serviceImpl contents do not include business-action markers. Assert preview generation replaces them with bundled templates containing:
|
|
|
|
```java
|
|
assertTrue(content.contains("tableBusinessActions"));
|
|
assertTrue(content.contains("executeBusinessAction"));
|
|
```
|
|
|
|
- [ ] **Step 4: Run tests and verify RED**
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest,GenProjectServiceImplTest test
|
|
```
|
|
|
|
Expected: failures because Qing backend action rendering, frontend field collection, and service fallbacks do not exist.
|
|
|
|
### Task 2: Generate Backend Business Actions
|
|
|
|
**Files:**
|
|
- Modify: `ruoyi-generator/src/main/resources/qing/controller.java.vm`
|
|
- Modify: `ruoyi-generator/src/main/resources/qing/service.java.vm`
|
|
- Modify: `ruoyi-generator/src/main/resources/qing/serviceImpl.java.vm`
|
|
|
|
- [ ] **Step 1: Give CRUD methods complete route mappings**
|
|
|
|
Remove the controller-level `@RequestMapping`. Prefix CRUD mappings directly:
|
|
|
|
```java
|
|
@PostMapping("/${moduleName}/${businessName}")
|
|
@GetMapping("/${moduleName}/${businessName}/page")
|
|
@GetMapping("/${moduleName}/${businessName}/{${pkColumn.javaField}}")
|
|
```
|
|
|
|
This leaves business actions free to use their absolute blueprint paths.
|
|
|
|
- [ ] **Step 2: Render controller action endpoints**
|
|
|
|
For each `$tableBusinessActions` item, generate the matching Spring mapping. For POST:
|
|
|
|
```java
|
|
@PostMapping("${action.path}")
|
|
public Result<Integer> ${action.code}(@RequestBody(required = false) Map<String, Object> params,
|
|
HttpServletRequest request)
|
|
{
|
|
Map<String, Object> actionParams = params == null
|
|
? new LinkedHashMap<String, Object>() : new LinkedHashMap<String, Object>(params);
|
|
PortalUser currentUser = tokenStore.getPortalUser(resolveToken(request));
|
|
if (currentUser == null) {
|
|
return Result.error("请先登录");
|
|
}
|
|
actionParams.put("__currentUserId", currentUser.getUserId());
|
|
try {
|
|
return Result.success(${className}Service.${action.code}(actionParams));
|
|
} catch (RuntimeException e) {
|
|
return Result.error(e.getMessage());
|
|
}
|
|
}
|
|
```
|
|
|
|
Render equivalent GET, PUT, and DELETE mappings.
|
|
|
|
- [ ] **Step 3: Render service interface methods**
|
|
|
|
For each table action:
|
|
|
|
```java
|
|
int ${action.code}(Map<String, Object> params);
|
|
```
|
|
|
|
- [ ] **Step 4: Render transactional service implementations**
|
|
|
|
Generate one method per action:
|
|
|
|
```java
|
|
@Transactional(rollbackFor = Exception.class)
|
|
public int ${action.code}(Map<String, Object> params)
|
|
```
|
|
|
|
Validate `requestFields`, execute `ruleChecks`, and execute `effects`. Reuse deterministic helpers for:
|
|
|
|
```text
|
|
EXISTS, NOT_EXISTS, FIELD_EQUALS, FIELD_NOT_EQUALS, FIELD_IN, NUMBER_GTE
|
|
INSERT_ROW, UPDATE_FIELD, UPDATE_FIELDS, SET_STATUS, INCREASE_NUMBER, DECREASE_NUMBER
|
|
```
|
|
|
|
For effects needing conditions, fall back to `action.requestFields` when `conditionFields` is empty.
|
|
|
|
- [ ] **Step 5: Resolve safe placeholders**
|
|
|
|
Implement:
|
|
|
|
```java
|
|
if ("${now}".equals(valueExpression)) return new Date();
|
|
if ("${current_user.id}".equals(valueExpression)) return params.get("__currentUserId");
|
|
if (valueExpression.startsWith("${param.") && valueExpression.endsWith("}")) {
|
|
String field = valueExpression.substring(8, valueExpression.length() - 1);
|
|
requireBusinessParam(params, field);
|
|
return params.get(field);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Run targeted tests**
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest test
|
|
```
|
|
|
|
Expected: backend rendering assertions pass.
|
|
|
|
### Task 3: Collect Missing Frontend Parameters
|
|
|
|
**Files:**
|
|
- Modify: `ruoyi-generator/src/main/resources/qing/index.vue.vm`
|
|
|
|
- [ ] **Step 1: Keep action metadata**
|
|
|
|
Continue serializing `requestFields` in `businessActionMap`.
|
|
|
|
- [ ] **Step 2: Collect unresolved fields before request**
|
|
|
|
Replace synchronous payload construction with:
|
|
|
|
```javascript
|
|
collectBusinessActionPayload(action, row) {
|
|
const payload = this.buildBusinessActionPayload(action, row)
|
|
const missingFields = (action.requestFields || []).filter(field => payload[field] === undefined)
|
|
return missingFields.reduce((promise, field) => {
|
|
return promise.then(() => this.$prompt(
|
|
"请输入" + this.formatBusinessFieldLabel(field),
|
|
action.name || "业务操作",
|
|
{ inputPlaceholder: this.businessFieldPlaceholder(field), confirmButtonText: "确定", cancelButtonText: "取消" }
|
|
).then(({ value }) => {
|
|
payload[field] = value
|
|
}))
|
|
}, Promise.resolve()).then(() => payload)
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Execute only after collection**
|
|
|
|
Call `collectBusinessActionPayload(action, row).then(payload => ...)`; cancellation sends no request.
|
|
|
|
- [ ] **Step 4: Surface request failures**
|
|
|
|
Add a request catch that displays the backend response message or “操作失败”.
|
|
|
|
- [ ] **Step 5: Run targeted tests**
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest test
|
|
```
|
|
|
|
Expected: frontend parameter-collection assertions pass.
|
|
|
|
### Task 4: Make Existing Installed Templates Upgrade Safely
|
|
|
|
**Files:**
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java`
|
|
- Modify: `sql/qing_templates.sql`
|
|
- Modify: `sql/db.sql`
|
|
|
|
- [ ] **Step 1: Add bundled service constants**
|
|
|
|
Add constants for `service.java.vm`, `serviceImpl.java.vm`, and their Qing classpath resources.
|
|
|
|
- [ ] **Step 2: Add stale-template detection**
|
|
|
|
For template ID `9201`, replace installed service templates when they lack markers:
|
|
|
|
```java
|
|
service.java.vm -> "$tableBusinessActions"
|
|
serviceImpl.java.vm -> "resolveBusinessValue" and "@Transactional"
|
|
controller.java.vm -> "executeBusinessAction"
|
|
```
|
|
|
|
- [ ] **Step 3: Synchronize SQL seeds**
|
|
|
|
Replace the backend controller/service/serviceImpl and frontend-index template records in `sql/qing_templates.sql`, then copy the same records into the runnable-template section of `sql/db.sql`.
|
|
|
|
- [ ] **Step 4: Run fallback and SQL consistency tests**
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
mvn -pl ruoyi-generator -Dtest=GenProjectServiceImplTest,QingTemplateSupportTest test
|
|
```
|
|
|
|
Expected: all targeted tests pass.
|
|
|
|
### Task 5: Generate and Verify the Running Project
|
|
|
|
**Files:**
|
|
- Generated preview workspace only; no source file is manually edited.
|
|
|
|
- [ ] **Step 1: Run generator tests**
|
|
|
|
```powershell
|
|
mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest,GenProjectServiceImplTest test
|
|
```
|
|
|
|
- [ ] **Step 2: Run the broader generator suite**
|
|
|
|
```powershell
|
|
mvn -pl ruoyi-generator test
|
|
```
|
|
|
|
- [ ] **Step 3: Rebuild EasyCode**
|
|
|
|
```powershell
|
|
npm run build
|
|
```
|
|
|
|
Run from `easycode-web`.
|
|
|
|
- [ ] **Step 4: Restart project 105 run preview**
|
|
|
|
Use the existing `/front/project/105/run-preview` API, wait until the new backend/frontend URLs are ready, and open `/books/2`.
|
|
|
|
- [ ] **Step 5: Verify borrow and reserve**
|
|
|
|
Log in as the seeded portal user, click each action, enter the prompted date, and confirm:
|
|
|
|
- neither request returns 404;
|
|
- successful actions create the corresponding record;
|
|
- the book availability count changes only for borrowing;
|
|
- the UI reports backend validation messages when an action is invalid.
|
|
|