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; /** * 电话模块对外门面(常驻 Bean)。 * *

为什么必须"常驻"

* 本门面永远是 Spring Bean,不随开关状态增删。这样调用方的 * {@code @Autowired PhoneApi} 在任何开关状态下都不会启动失败 * —— 与 {@code TaskFacade} / {@code SatisfactionFacade} 是同一条设计约束 * (历史教训:用 {@code @ConditionalOnProperty} 摘 Bean 会让调用方启动即失败, * 且只在启动时求值一次,做不到"改完参数 10 秒生效")。 * *

关闭时的行为

* 开关({@code sys_config: phone.enabled})关闭时,本门面各方法返回空值/空列表并打 WARN, * 不抛异常:电话是随访的一条发送通道,模块被关掉时不能让调用方(随访编排、定时任务) * 跟着炸。HTTP 侧另有 {@code PhoneAvailabilityInterceptor} 负责"明确报错", * 两者分工:端点 → 明确报错;模块内调用 → 静默降级。 * *

使用示例

*
 * @Autowired
 * private PhoneApi phoneApi;
 *
 * if (phoneApi.isEnabled()) {
 *     List<PullTaskVO> tasks = phoneApi.taskPull();
 * }
 * 
*/ @Slf4j @Component public class PhoneFacade implements PhoneApi { /** * 运行时开关({@code sys_config} 优先,{@link PhoneProperties} 兜底) */ private final PhoneSwitch phoneSwitch; /** * 模块内实现(唯一实现 {@code PhoneServiceImpl})。 *

* 与满意度门面不同,这里不需要 {@code ObjectProvider} + 自引用过滤: * 本类不实现 {@code IPhoneService},不存在"Spring 把自引用当兜底候选"导致的无限递归; * 实现 Bean 也不受任何条件装配控制,恒定存在。 *

* 允许为 {@code null}:仅供测试/非 Spring 环境构造时使用(此时所有方法按"不可用"降级)。 */ private final IPhoneService phoneService; /** * Spring 使用的构造器。 *

* ⚠️ 显式标注 {@code @Autowired}:本类有两个公开构造器(另一个给测试用), * 标注后 Spring 不会退回查找无参构造器。该规则已由 * {@code sql/_probe/verify_spring_beans.py} 静态把关。 */ @Autowired public PhoneFacade(PhoneSwitch phoneSwitch, IPhoneService phoneService) { this.phoneSwitch = phoneSwitch; this.phoneService = phoneService; } /** * 供测试/非 Spring 环境直接构造:{@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 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; } }