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 入口:宣教生成入口 + 开关/状态查询。 * *

为什么这些端点是"新增"的(与出入院模块的关键区别)

* 出入院模块的 controller 是把 ruoyi-admin 已有的端点搬进来(URL 一个不变); * 而宣教在本仓库里从来没有过 controller —— 它原本只是 * {@code PatMedInhospServiceImpl#dealOutHospInfo} 内部的两段调用 * (入院宣教 L331-409、药品宣教 L411-444),迁出后也只到 Service 层为止。 * 因此本类的 URL 是新定义的,前端若要手动触发需要接新地址; * 但"主流程自动触发"不受影响(那是 {@code RyTask} / {@code dealOutHospInfo} 走的门面调用)。 * *

端点清单

*
 * POST /smartor/publicity/dealInHospPublicity    入院宣教(科室 type=4 / 病区 type=5)
 * POST /smartor/publicity/dealMedicalPublicity   药品宣教(仅 sltd 站点,type=0)
 * GET  /smartor/publicity/status                 开关与子开关状态(只读、不受开关拦截)
 * 
* *

⚠️ 开关的层级(两个开关,别混)

*
    *
  1. {@code sys_config: publicity.enabled} —— 模块级开关,由 * {@link PublicityFacade#isEnabled()} 在运行时读(10 秒缓存)。 * 它只决定 {@link PublicityFacade} 是否把调用转发下去; * 关闭时两个生成端点返回 0 并打 WARN。
  2. *
  3. {@code sys_config: in.helibrary.switch} —— 入院宣教自身的业务开关, * 只有值等于 {@code "2"} 时入院宣教才真的执行。它在 * {@code PublicityServiceImpl} 内部读取,与模块开关无关。
  4. *
* 本类的 {@code /status} 会把两者分别如实报出来 —— 这两个开关是排查 * "为什么没发宣教"时最容易看错的地方。 * *

与出入院模块拦截器的一致性

* 两个生成端点由 {@code PublicityAvailabilityInterceptor} 在模块关闭时拦下并抛 * 明确的业务异常(而不是静默返回 0),与 {@code inhospvisit} 的做法一致; * {@code /status} 刻意不挂拦截器 —— 模块关掉时你更需要能查到状态。 */ @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"; /** * 本模块常驻门面。 *

* ⚠️ 注入门面而不是 {@code PublicityService}:门面永远存在, * 即使模块被开关关掉或没被打进应用,本 Controller 也不会启动失败, * 只是调用降级并打 WARN。 */ @Autowired private PublicityFacade publicityFacade; /** * 参数配置服务(ruoyi-common 的接口,实现在 ruoyi-system)。 *

* ⚠️ 用 {@code ObjectProvider} 惰性取:本模块只依赖 ruoyi-common, * 在独立部署/测试等"容器里没有实现"的场景下不能因此启动失败。 */ @Autowired private ObjectProvider configServiceProvider; /** * 入院宣教:科室维度(type=4)+ 病区维度(type=5)。 *

* 逐行对应原 {@code PatMedInhospServiceImpl#dealOutHospInfo} L331-409 的调用部分: * 原实现在主流程里直接调 {@code dealInHospPublicity(config)},此处只是把它暴露成端点。 *

* 返回处理记录数(与原实现的日志用值同源),便于手工触发时看到结果。 */ @ApiOperation("入院宣教(科室 type=4 / 病区 type=5)") @PostMapping("/dealInHospPublicity") public int dealInHospPublicity() { return publicityFacade.dealInHospPublicity(readVisitEarlyDay()); } /** * 药品宣教:按患者的 {@code medicalCode} 逐个药品编码匹配长期宣教任务(type=0)。 *

* 仅 {@code spring.profiles.active == "sltd"} 时执行,其它站点由 * {@code PublicityServiceImpl} 直接返回 0(站点判断不在本 Controller, * 与原实现位置一致)。 */ @ApiOperation("药品宣教(仅 sltd 站点,type=0)") @PostMapping("/dealMedicalPublicity") public int dealMedicalPublicity() { return publicityFacade.dealMedicalPublicity(readVisitEarlyDay()); } /** * 开关与子开关状态(只读,不触发任何业务)。 *

* 专为排查"为什么没发宣教"而设:把两个层级的开关、以及"实现是否装配" * 分别报出来,避免把"模块关了"误判成"没配长期宣教任务"。 */ @ApiOperation("查询宣教模块开关状态") @GetMapping("/status") public Map status() { Map 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} 同一做法)。 *

* 取不到(没有实现 / 读库异常)时返回 {@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; } } }