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 / 数据库。
|
*
|
* <h3>为什么不用 JUnit</h3>
|
* 与满意度模块的 {@code SatisfactionSwitchCheck} 同款:本仓离线仓库缺
|
* {@code surefire-junit3:2.12.4},走
|
* {@code mvn -o -q -pl ruoyi-task -am -DskipTests test-compile} + {@code java -cp} 这条路。
|
*
|
* <h3>断言覆盖</h3>
|
* <ol>
|
* <li>sys_config 优先于 yml;</li>
|
* <li>yml 兜底;</li>
|
* <li>参数行缺失 → 回落 yml;</li>
|
* <li>非法值 → 按"不是 false 即启用";</li>
|
* <li>fail-open:读不到配置服务时按启用,且不抛异常;</li>
|
* <li>10 秒缓存:翻转值不会立即生效,{@code invalidateCache()} 后立即生效;</li>
|
* <li>拦截器:开启放行、关闭抛 {@link ServiceException} 且 message 含
|
* {@code task.enabled=false} 与 {@code 接口不可用}(并验证 request 为 null 不炸);</li>
|
* <li>守卫路径恰好 2 条,且是任务模块自己的两个前缀;</li>
|
* <li>{@link TaskFacade} 是 {@link TaskApi} 且随开关变化。</li>
|
* </ol>
|
*/
|
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<String> 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<String> 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() {
|
}
|
}
|