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 / 数据库。 * *

为什么不用 JUnit

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

断言覆盖

*
    *
  1. sys_config 优先于 yml;
  2. *
  3. yml 兜底;参数行缺失 → 回落 yml;
  4. *
  5. 非法值 → 按"不是 false 即启用";
  6. *
  7. fail-open:读不到配置服务时按启用,且不抛异常;
  8. *
  9. 10 秒缓存与 invalidateCache;
  10. *
  11. 拦截器:开启放行、关闭抛 {@link ServiceException} 且 message 含 * {@code phone.enabled=false} 与 {@code 接口不可用}(并验证 request 为 null 不炸);
  12. *
  13. 守卫路径恰好 8 条(6 条在原 {@code /smartor/serviceSubtask/**} 下 + * 2 条三汇 {@code /smartor/shphone/**}),且"归电话的 6 条"与 * {@code TaskWebConfig#excludedPhonePaths()} 一一对应;
  14. *
  15. 门面:{@link PhoneFacade} 是 {@link PhoneApi};关闭时各方法降级为空值/空列表且不抛异常。
  16. *
*/ 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 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() { } }