package com.ruoyi.publicity.api;
|
|
import com.ruoyi.common.core.service.PublicityApi;
|
import com.ruoyi.publicity.config.PublicityProperties;
|
import com.ruoyi.publicity.config.PublicitySwitch;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Component;
|
|
/**
|
* 宣教模块对外门面(<b>常驻 Bean</b>),实现 ruoyi-common 里的 {@link PublicityApi}。
|
*
|
* <h3>为什么实现 ruoyi-common 的接口,而不是自带一个</h3>
|
* 调用方是 {@code ruoyi-inhospvisit},而它只依赖 ruoyi-common(不能依赖 ruoyi-publicity,
|
* 否则 {@code inhospvisit <-> publicity} 双向依赖 → Maven 构建失败)。
|
* 所以契约放在 ruoyi-common,本模块实现它;
|
* 调用方用 {@code ObjectProvider}/{@code SpringUtils.getBean} 按类型查找 + 降级。
|
* —— 与满意度的 {@code SatisfactionFacade} 实现 ruoyi-common 的
|
* {@code ISatisfactionCategoryService} 完全同一手法。
|
*
|
* <h3>关闭时的行为</h3>
|
* 返回 0(没有宣教要做)并打 WARN,<b>不抛异常</b>。
|
* 宣教失败绝不能拖垮出入院随访主流程。
|
*/
|
@Slf4j
|
@Component
|
public class PublicityFacade implements PublicityApi {
|
|
private final PublicityProperties properties;
|
|
private final PublicitySwitch publicitySwitch;
|
|
/**
|
* 内部实现({@link DefaultPublicityApi});
|
* 用 {@code ObjectProvider} 惰性取,即使实现缺失也不至于注入失败。
|
*/
|
private final ObjectProvider<PublicityApi> apiProvider;
|
|
/**
|
* Spring 使用的构造器。
|
* <p>
|
* ⚠️ <b>必须显式标注 {@code @Autowired}</b>:本类有两个公开构造器,
|
* 不标注会因"构造器不唯一"退回无参构造器 → 启动报 {@code No default constructor found}。
|
* —— 满意度模块实机踩到的坑,此处刻意规避。
|
*/
|
@Autowired
|
public PublicityFacade(PublicityProperties properties,
|
PublicitySwitch publicitySwitch,
|
ObjectProvider<PublicityApi> apiProvider) {
|
this.properties = properties;
|
this.publicitySwitch = publicitySwitch;
|
this.apiProvider = apiProvider;
|
}
|
|
/**
|
* 供<b>测试/非 Spring 环境</b>直接构造
|
*/
|
public PublicityFacade(PublicityProperties properties, PublicityApi delegate, String sysConfigValue) {
|
this(properties, new PublicitySwitch(properties, sysConfigValue), new FixedProvider(delegate));
|
}
|
|
/**
|
* 取运行时开关(供测试断言 / 将来若加拦截器时复用)
|
*/
|
public PublicitySwitch getPublicitySwitch() {
|
return publicitySwitch;
|
}
|
|
/**
|
* 取内部实现;关闭、缺失、或只找到自己时返回 {@code null}。
|
* <p>
|
* ⚠️ <b>必须排除"自己"</b>:本类自身也实现了 {@link PublicityApi},
|
* Spring 按类型查找时若没有别的实现,会把自引用当兜底候选返回,
|
* 于是 {@code delegate.dealInHospPublicity(...)} 变成无限递归 → {@code StackOverflowError}。
|
* <p>
|
* 用 {@code stream()} 而不是 {@code getIfAvailable()}:后者候选 >1 时抛
|
* {@code NoUniqueBeanDefinitionException},会被吞成 null,掩盖配置错误。
|
*/
|
private PublicityApi delegateOrNull() {
|
if (apiProvider == null) {
|
return null;
|
}
|
try {
|
return apiProvider.stream()
|
.filter(api -> api != this)
|
.findFirst()
|
.orElse(null);
|
} catch (Exception e) {
|
log.warn("【宣教】获取内部实现失败,按未启用处理:{}", e.toString());
|
return null;
|
}
|
}
|
|
/**
|
* 宣教模块是否可用(开关打开 <b>且</b> 实现存在)
|
*/
|
public boolean isEnabled() {
|
return publicitySwitch.isEnabled() && delegateOrNull() != null;
|
}
|
|
@Override
|
public int dealInHospPublicity(String config) {
|
if (!isEnabled()) {
|
log.warn("【宣教】模块未启用(参数配置 publicity.enabled=false),入院宣教跳过。config={}", config);
|
return 0;
|
}
|
return delegateOrNull().dealInHospPublicity(config);
|
}
|
|
@Override
|
public int dealMedicalPublicity(String config) {
|
if (!isEnabled()) {
|
log.warn("【宣教】模块未启用(参数配置 publicity.enabled=false),药品宣教跳过。config={}", config);
|
return 0;
|
}
|
return delegateOrNull().dealMedicalPublicity(config);
|
}
|
|
/**
|
* 固定值的 {@code ObjectProvider} 实现:仅用于非 Spring 环境/测试
|
*/
|
private static final class FixedProvider implements ObjectProvider<PublicityApi> {
|
|
private final PublicityApi api;
|
|
private FixedProvider(PublicityApi api) {
|
this.api = api;
|
}
|
|
@Override
|
public PublicityApi getObject(Object... args) {
|
return api;
|
}
|
|
@Override
|
public PublicityApi getIfAvailable() {
|
return api;
|
}
|
|
@Override
|
public PublicityApi getIfUnique() {
|
return api;
|
}
|
|
@Override
|
public PublicityApi getObject() {
|
return api;
|
}
|
|
@Override
|
public java.util.stream.Stream<PublicityApi> stream() {
|
return api == null ? java.util.stream.Stream.empty() : java.util.stream.Stream.of(api);
|
}
|
}
|
}
|