# 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 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 ${action.code}(@RequestBody(required = false) Map params, HttpServletRequest request) { Map actionParams = params == null ? new LinkedHashMap() : new LinkedHashMap(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 params); ``` - [ ] **Step 4: Render transactional service implementations** Generate one method per action: ```java @Transactional(rollbackFor = Exception.class) public int ${action.code}(Map 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.