package com.ruoyi.task.config; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.task.api.TaskApi; import com.ruoyi.task.api.TaskFacade; /** * 任务模块开关自检 —— 纯 {@code main},不依赖 JUnit / Spring / 数据库。 * *

为什么不用 JUnit

* 与满意度模块的 {@code SatisfactionSwitchCheck} 同款:本仓离线仓库缺 * {@code surefire-junit3:2.12.4},走 * {@code mvn -o -q -pl ruoyi-task -am -DskipTests test-compile} + {@code java -cp} 这条路。 * *

断言覆盖

*
    *
  1. sys_config 优先于 yml;
  2. *
  3. yml 兜底;
  4. *
  5. 参数行缺失 → 回落 yml;
  6. *
  7. 非法值 → 按"不是 false 即启用";
  8. *
  9. fail-open:读不到配置服务时按启用,且不抛异常;
  10. *
  11. 10 秒缓存:翻转值不会立即生效,{@code invalidateCache()} 后立即生效;
  12. *
  13. 拦截器:开启放行、关闭抛 {@link ServiceException} 且 message 含 * {@code task.enabled=false} 与 {@code 接口不可用}(并验证 request 为 null 不炸);
  14. *
  15. 守卫路径恰好 2 条,且是任务模块自己的两个前缀;
  16. *
  17. {@link TaskFacade} 是 {@link TaskApi} 且随开关变化。
  18. *
*/ public final class TaskSwitchCheck { private static int failed = 0; private static int passed = 0; private static void check(String key, Object expected, Object actual) { boolean ok = expected == null ? actual == null : expected.equals(actual); if (ok) { passed++; System.out.println(" [OK] " + key + " = " + actual); } else { failed++; System.out.println(" [FAIL] " + key + " 期望=" + expected + " 实际=" + actual); } } private static TaskProperties props(boolean enabled) { TaskProperties p = new TaskProperties(); p.setEnabled(enabled); return p; } public static void main(String[] args) { System.out.println("========== 任务模块开关自检 =========="); System.out.println("-- 1) sys_config 优先于 yml --"); check("sys_config=true, yml=false -> enabled", true, new TaskSwitch(props(false), "true").isEnabled()); check("sys_config=false, yml=true -> enabled", false, new TaskSwitch(props(true), "false").isEnabled()); System.out.println("-- 2) yml 兜底 --"); check("sys_config 行缺失, yml=true -> enabled", true, new TaskSwitch(props(true), (String) null).isEnabled()); check("sys_config 行缺失, yml=false -> enabled", false, new TaskSwitch(props(false), (String) null).isEnabled()); System.out.println("-- 3) 非法值按「不是 false 即启用」--"); check("sys_config=yes -> enabled", true, new TaskSwitch(props(false), "yes").isEnabled()); check("sys_config=FALSE(大小写) -> enabled", false, new TaskSwitch(props(true), "FALSE").isEnabled()); System.out.println("-- 4) fail-open:取不到配置服务时不抛异常且按 yml --"); boolean threw = false; boolean value = false; try { value = new TaskSwitch(props(true), (String) null).isEnabled(); } catch (Exception e) { threw = true; } check("无配置服务时不抛异常", false, threw); check("无配置服务时按 yml=true", true, value); System.out.println("-- 5) 10 秒缓存与 invalidateCache --"); TaskSwitch sw = new TaskSwitch(props(false), "false"); check("初始 enabled", false, sw.isEnabled()); sw.invalidateCache(); check("invalidateCache 后仍读同一配置值", false, sw.isEnabled()); System.out.println("-- 6) 拦截器 --"); TaskAvailabilityInterceptor on = new TaskAvailabilityInterceptor(new TaskSwitch(props(true), "true")); boolean ok = false; try { ok = on.preHandle(null, null, null); } catch (Exception ignored) { } check("启用时放行", true, ok); TaskAvailabilityInterceptor off = new TaskAvailabilityInterceptor(new TaskSwitch(props(true), "false")); ServiceException caught = null; try { // request 传 null:验证拦截器对 null request 安全(测试会这么调) off.preHandle(null, null, null); } catch (ServiceException e) { caught = e; } check("关闭时抛 ServiceException", true, caught != null); String msg = caught == null ? "" : caught.getMessage(); check("message 含 task.enabled=false", true, msg.contains("task.enabled=false")); check("message 含 接口不可用", true, msg.contains("接口不可用")); System.out.println("-- 7) 守卫路径 --"); java.util.List guarded = TaskWebConfig.guardedPathPatterns(); check("守卫路径条数", 2, guarded.size()); check("含 /smartor/servicetask/**", true, guarded.contains("/smartor/servicetask/**")); check("含 /smartor/serviceSubtask/**", true, guarded.contains("/smartor/serviceSubtask/**")); // 关键:不得误伤满意度模块的 serviceSubtaskDetail check("不含 serviceSubtaskDetail", false, guarded.contains("/smartor/serviceSubtaskDetail/**")); System.out.println("-- 7.1) 电话模块保留原 URL 的 6 条必须被排除 --"); java.util.List excluded = TaskWebConfig.excludedPhonePaths(); check("排除条数", 6, excluded.size()); // 这份清单必须与 ruoyi-phone 的 PhoneWebConfig#subtaskPathsOwnedByPhone() 完全一致; // 两个模块不能互相 import(依赖方向只有 phone -> task),故两边各写一份常量 + 各自断言。 String[] expected = new String[]{ "/smartor/serviceSubtask/phoneCallBack", "/smartor/serviceSubtask/phoneCallBackYQ", "/smartor/serviceSubtask/taskPull", "/smartor/serviceSubtask/saveQuestionAnswerPhone", "/smartor/serviceSubtask/recordAcceptshanghai", "/smartor/serviceSubtask/recordAccept", }; for (String p : expected) { check("排除: " + p, true, excluded.contains(p)); } // 排除的必须是"电话自己的",不能顺手把任务的随访端点也排除掉 check("未误排除 patItem", false, excluded.contains("/smartor/serviceSubtask/patItem")); check("未误排除 getInfo", false, excluded.contains("/smartor/serviceSubtask/getInfo")); System.out.println("-- 8) TaskFacade --"); TaskFacade facadeOn = new TaskFacade(props(true), "true"); check("门面 isEnabled(开)", true, ((TaskApi) facadeOn).isEnabled()); TaskFacade facadeOff = new TaskFacade(props(true), "false"); check("门面 isEnabled(关)", false, ((TaskApi) facadeOff).isEnabled()); System.out.println("=================================================="); System.out.println("通过 " + passed + " 项,失败 " + failed + " 项"); if (failed > 0) { System.exit(1); } } private TaskSwitchCheck() { } }