package com.ruoyi.message.config;
import com.ruoyi.message.MessageChannel;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.SmsSender;
import com.ruoyi.message.sms.SmsSenderFactory;
import com.ruoyi.message.sms.provider.shiyi.ShiyiSmsUtil;
import com.ruoyi.message.wechat.WechatSendRequest;
import com.ruoyi.message.wechat.WechatSender;
import com.ruoyi.message.wechat.WechatSenderFactory;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* 「新增 {@code message.*} 前缀项」的绑定与工厂装配校验(不依赖 JUnit)。
*
* 覆盖两件事:
*
* - {@code message.sms.*} / {@code message.wechat.*} 能绑到对应的嵌套字段,未覆盖的项回落默认值;
* - 工厂按 key 路由确实是「调用到对应发送方」——这是四个站点发送链路里本地唯一能验证的一环。
*
*/
public final class MessagePrefixBindingCheck {
private static int failed = 0;
private MessagePrefixBindingCheck() {
}
public static void main(String[] args) throws Exception {
String[] yaml = {
"message:",
" sms:",
" shiyi:",
" ws-url: http://his.example.com:7790/MediInfoHis.svc",
" default-caozuoydm: OPR001",
" messaging-insert:",
" ws-url: http://msg.example.com:8081/MessagingInsert.asmx",
" wechat:",
" shiyi:",
" title: 自定义标题",
" xinhua:",
" service: custom.service",
};
ConfigurableEnvironment environment = environment(yaml);
Binder binder = new Binder(ConfigurationPropertySources.get(environment));
// ---------- 1) 前缀项绑定 ----------
MessageProperties.Sms sms = binder
.bind(ConfigurationPropertyName.of("message.sms"), Bindable.of(MessageProperties.Sms.class))
.orElseGet(MessageProperties.Sms::new);
MessageProperties.Wechat wechat = binder
.bind(ConfigurationPropertyName.of("message.wechat"), Bindable.of(MessageProperties.Wechat.class))
.orElseGet(MessageProperties.Wechat::new);
check("message.sms.shiyi.ws-url", "http://his.example.com:7790/MediInfoHis.svc",
sms.getShiyi().getWsUrl());
check("message.sms.shiyi.default-caozuoydm", "OPR001", sms.getShiyi().getDefaultCaozuoydm());
check("message.sms.messaging-insert.ws-url", "http://msg.example.com:8081/MessagingInsert.asmx",
sms.getMessagingInsert().getWsUrl());
check("message.wechat.shiyi.title", "自定义标题", wechat.getShiyi().getTitle());
check("message.wechat.xinhua.service", "custom.service", wechat.getXinhua().getService());
// 同组未覆盖项应回落默认值
check("message.wechat.shiyi.url(回落默认)", "http://192.200.54.14:5003/sfjk/SendMessage",
wechat.getShiyi().getUrl());
check("message.sms.shiyi.ws-url 默认值不丢", "http://192.178.0.104:7790/MediInfoHis.svc",
new MessageProperties.Sms().getShiyi().getWsUrl());
// ---------- 2) 工厂路由确实调到对应发送方 ----------
SmsSenderFactory smsFactory = new SmsSenderFactory(Collections.singletonList(new CountingSmsSender()));
WechatSenderFactory wechatFactory = new WechatSenderFactory(Collections.singletonList(new CountingWechatSender()));
SendResult smsResult = smsFactory.send("probe-channel", SmsSendRequest.of("13500000001", "内容"));
check("短信工厂按 key 路由到对应发送方", "true", String.valueOf(smsResult.isOk()));
SendResult wechatResult = wechatFactory.send("probe-channel", new WechatSendRequest());
check("公众号工厂按 key 路由到对应发送方", "true", String.valueOf(wechatResult.isOk()));
try {
smsFactory.send("不存在的渠道", SmsSendRequest.of("13500000001", "内容"));
check("未知渠道应抛 SendException", "抛异常", "没抛");
} catch (com.ruoyi.message.SendException e) {
check("未知渠道应抛 SendException", "抛异常", "抛异常");
}
// ---------- 3) ShiyiSmsUtil 仍能正常构造(保底回调/参数校验行为不丢) ----------
ShiyiSmsUtil shiyiSmsUtil = new ShiyiSmsUtil(new MessageProperties(new RootMessageProperties()));
boolean paramChecked = false;
try {
shiyiSmsUtil.sendSms(null);
} catch (IllegalArgumentException e) {
paramChecked = "短信请求不能为空".equals(e.getMessage());
}
check("ShiyiSmsUtil 入参校验仍生效", "true", String.valueOf(paramChecked));
System.out.println();
if (failed == 0) {
System.out.println("===== message.* 绑定与工厂路由校验全部通过 =====");
} else {
System.out.println("===== message.* 绑定与工厂路由校验失败 " + failed + " 项 =====");
System.exit(1);
}
}
private static ConfigurableEnvironment environment(String[] inlineYaml) throws IOException {
ConfigurableEnvironment environment = new StandardEnvironment();
environment.getPropertySources()
.remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
environment.getPropertySources()
.remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
org.springframework.boot.env.YamlPropertySourceLoader loader =
new org.springframework.boot.env.YamlPropertySourceLoader();
List> sources = loader.load("inline.yml",
new org.springframework.core.io.ByteArrayResource(
String.join("\n", inlineYaml).getBytes(java.nio.charset.StandardCharsets.UTF_8)));
for (PropertySource> source : sources) {
environment.getPropertySources().addLast(source);
}
return environment;
}
private static void check(String key, String expected, String actual) {
boolean ok = expected.equals(actual);
if (!ok) {
failed++;
}
System.out.println((ok ? "[OK] " : "[FAIL] ") + key + " 期望=[" + expected + "] 实际=[" + actual + "]");
}
/** 探针短信发送方 */
static class CountingSmsSender implements SmsSender {
@Override
public String key() {
return "probe-channel";
}
@Override
public SendResult send(SmsSendRequest request) {
return SendResult.success(key(), MessageChannel.SMS, "called");
}
}
/** 探针公众号发送方 */
static class CountingWechatSender implements WechatSender {
@Override
public String key() {
return "probe-channel";
}
@Override
public SendResult send(WechatSendRequest request) {
return SendResult.success(key(), MessageChannel.WECHAT, "called");
}
}
}