liusheng
2026-10-10 0afac42fb2e9d6e5291304dd0044ce3042b45d2b
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
package com.ruoyi.message.sms.provider.ls;
 
import com.alibaba.fastjson2.JSON;
import com.google.gson.Gson;
import com.ruoyi.common.utils.OkHttpExample;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.message.MessageChannel;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.AbstractSmsSender;
import com.ruoyi.message.sms.SmsSendRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
 
/**
 * 丽水短信发送方({@code ls} 通道,丽水 13 家医院)。
 * <p>
 * 对接特征(与其他通道差异最大的一个):
 * <ul>
 *   <li>报文为 {@code XiaoXiTou + YeWuXX.DuanXinXX} 的嵌套 JSON;</li>
 *   <li>需要先用 {@code LyraTokenUtil} 换 token,再以 header 传 token;</li>
 *   <li>机构地址/租户/发送人存在 {@code sys_config} 表的 {@code ls.msg.info} 里(按机构查询);</li>
 *   <li>成功判定为响应体 {@code Code == "0"}。</li>
 * </ul>
 * 代码来源:{@code RyTask.ls 分支} + {@code RyTask.getDXCode}(行为保持不变)。
 */
@Slf4j
@Component
public class LishuiSmsSender extends AbstractSmsSender {
 
    /**
     * 渠道 key,与 {@code spring.profiles.active=ls} 对应
     */
    public static final String KEY = "ls";
 
    private final ObjectProvider<LishuiMessageConfigProvider> configProvider;
 
    public LishuiSmsSender(ObjectProvider<LishuiMessageConfigProvider> configProvider) {
        this.configProvider = configProvider;
    }
 
    @Override
    public String key() {
        return KEY;
    }
 
    @Override
    public String description() {
        return "丽水短信(13家医院,token + 机构级配置)";
    }
 
    @Override
    protected SendResult doSend(SmsSendRequest request) throws Exception {
        LishuiMessageConfig config = resolveConfig(request.getOrgid());
        if (config == null || StringUtils.isEmpty(config.getAddress())) {
            //与线上行为一致:该机构没有配置短信地址,视为发送失败
            return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败,该机构没有配置短信地址", null);
        }
 
        String token = resolveToken(request.getOrgid(), config);
        Map<String, Object> body = buildBody(request, config);
 
        log.info("【{}】丽水短信发送,地址:{},参数:{},机构ID:{}",
                KEY, config.getAddress(), request, request.getOrgid());
        String result = OkHttpExample.sendPostRequest(config.getAddress(), new Gson().toJson(body), token);
        log.info("【{}】丽水短信发送结果:{}", KEY, result);
 
        if (StringUtils.isEmpty(result)) {
            return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败", result);
        }
        Map<String, Object> textParam = JSON.parseObject(result, Map.class);
        Object codeObj = textParam == null ? null : textParam.get("Code");
        String code = codeObj == null ? null : codeObj.toString();
        if ("0".equals(code)) {
            return SendResult.success(KEY, MessageChannel.SMS, result).withCode(code);
        }
        return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败", result).withCode(code);
    }
 
    /**
     * 优先使用业务模块注册的 {@link LishuiMessageConfigProvider}(查 sys_config),
     * 否则退化为代码内静态配置。
     */
    private LishuiMessageConfig resolveConfig(String orgid) {
        LishuiMessageConfigProvider provider = configProvider.getIfAvailable();
        if (provider != null) {
            LishuiMessageConfig config = provider.getConfig(orgid);
            if (config != null) {
                if (StringUtils.isEmpty(config.getOrgid())) {
                    config.setOrgid(orgid);
                }
                return config;
            }
        }
        LishuiMessageConfig fallback = LishuiStaticConfig.fallback(orgid);
        return fallback;
    }
 
    private String resolveToken(String orgid, LishuiMessageConfig config) {
        if (StringUtils.isEmpty(config.getTokenUrl())) {
            return null;
        }
        LishuiMessageConfigProvider provider = configProvider.getIfAvailable();
        if (provider == null) {
            log.warn("【{}】未找到 LishuiMessageConfigProvider 实现,无法换取 token,将以空 token 调用", KEY);
            return null;
        }
        return provider.getToken(orgid, config.getTokenUrl());
    }
 
    /**
     * 组装丽水报文:{@code XiaoXiTou + YeWuXX.DuanXinXX}(与 {@code RyTask.getDXCode} 完全一致)
     */
    private Map<String, Object> buildBody(SmsSendRequest request, LishuiMessageConfig config) {
        Map<String, Object> headerMap = new LinkedHashMap<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        headerMap.put("XiaoXiID", IdUtils.simpleUUID());
        headerMap.put("FaSongSJ", sdf.format(new Date()));
        headerMap.put("ZuHuID", config.getZuHuId());
        headerMap.put("ZuHuMC", config.getZuHuMc());
        headerMap.put("FaSongJGID", config.getOrgid());
        headerMap.put("FaSongJGMC", config.getZuHuMc());
        headerMap.put("FaSongXTJC", "SUIFANGXT");
        headerMap.put("FaSongXTMC", "随访系统");
        headerMap.put("FaSongFCSJC", "ZJHES");
        headerMap.put("XiaoXiLX", "FZ_PT_DuanXinFS");
        headerMap.put("XiaoXiMC", "短信发送");
 
        Map<String, Object> duanXinXX = new LinkedHashMap<>();
        duanXinXX.put("ShouJiHM", request.getPhone());
        duanXinXX.put("FaSongNR", request.getContent());
        duanXinXX.put("FaSongRID", config.getSendPersonId());
        duanXinXX.put("FaSongRXM", config.getSendPersonName());
 
        Map<String, Object> yeWuXX = new LinkedHashMap<>();
        yeWuXX.put("DuanXinXX", duanXinXX);
 
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("XiaoXiTou", headerMap);
        body.put("YeWuXX", yeWuXX);
        return body;
    }
}