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
package com.ruoyi.template.config;
 
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.template.api.TemplateFacade;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
/**
 * 模板模块开关拦截器。
 *
 * <h3>作用</h3>
 * 当开关关闭({@code sys_config} 中 {@code template.enabled=false})时,
 * 命中模板端点的请求会被立即拦下并抛出 {@link ServiceException},
 * 由 RuoYi 的全局异常处理器返回<b>明确的业务错误提示</b>(而不是 404 或静默返回空数据)。
 * <p>
 * 开关值由 {@link TemplateSwitch} 运行时读取({@code sys_config},10 秒进程内缓存),
 * 因此改完参数设置<b>不需要重启</b>,最多 10 秒即生效。
 *
 * <h3>为什么不用 {@code @ConditionalOnProperty} 让 Controller 不注册</h3>
 * <ul>
 *   <li>那样调用方只会拿到 HTTP 404 —— 是失败,但不是"明确错误",
 *       前端无法区分"模块被关闭"和"地址写错了";</li>
 *   <li>{@code @ConditionalOnProperty} 只在<b>启动时</b>求值一次,
 *       做不到"改完参数配置就生效",与参数表开关的诉求冲突。</li>
 * </ul>
 * <p>
 * 这两条理由与满意度模块 {@code SatisfactionAvailabilityInterceptor} 完全一致 ——
 * 本类是照它的形状写的(`ServiceException` + 直接注入 switch + request 允许为 null)。
 *
 * <h3>为什么不会影响其它业务与启动</h3>
 * <ul>
 *   <li>本拦截器只登记在 {@link TemplateWebConfig#guardedPathPatterns()} 列出的
 *       <b>模板端点</b>上,不会碰服务子任务/档案/问卷任务实例/方案流程等非模板接口;</li>
 *   <li>模块关闭时 Controller 与 Service 依旧是普通 Bean(启动不受影响);</li>
 *   <li>其它模块通过 {@link com.ruoyi.template.api.TemplateFacade} 取模板时,
 *       门面<b>同样会拒绝访问</b>(抛 {@code TemplateModuleDisabledException})——
 *       本开关是"模板能力总闸",HTTP 与门面两条路都受它管,
 *       区别只在异常类型({@code ServiceException} 给 HTTP 调用方,
 *       {@code TemplateModuleDisabledException} 给 Java 调用方精确 catch)。</li>
 * </ul>
 */
@Slf4j
@Component
public class TemplateAvailabilityInterceptor implements HandlerInterceptor {
 
    /**
     * 模块被关闭时返回给调用方的错误提示(前端可直接展示)。
     * <p>
     * ⚠️ <b>单一来源</b>:直接引用门面的常量,不在此处另抄一份。
     * 门面(跨模块 Java 调用)与拦截器(HTTP 端点)是同一个开关、同一个原因,
     * 文案必须逐字一致;两处各写一份迟早会漂移,而漂移了不报错、只有用户看得见。
     */
    public static final String DISABLED_MESSAGE = TemplateFacade.DISABLED_MESSAGE;
 
    private final TemplateSwitch templateSwitch;
 
    public TemplateAvailabilityInterceptor(TemplateSwitch templateSwitch) {
        this.templateSwitch = templateSwitch;
    }
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (!templateSwitch.isEnabled()) {
            // request 允许为 null(单元测试直接调 preHandle 时不构造请求对象)
            String method = request == null ? "-" : request.getMethod();
            String uri = request == null ? "-" : request.getRequestURI();
            log.warn("【模板】模块已关闭(参数配置 template.enabled=false),拒绝访问 {} {}", method, uri);
            throw new ServiceException(DISABLED_MESSAGE);
        }
        return true;
    }
}