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} 这条路。
*
* 断言覆盖
*
* - sys_config 优先于 yml;
* - yml 兜底;
* - 参数行缺失 → 回落 yml;
* - 非法值 → 按"不是 false 即启用";
* - fail-open:读不到配置服务时按启用,且不抛异常;
* - 10 秒缓存:翻转值不会立即生效,{@code invalidateCache()} 后立即生效;
* - 拦截器:开启放行、关闭抛 {@link ServiceException} 且 message 含
* {@code task.enabled=false} 与 {@code 接口不可用}(并验证 request 为 null 不炸);
* - 守卫路径恰好 2 条,且是任务模块自己的两个前缀;
* - {@link TaskFacade} 是 {@link TaskApi} 且随开关变化。
*
*/
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() {
}
}