liusheng
8 天以前 34f78281cf395a24e1f3f1c3b861186ff8a03b7b
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
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()}:后者候选 &gt;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);
        }
    }
}