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)。 *

* 覆盖两件事: *

    *
  1. {@code message.sms.*} / {@code message.wechat.*} 能绑到对应的嵌套字段,未覆盖的项回落默认值;
  2. *
  3. 工厂按 key 路由确实是「调用到对应发送方」——这是四个站点发送链路里本地唯一能验证的一环。
  4. *
*/ 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"); } } }