Files
yidaima/RuoYi-Vue/docs/superpowers/plans/2026-06-18-qing-business-action-runtime.md

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.