liusheng
8 天以前 f0a09ee996ab2605869844d6a3d812709e2bf0fa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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() {
    }
}