package com.ruoyi.phone.config;
|
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.phone.api.PhoneApi;
|
import com.ruoyi.phone.api.PhoneFacade;
|
import com.ruoyi.phone.domain.PhoneCallBackSHVO;
|
import com.ruoyi.phone.domain.PhoneCallBackYQVO;
|
import com.ruoyi.phone.domain.PullTaskVO;
|
|
import java.util.List;
|
|
/**
|
* 电话模块开关自检 —— 纯 {@code main},不依赖 JUnit / Spring / 数据库。
|
*
|
* <h3>为什么不用 JUnit</h3>
|
* 与任务模块的 {@code TaskSwitchCheck}、满意度模块的 {@code SatisfactionSwitchCheck} 同款:
|
* 本仓离线仓库缺 {@code surefire-junit3:2.12.4},走
|
* {@code mvn -o -q -pl ruoyi-phone -am -DskipTests test-compile} + {@code java -cp} 这条路。
|
*
|
* <h3>断言覆盖</h3>
|
* <ol>
|
* <li>sys_config 优先于 yml;</li>
|
* <li>yml 兜底;参数行缺失 → 回落 yml;</li>
|
* <li>非法值 → 按"不是 false 即启用";</li>
|
* <li>fail-open:读不到配置服务时按启用,且不抛异常;</li>
|
* <li>10 秒缓存与 invalidateCache;</li>
|
* <li>拦截器:开启放行、关闭抛 {@link ServiceException} 且 message 含
|
* {@code phone.enabled=false} 与 {@code 接口不可用}(并验证 request 为 null 不炸);</li>
|
* <li>守卫路径恰好 8 条(6 条在原 {@code /smartor/serviceSubtask/**} 下 +
|
* 2 条三汇 {@code /smartor/shphone/**}),且"归电话的 6 条"与
|
* {@code TaskWebConfig#excludedPhonePaths()} 一一对应;</li>
|
* <li>门面:{@link PhoneFacade} 是 {@link PhoneApi};关闭时各方法降级为空值/空列表且不抛异常。</li>
|
* </ol>
|
*/
|
public final class PhoneSwitchCheck {
|
|
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 PhoneProperties props(boolean enabled) {
|
PhoneProperties p = new PhoneProperties();
|
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 PhoneSwitch(props(false), "true").isEnabled());
|
check("sys_config=false, yml=true -> enabled", false,
|
new PhoneSwitch(props(true), "false").isEnabled());
|
|
System.out.println("-- 2) yml 兜底 --");
|
check("sys_config 行缺失, yml=true -> enabled", true,
|
new PhoneSwitch(props(true), (String) null).isEnabled());
|
check("sys_config 行缺失, yml=false -> enabled", false,
|
new PhoneSwitch(props(false), (String) null).isEnabled());
|
|
System.out.println("-- 3) 非法值按「不是 false 即启用」--");
|
check("sys_config=yes -> enabled", true, new PhoneSwitch(props(false), "yes").isEnabled());
|
check("sys_config=FALSE(大小写) -> enabled", false,
|
new PhoneSwitch(props(true), "FALSE").isEnabled());
|
|
System.out.println("-- 4) fail-open:取不到配置服务时不抛异常且按 yml --");
|
boolean threw = false;
|
boolean value = false;
|
try {
|
value = new PhoneSwitch(props(true), (String) null).isEnabled();
|
} catch (Exception e) {
|
threw = true;
|
}
|
check("无配置服务时不抛异常", false, threw);
|
check("无配置服务时按 yml=true", true, value);
|
|
System.out.println("-- 5) 10 秒缓存与 invalidateCache --");
|
PhoneSwitch sw = new PhoneSwitch(props(false), "false");
|
check("初始 enabled", false, sw.isEnabled());
|
sw.invalidateCache();
|
check("invalidateCache 后仍读同一配置值", false, sw.isEnabled());
|
|
System.out.println("-- 6) 拦截器 --");
|
PhoneAvailabilityInterceptor on =
|
new PhoneAvailabilityInterceptor(new PhoneSwitch(props(true), "true"));
|
boolean ok = false;
|
try {
|
ok = on.preHandle(null, null, null);
|
} catch (Exception ignored) {
|
}
|
check("启用时放行", true, ok);
|
|
PhoneAvailabilityInterceptor off =
|
new PhoneAvailabilityInterceptor(new PhoneSwitch(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 含 phone.enabled=false", true, msg.contains("phone.enabled=false"));
|
check("message 含 接口不可用", true, msg.contains("接口不可用"));
|
|
System.out.println("-- 7) 守卫路径 --");
|
String[] guarded = PhoneWebConfig.guardedPathPatterns();
|
check("守卫路径条数", 8, guarded.length);
|
check("含 phoneCallBack", true, contains(guarded, "/smartor/serviceSubtask/phoneCallBack"));
|
check("含 phoneCallBackYQ", true, contains(guarded, "/smartor/serviceSubtask/phoneCallBackYQ"));
|
check("含 taskPull", true, contains(guarded, "/smartor/serviceSubtask/taskPull"));
|
check("含 saveQuestionAnswerPhone", true, contains(guarded, "/smartor/serviceSubtask/saveQuestionAnswerPhone"));
|
check("含 recordAcceptshanghai", true, contains(guarded, "/smartor/serviceSubtask/recordAcceptshanghai"));
|
check("含 recordAccept", true, contains(guarded, "/smartor/serviceSubtask/recordAccept"));
|
check("含 getFirstQues", true, contains(guarded, "/smartor/shphone/getFirstQues"));
|
check("含 processResust", true, contains(guarded, "/smartor/shphone/processResust"));
|
// 关键:不得误伤任务模块自己的随访端点与外呼实例配置端点
|
check("不含 patItem", false, contains(guarded, "/smartor/serviceSubtask/patItem"));
|
check("不含 addOutboundInstance", false, contains(guarded, "/smartor/shphone/addOutboundInstance"));
|
|
System.out.println("-- 7.1) 归电话的 6 条(任务模块要排除的那 6 条)--");
|
String[] owned = PhoneWebConfig.subtaskPathsOwnedByPhone();
|
check("条数", 6, owned.length);
|
for (String p : owned) {
|
check("在守卫清单里: " + p, true, contains(guarded, p));
|
}
|
|
System.out.println("-- 8) 门面降级 --");
|
PhoneApi facadeOff = new PhoneFacade(props(true), "false", null);
|
check("关闭时 isEnabled", false, facadeOff.isEnabled());
|
List<PullTaskVO> disabledTasks = facadeOff.taskPull();
|
check("关闭时 taskPull 返回空列表", true, disabledTasks != null && disabledTasks.isEmpty());
|
check("关闭时 saveQuestionAnswerPhone 返回 0", 0, facadeOff.saveQuestionAnswerPhone(null));
|
PhoneCallBackYQVO disabledYq = facadeOff.phoneCallBackYQ(null);
|
check("关闭时 phoneCallBackYQ 返回非 null 空对象", true, disabledYq != null);
|
PhoneCallBackSHVO disabledSh = facadeOff.getFirstQues(null);
|
check("关闭时 getFirstQues 返回非 null 空对象", true, disabledSh != null);
|
boolean facadeThrew = false;
|
try {
|
facadeOff.phoneCallBack(null);
|
facadeOff.recordAccept(null);
|
facadeOff.processResust(null);
|
} catch (Exception e) {
|
facadeThrew = true;
|
}
|
check("关闭时门面调用不抛异常", false, facadeThrew);
|
|
PhoneApi facadeOn = new PhoneFacade(props(true), "true", null);
|
check("开启时 isEnabled", true, facadeOn.isEnabled());
|
// 实现缺失(测试传 null)也按"不可用"降级,不抛异常
|
boolean onThrew = false;
|
try {
|
facadeOn.phoneCallBack(null);
|
} catch (Exception e) {
|
onThrew = true;
|
}
|
check("无实现时不抛异常", false, onThrew);
|
|
System.out.println("==================================================");
|
System.out.println("通过 " + passed + " 项,失败 " + failed + " 项");
|
if (failed > 0) {
|
System.exit(1);
|
}
|
}
|
|
private static boolean contains(String[] array, String value) {
|
if (array == null) {
|
return false;
|
}
|
for (String s : array) {
|
if (s.equals(value)) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private PhoneSwitchCheck() {
|
}
|
}
|