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: 平台管理员可以将商家或骑手标记为审核不通过并填写必填原因,后端持久化该原因并随用户信息返回。
Architecture: 沿用 InfoUser、平台用户新增/修改接口和现有 sys_user_audit 字典,不增加新接口或审核历史表。Controller 负责平台写入时状态与原因的一致性校验,并禁止 App 资料更新修改审核状态;MyBatis 在审核状态发生更新时同步写入或清空原因,平台两个审核页负责条件展示和前端必填校验。
Tech Stack: Java 21、Spring Boot、MyBatis XML、MySQL、Vue 2、Element UI、vue-i18n、JUnit 5、Mockito。
info_user.audit_reject_reason 使用 TEXT NULL,不设置应用层字符数限制。0=待审核、1=审核通过、2=审核不通过。2 时原因去除首尾空白后必须非空;状态明确更新为其他值时原因保存为 NULL。updatesql/sql.md,不得直接执行。Files:
ruoyi-admin/src/test/java/com/ruoyi/app/user/InfoUserControllerTest.javaruoyi-admin/src/main/java/com/ruoyi/app/user/InfoUserController.javaruoyi-system/src/main/java/com/ruoyi/system/domain/InfoUser.javaruoyi-system/src/main/resources/mapper/infouser/InfoUserMapper.xmlruoyi-admin/src/main/resources/i18n/messages_zh_CN.propertiesruoyi-admin/src/main/resources/i18n/messages_zh_TW.propertiesruoyi-admin/src/main/resources/i18n/messages_en_US.propertiesruoyi-admin/src/main/resources/i18n/messages_vi.propertiesInterfaces:
POST /infouser/user、PUT /infouser/user 的现有 InfoUser JSON 请求。Produces: InfoUser.getAuditRejectReason()/setAuditRejectReason(String);现有用户信息响应新增 auditRejectReason。
[ ] Step 1: 写失败测试
在 InfoUserControllerTest 增加行为测试:新增或修改为状态 2 且原因只有空白时返回错误且不调用写入 Service;状态为 2 且原因有效时修剪后保存;状态为 1 时将旧原因清空后保存;App 资料更新不能改变审核状态。
@Test
void rejectsAuditRejectionWithoutReason() {
InfoUser user = new InfoUser();
user.setAuditStatus("2");
user.setAuditRejectReason(" ");
AjaxResult result = controller.edit(user);
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
verify(infoUserService, never()).updateInfoUser(any(InfoUser.class));
}
@Test
void trimsAndSavesAuditRejectionReason() {
InfoUser user = new InfoUser();
user.setAuditStatus("2");
user.setAuditRejectReason(" 证件照片模糊 ");
when(infoUserService.updateInfoUser(user)).thenReturn(1);
controller.edit(user);
assertEquals("证件照片模糊", user.getAuditRejectReason());
verify(infoUserService).updateInfoUser(user);
}
@Test
void clearsOldReasonWhenAuditPasses() {
InfoUser user = new InfoUser();
user.setAuditStatus("1");
user.setAuditRejectReason("旧原因");
when(infoUserService.updateInfoUser(user)).thenReturn(1);
controller.edit(user);
assertEquals(null, user.getAuditRejectReason());
verify(infoUserService).updateInfoUser(user);
}
PowerShell 临时设置 JDK 21 后运行:
$env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7'
$env:Path="$env:JAVA_HOME\bin;$env:Path"
mvn -pl ruoyi-admin -am -Dtest=InfoUserControllerTest -Dsurefire.failIfNoSpecifiedTests=false test
预期:编译失败,提示 InfoUser 尚无 auditRejectReason 访问方法。
[ ] Step 3: 实现最小后端改动
InfoUser 新增 String auditRejectReason 及访问方法,并加入 toString()。
InfoUserMapper.xml 的 resultMap、insert 列和值补齐 audit_reject_reason。
update 中使用 <if test="auditStatus != null">audit_reject_reason = #{auditRejectReason},</if>,确保审核状态更新为非 2 时能写入 NULL。
InfoUserController.add/edit 在调用 Service 前共用状态校验、原因修剪和旧原因清空。
/infouser/user/setuser 不再复制客户端的 auditStatus。
InfoUser.updateUserInfo 使用 @TableField(exist = false),只作为 /setuser 的请求标记,不写入数据库。
/setuser 保存资料成功后,当 updateUserInfo=true 时通过单条条件更新把当前状态 2 改为 0,同时清空 audit_reject_reason;其他状态或标记值不触发审核状态更新。
四个后端资源文件新增 no.user.audit.reject.reason.required。
[ ] Step 4: 运行定向测试确认 GREEN
运行 Step 2 命令,预期 InfoUserControllerTest 全部通过。
Files:
E:/QtwCode/foodie/foodie-admin-vue/src/views/infouser/user/sjuser.vueE:/QtwCode/foodie/foodie-admin-vue/src/views/infouser/user/peisus.vueE:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.zh_CN.jsE:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.zh_TW.jsE:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.en_US.jsE:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.vi.jsInterfaces:
sys_user_audit 字典新增的值 2;用户详情中的 auditRejectReason。Produces: PUT /infouser/user 请求中的 auditStatus 与 auditRejectReason。
[ ] Step 1: 添加审核不通过展示和输入控件
两个列表的审核状态列增加值 2 的 danger 标签。
两个审核表单在 form.auditStatus === '2' 时显示 type="textarea" 的原因输入框。
reset() 增加 auditRejectReason: null。
[ ] Step 2: 添加条件必填与提交归一化
两页的 rules.auditRejectReason 在状态为 2 时拒绝空值或纯空白。
submitForm() 验证通过后:状态为 2 时修剪原因,否则赋值 null,再调用现有 updateUser。
[ ] Step 3: 补齐四语言文案
route 增加“审核不通过”。
infoussanhuser 与 infouspeisus 分别增加“审核不通过原因”“请输入审核不通过原因”“审核不通过原因不能为空”。
[ ] Step 4: 构建平台前端
npm run build:prod
工作目录:E:\QtwCode\foodie\foodie-admin-vue。预期构建成功;不得改动原有脏文件 src/views/chanting/store/shangc.vue。
Files:
updatesql/sql.mdspecs/021-user-audit-rejection/tasks.mdInterfaces:
Produces: 开发者可手动执行的数据库迁移 SQL 与可审计任务状态。
[ ] Step 1: 追加迁移 SQL
ALTER TABLE info_user
ADD COLUMN audit_reject_reason TEXT NULL COMMENT '用户审核不通过原因' AFTER audit_status;
INSERT INTO sys_dict_data
(dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '审核不通过', '2', 'sys_user_audit', '', 'danger', 'N', '0', 'admin', NOW(), '用户审核不通过'
WHERE NOT EXISTS (
SELECT 1 FROM sys_dict_data WHERE dict_type = 'sys_user_audit' AND dict_value = '2'
);
[ ] Step 2: 完成任务清单并执行后端验证
运行 Task 1 定向测试,再运行:
mvn -pl ruoyi-admin -am -DskipTests package
预期后端模块构建成功。
[ ] Step 3: 最终差异检查
后端仓库只包含本需求规格、测试、Java、Mapper、i18n 和 SQL 变更,以及用户原有的 .claude/homunculus/observations.jsonl 脏文件。
平台仓库只新增本需求对两个审核页和四语言文件的修改,保留且不混入原有 shangc.vue 改动。
git diff --check 无空白错误,前端文件仍为 CRLF。