9.0 KiB
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:
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:
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:
assertTrue(content.contains("tableBusinessActions"));
assertTrue(content.contains("executeBusinessAction"));
- Step 4: Run tests and verify RED
Run:
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:
@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:
@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:
int ${action.code}(Map<String, Object> params);
- Step 4: Render transactional service implementations
Generate one method per action:
@Transactional(rollbackFor = Exception.class)
public int ${action.code}(Map<String, Object> params)
Validate requestFields, execute ruleChecks, and execute effects. Reuse deterministic helpers for:
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:
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:
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:
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:
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:
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:
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
mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest,GenProjectServiceImplTest test
- Step 2: Run the broader generator suite
mvn -pl ruoyi-generator test
- Step 3: Rebuild EasyCode
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.