liusheng
9 天以前 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
package com.ruoyi.publicity.controller;
 
import com.ruoyi.common.core.service.IConfigService;
import com.ruoyi.publicity.api.PublicityFacade;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.LinkedHashMap;
import java.util.Map;
 
/**
 * 宣教模块的 HTTP 入口:<b>宣教生成入口 + 开关/状态查询</b>。
 *
 * <h3>为什么这些端点是"新增"的(与出入院模块的关键区别)</h3>
 * 出入院模块的 controller 是<b>把 ruoyi-admin 已有的端点搬进来</b>(URL 一个不变);
 * 而宣教在本仓库里<b>从来没有过 controller</b> —— 它原本只是
 * {@code PatMedInhospServiceImpl#dealOutHospInfo} 内部的两段调用
 * (入院宣教 L331-409、药品宣教 L411-444),迁出后也只到 Service 层为止。
 * 因此本类的 URL 是<b>新定义</b>的,前端若要手动触发需要接新地址;
 * 但"主流程自动触发"不受影响(那是 {@code RyTask} / {@code dealOutHospInfo} 走的门面调用)。
 *
 * <h3>端点清单</h3>
 * <pre>
 * POST /smartor/publicity/dealInHospPublicity    入院宣教(科室 type=4 / 病区 type=5)
 * POST /smartor/publicity/dealMedicalPublicity   药品宣教(仅 sltd 站点,type=0)
 * GET  /smartor/publicity/status                 开关与子开关状态(只读、不受开关拦截)
 * </pre>
 *
 * <h3>⚠️ 开关的层级(两个开关,别混)</h3>
 * <ol>
 *   <li>{@code sys_config: publicity.enabled} —— <b>模块级</b>开关,由
 *       {@link PublicityFacade#isEnabled()} 在运行时读(10 秒缓存)。
 *       它<b>只</b>决定 {@link PublicityFacade} 是否把调用转发下去;
 *       关闭时两个生成端点返回 0 并打 WARN。</li>
 *   <li>{@code sys_config: in.helibrary.switch} —— <b>入院宣教自身的业务开关</b>,
 *       只有值等于 {@code "2"} 时入院宣教才真的执行。它在
 *       {@code PublicityServiceImpl} 内部读取,与模块开关无关。</li>
 * </ol>
 * 本类的 {@code /status} 会把两者分别如实报出来 —— 这两个开关是排查
 * "为什么没发宣教"时最容易看错的地方。
 *
 * <h3>与出入院模块拦截器的一致性</h3>
 * 两个生成端点由 {@code PublicityAvailabilityInterceptor} 在模块关闭时拦下并抛
 * 明确的业务异常(而不是静默返回 0),与 {@code inhospvisit} 的做法一致;
 * {@code /status} <b>刻意不挂拦截器</b> —— 模块关掉时你更需要能查到状态。
 */
@Slf4j
@Api(description = "宣教")
@RestController
@RequestMapping("/smartor/publicity")
public class PublicityController {
 
    /** 出院随访「通知类任务」提前几天发(与出入院模块读同一个键) */
    private static final String VISIT_EARLY_DAY_KEY = "visit.early.day";
 
    /** 入院宣教自身的业务开关:只有等于 "2" 才执行(与 PublicityServiceImpl 内一致) */
    private static final String HELIBRARY_SWITCH_KEY = "in.helibrary.switch";
 
    /** 入院宣教业务开关的"开"值 */
    private static final String HELIBRARY_SWITCH_ON = "2";
 
    /**
     * 本模块常驻门面。
     * <p>
     * ⚠️ 注入门面而不是 {@code PublicityService}:门面永远存在,
     * 即使模块被开关关掉或没被打进应用,本 Controller 也<b>不会启动失败</b>,
     * 只是调用降级并打 WARN。
     */
    @Autowired
    private PublicityFacade publicityFacade;
 
