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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package com.ruoyi.phone.api;
 
import com.ruoyi.phone.config.PhoneProperties;
import com.ruoyi.phone.config.PhoneSwitch;
import com.ruoyi.phone.domain.PhoneCallBackSHVO;
import com.ruoyi.phone.domain.PhoneCallBackVO;
import com.ruoyi.phone.domain.PhoneCallBackYQVO;
import com.ruoyi.phone.domain.PhoneCallRecordVO;
import com.ruoyi.phone.domain.PhoneCallReqYQVO;
import com.ruoyi.phone.domain.PhoneCallResultYQVO;
import com.ruoyi.phone.domain.PullTaskVO;
import com.ruoyi.phone.service.IPhoneService;
import com.ruoyi.task.domain.ServiceSubTaskDetailReq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.util.Collections;
import java.util.List;
 
/**
 * 电话模块对外门面(<b>常驻 Bean</b>)。
 *
 * <h3>为什么必须"常驻"</h3>
 * 本门面<b>永远</b>是 Spring Bean,不随开关状态增删。这样调用方的
 * {@code @Autowired PhoneApi} 在任何开关状态下都<b>不会启动失败</b>
 * —— 与 {@code TaskFacade} / {@code SatisfactionFacade} 是同一条设计约束
 * (历史教训:用 {@code @ConditionalOnProperty} 摘 Bean 会让调用方启动即失败,
 * 且只在启动时求值一次,做不到"改完参数 10 秒生效")。
 *
 * <h3>关闭时的行为</h3>
 * 开关({@code sys_config: phone.enabled})关闭时,本门面各方法<b>返回空值/空列表并打 WARN</b>,
 * <b>不抛异常</b>:电话是随访的一条发送通道,模块被关掉时不能让调用方(随访编排、定时任务)
 * 跟着炸。HTTP 侧另有 {@code PhoneAvailabilityInterceptor} 负责"明确报错",
 * 两者分工:<b>端点 → 明确报错;模块内调用 → 静默降级</b>。
 *
 * <h3>使用示例</h3>
 * <pre>
 * &#64;Autowired
 * private PhoneApi phoneApi;
 *
 * if (phoneApi.isEnabled()) {
 *     List&lt;PullTaskVO&gt; tasks = phoneApi.taskPull();
 * }
 * </pre>
 */
@Slf4j
@Component
public class PhoneFacade implements PhoneApi {
 
    /**
     * 运行时开关({@code sys_config} 优先,{@link PhoneProperties} 兜底)
     */
    private final PhoneSwitch phoneSwitch;
 
    /**
     * 模块内实现(唯一实现 {@code PhoneServiceImpl})。
     * <p>
     * 与满意度门面不同,这里不需要 {@code ObjectProvider} + 自引用过滤:
     * 本类<b>不实现</b> {@code IPhoneService},不存在"Spring 把自引用当兜底候选"导致的无限递归;
     * 实现 Bean 也不受任何条件装配控制,恒定存在。
     * <p>
     * 允许为 {@code null}:仅供测试/非 Spring 环境构造时使用(此时所有方法按"不可用"降级)。
     */
    private final IPhoneService phoneService;
 
    /**
     * Spring 使用的构造器。
     * <p>
     * ⚠️ <b>显式标注 {@code @Autowired}</b>:本类有两个公开构造器(另一个给测试用),
     * 标注后 Spring 不会退回查找无参构造器。该规则已由
     * {@code sql/_probe/verify_spring_beans.py} 静态把关。
     */
    @Autowired
    public PhoneFacade(PhoneSwitch phoneSwitch, IPhoneService phoneService) {
        this.phoneSwitch = phoneSwitch;
        this.phoneService = phoneService;
    }
 
    /**
     * 供<b>测试/非 Spring 环境</b>直接构造:{@code sysConfigValue} 就是 {@code sys_config}
     * 里 {@code phone.enabled} 的值,{@code null} 表示"参数行不存在";{@code phoneService} 可为 {@code null}。
     */
    public PhoneFacade(PhoneProperties properties, String sysConfigValue, IPhoneService phoneService) {
        this(new PhoneSwitch(properties, sysConfigValue), phoneService);
    }
 
    /**
     * 取运行时开关(供拦截器复用同一实例、也供测试断言)
     */
    public PhoneSwitch getPhoneSwitch() {
        return phoneSwitch;
    }
 
    @Override
    public boolean isEnabled() {
        return phoneSwitch.isEnabled();
    }
 
    @Override
    public void phoneCallBack(PhoneCallBackVO phoneCallBackVO) {
        if (!available("phoneCallBack")) {
            return;
        }
        phoneService.phoneCallBack(phoneCallBackVO);
    }
 
    @Override
    public PhoneCallBackYQVO phoneCallBackYQ(PhoneCallReqYQVO phoneCallReqYQVO) {
        if (!available("phoneCallBackYQ")) {
            return new PhoneCallBackYQVO();
        }
        return phoneService.phoneCallBackYQ(phoneCallReqYQVO);
    }
 
    @Override
    public List<PullTaskVO> taskPull() {
        if (!available("taskPull")) {
            return Collections.emptyList();
        }
        return phoneService.taskPull();
    }
 
    @Override
    public Integer saveQuestionAnswerPhone(ServiceSubTaskDetailReq serviceSubTaskDetailReq) {
        if (!available("saveQuestionAnswerPhone")) {
            return 0;
        }
        return phoneService.saveQuestionAnswerPhone(serviceSubTaskDetailReq);
    }
 
    @Override
    public Integer recordAccept(PhoneCallRecordVO phoneCallRecordVO) {
        if (!available("recordAccept")) {
            return 0;
        }
        return phoneService.recordAccept(phoneCallRecordVO);
    }
 
    @Override
    public PhoneCallBackSHVO getFirstQues(PhoneCallReqYQVO phoneCallReqYQVO) {
        if (!available("getFirstQues")) {
            return new PhoneCallBackSHVO();
        }
        return phoneService.getFirstQues(phoneCallReqYQVO);
    }
 
    @Override
    public PhoneCallBackSHVO processResust(PhoneCallResultYQVO phoneCallResultYQVO) {
        if (!available("processResust")) {
            return new PhoneCallBackSHVO();
        }
        return phoneService.processResust(phoneCallResultYQVO);
    }
 
    /**
     * 开关 + 实现可用性判断,不可用时打 WARN 并让调用方降级
     */
    private boolean available(String method) {
        if (!phoneSwitch.isEnabled()) {
            log.warn("【电话】模块已关闭(参数配置 phone.enabled=false),{} 本次调用降级为空结果", method);
            return false;
        }
        if (phoneService == null) {
            log.warn("【电话】未找到电话模块实现(IPhoneService 缺失),{} 本次调用降级为空结果", method);
            return false;
        }
        return true;
    }
}