package com.ruoyi.message.sms.provider.xh;
import com.ruoyi.common.utils.HttpUtil;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.message.MessageChannel;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.config.MessageProperties;
import com.ruoyi.message.config.XhsmsConfig;
import com.ruoyi.message.config.RootMessageProperties;
import com.ruoyi.message.sms.AbstractSmsSender;
import com.ruoyi.message.sms.SmsSendRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 新华短信发送方({@code xh} 通道,通用短信网关)。
*
* 对接特征:HTTP POST 表单 {@code action/account/password/mobile/content/extno/rt},无签名。
*
* ⚠️ 现有三个入口对同一个网关的调用方式并不一致,且其中两个是坏的。为满足「按站点原样封装、
* 不改线上行为」的要求,这里把三种模式都保留为独立方法,由调用方按原站点选择:
*
* - {@link #sendJsonLegacy} —— 等价 {@code RyTask}:拼好表单参数却发送 {@code new Gson().toJson(req)}
* ({@code req} 是空 map,实际发出 {@code {}}),且把 {@code Boolean} 当 {@code String} 返回值,
* 必然触发 NPE 被异常分支吞掉。结果:**新华站点长期任务短信实际发不出去**;
* - {@link #sendForm} —— 等价 {@code RedisMqReceiver}:发送 URL 编码后的表单字符串(正确),
* 但原实现忽略了返回值,代码里固定按成功处理;
* - {@link #sendJson} —— 等价 {@code ServiceSubtaskServiceImpl.smsSubTask} / {@code SmsController}:
* 发送 {@code Gson.toJson(map)} 的 JSON。
*
* TODO 待确认:以上三种写法哪一种才是新华网关真正接受的报文格式(很可能是 {@link #sendForm}),
* 确认后应统一为一种并删除其余两种。
*/
@Slf4j
@Component
public class XinHuaSmsSender extends AbstractSmsSender {
/**
* 渠道 key,与 {@code spring.profiles.active=xh} 对应
*/
public static final String KEY = "xh";
private final MessageProperties properties;
public XinHuaSmsSender(MessageProperties properties) {
this.properties = properties;
}
@Override
public String key() {
return KEY;
}
@Override
public String description() {
return "新华短信(通用网关,表单/JSON 两种报文)";
}
@Override
protected SendResult doSend(SmsSendRequest request) {
//默认按 RedisMqReceiver 的(正确)表单模式发送
return sendForm(request);
}
/**
* 模式一:表单字符串(等价 {@code RedisMqReceiver},推荐)
*/
public SendResult sendForm(SmsSendRequest request) {
XhsmsConfig cfg = properties.getSms().getXhsms();
String encodedParams = buildEncodedParams(request, cfg);
log.info("【{}】新华短信发送(表单),地址:{},参数:{}", KEY, cfg.getPath(), encodedParams);
HttpUtils.sendPost(cfg.getPath(), encodedParams);
//与线上行为一致:原实现未使用返回值,固定按成功处理
return SendResult.success(KEY, MessageChannel.SMS, encodedParams);
}
/**
* 模式二:JSON 报文(等价 {@code smsSubTask} / {@code SmsController})
*/
public SendResult sendJson(SmsSendRequest request) {
XhsmsConfig cfg = properties.getSms().getXhsms();
Map params = buildParams(request, cfg);
String json = com.alibaba.fastjson.JSON.toJSONString(params);
log.info("【{}】新华短信发送(JSON),地址:{},参数:{}", KEY, cfg.getPath(), json);
Boolean ok = HttpUtil.postJsonRequest(cfg.getPath(), json);
if (Boolean.TRUE.equals(ok)) {
return SendResult.success(KEY, MessageChannel.SMS, json);
}
return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败", json);
}
/**
* 模式三:兼容 {@code RyTask} 的旧实现(发送空 JSON,必然失败)。
*
* 保留此方法是为了让 {@code RyTask} 的改造可以做到「行为一字不差」;确认网关格式后应删除。
*/
public SendResult sendJsonLegacy(SmsSendRequest request) {
XhsmsConfig cfg = properties.getSms().getXhsms();
//原实现:Map req = new HashMap<>(); ...(HttpUtils.sendPost(xhsmsPath, new Gson().toJson(req));
// Gson 序列化空 map 的结果即 "{}"
String emptyJson = "{}";
log.info("【{}】新华短信发送(RyTask 旧实现,发送空报文),地址:{}", KEY, cfg.getPath());
Boolean result = HttpUtils.sendPost(cfg.getPath(), emptyJson);
if (Boolean.TRUE.equals(result)) {
return SendResult.success(KEY, MessageChannel.SMS, emptyJson);
}
return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败", emptyJson);
}
private Map buildParams(SmsSendRequest request, XhsmsConfig cfg) {
Map params = new LinkedHashMap<>();
params.put("action", "send");
params.put("account", cfg.getAccount());
params.put("password", cfg.getPwd());
params.put("mobile", request.getPhone());
params.put("content", request.getContent());
params.put("extno", cfg.getExtno());
params.put("rt", "json");
return params;
}
private String buildEncodedParams(SmsSendRequest request, XhsmsConfig cfg) {
StringBuilder encodedParams = new StringBuilder();
for (Map.Entry entry : buildParams(request, cfg).entrySet()) {
if (encodedParams.length() > 0) {
encodedParams.append("&");
}
encodedParams.append(encode(entry.getKey())).append("=").append(encode(entry.getValue()));
}
return encodedParams.toString();
}
private String encode(String value) {
try {
return URLEncoder.encode(value == null ? "" : value, "UTF-8");
} catch (Exception e) {
return value;
}
}
}