    /**
     * 参数配置服务(ruoyi-common 的接口,实现在 ruoyi-system)。
     * <p>
     * ⚠️ 用 {@code ObjectProvider} 惰性取:本模块只依赖 ruoyi-common,
     * 在独立部署/测试等"容器里没有实现"的场景下不能因此启动失败。
     */
    @Autowired
    private ObjectProvider<IConfigService> configServiceProvider;
 
    /**
     * 入院宣教:科室维度(type=4)+ 病区维度(type=5)。
     * <p>
     * 逐行对应原 {@code PatMedInhospServiceImpl#dealOutHospInfo} L331-409 的调用部分:
     * 原实现在主流程里直接调 {@code dealInHospPublicity(config)},此处只是把它暴露成端点。
     * <p>
     * 返回处理记录数(与原实现的日志用值同源),便于手工触发时看到结果。
     */
    @ApiOperation("入院宣教(科室 type=4 / 病区 type=5)")
    @PostMapping("/dealInHospPublicity")
    public int dealInHospPublicity() {
        return publicityFacade.dealInHospPublicity(readVisitEarlyDay());
    }
 
    /**
     * 药品宣教:按患者的 {@code medicalCode} 逐个药品编码匹配长期宣教任务(type=0)。
     * <p>
     * 仅 {@code spring.profiles.active == "sltd"} 时执行,其它站点由
     * {@code PublicityServiceImpl} 直接返回 0(站点判断<b>不在</b>本 Controller,
     * 与原实现位置一致)。
     */
    @ApiOperation("药品宣教(仅 sltd 站点,type=0)")
    @PostMapping("/dealMedicalPublicity")
    public int dealMedicalPublicity() {
        return publicityFacade.dealMedicalPublicity(readVisitEarlyDay());
    }
 
    /**
     * 开关与子开关状态(<b>只读</b>,不触发任何业务)。
     * <p>
     * 专为排查"为什么没发宣教"而设:把两个层级的开关、以及"实现是否装配"
     * 分别报出来,避免把"模块关了"误判成"没配长期宣教任务"。
     */
    @ApiOperation("查询宣教模块开关状态")
    @GetMapping("/status")
    public Map<String, Object> status() {
        Map<String, Object> m = new LinkedHashMap<>();
        boolean moduleEnabled = publicityFacade.isEnabled();
        m.put("moduleEnabled", moduleEnabled);
        m.put("switchKey", "publicity.enabled");
        m.put("switchValue", readConfig("publicity.enabled"));
        String helibrary = readConfig(HELIBRARY_SWITCH_KEY);
        m.put("inHospPublicitySwitchKey", HELIBRARY_SWITCH_KEY);
        m.put("inHospPublicitySwitchValue", helibrary);
        m.put("inHospPublicityOn", HELIBRARY_SWITCH_ON.equals(helibrary));
        m.put("note", "moduleEnabled=false 时两个生成端点会被拦截;"
                + "inHospPublicityOn=false 时入院宣教整块不执行,与模块开关无关");
        return m;
    }
 
    /**
     * 读 {@code sys_config: visit.early.day}(与出入院模块 {@code InhospVisitController} 同一做法)。
     * <p>
     * 取不到(没有实现 / 读库异常)时返回 {@code null} 并打 WARN ——
     * 原实现在这种情况下拿到的也是 {@code null},故语义一致。
     */
    private String readVisitEarlyDay() {
        return readConfig(VISIT_EARLY_DAY_KEY);
    }
 
    /**
     * 读任意 {@code sys_config} 键;没有实现或读失败时返回 {@code null} 并打 WARN。
     */
    private String readConfig(String key) {
        if (configServiceProvider == null) {
            return null;
        }
        try {
            IConfigService svc = configServiceProvider.stream().findFirst().orElse(null);
            if (svc == null) {
                log.warn("【宣教】容器里没有 IConfigService 实现,{} 按未配置(null)处理。", key);
                return null;
            }
            return svc.selectConfigByKey(key);
        } catch (Exception e) {
            log.warn("【宣教】读取 {} 失败,按未配置(null)处理:{}", key, e.toString());
            return null;
        }
    }
}