liusheng
2026-10-10 0afac42fb2e9d6e5291304dd0044ce3042b45d2b
短信模块提交
已删除16个文件
已重命名34个文件
已修改18个文件
已添加144个文件
已复制1个文件
7209 ■■■■■ 文件已修改
pom.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/component/RedisMqReceiver.java 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/SmsController.java 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSLTDHealthcareRecordController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceTelInfoController.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ShiyiSmsController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/tools/BaseSmsaccountController.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/test/WeChatService.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/dx/MessageSend.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/sms/smsUtils.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/pom.xml 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/MessageChannel.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/SendException.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/SendResult.java 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/config/AliyunSmsConfig.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/config/MessageConfigValidator.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/config/MessageProperties.java 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/config/MtSmsConfig.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/config/RootMessageProperties.java 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/config/XhsmsConfig.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/AbstractSmsSender.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/SmsSendRequest.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/SmsSender.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/SmsSenderFactory.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/aliyun/AliYunSmsApi.java 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/aliyun/AliYunSmsSender.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/hzszlyy/ShiyiSmsApi.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/hzszlyy/ShiyiSmsSender.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/ls/LishuiMessageConfig.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/ls/LishuiMessageConfigProvider.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/ls/LishuiSmsSender.java 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/ls/LishuiStaticConfig.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/MessagingInsertSmsApi.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/MessagingInsertSmsSender.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsert.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertLocator.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap12Stub.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap_BindingStub.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap_PortType.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/nhfy/MtSubmitSmResp.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/nhfy/MtSubmitSmUtil.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/nhfy/NhfySmsSender.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/shiyi/ShiyiHisSmsSender.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsRequest.java 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsResponse.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsUtil.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/SltdResponseParser.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/SltdSmsSender.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/Hello.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/HelloResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GReply.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GReplyResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GReport.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GReportResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GSearchNumber.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GSearchNumberResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GSend.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GSendResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/MessageSend.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/ObjectFactory.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetNumber.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetNumberResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetTemplateList.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetTemplateListResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetUserList.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetUserListResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSSaveContent.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSSaveContentResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSVerify.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyPrivate.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyPrivateResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyResponse.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SmsService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SmsServiceSoap.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/package-info.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/xh/XinHuaSmsSender.java 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatMpConfig.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatMpConfigProvider.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatSendRequest.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatSender.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatSenderFactory.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/hzszlyy/ShiyiMpApi.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/hzszlyy/ShiyiMpSender.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/sdk/WxMpSdkSender.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/xh/XinHuaMpApi.java 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/xh/XinHuaMpSender.java 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/xh/XinHuaWXReq.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/java/com/ruoyi/message/wechat/support/WxMpUtils.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/main/resources/wsdl/MessagingInsert.asmx 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/test/java/com/ruoyi/message/config/MessagePrefixBindingCheck.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/test/java/com/ruoyi/message/config/MessagePropertiesBindingCheck.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/test/java/com/ruoyi/message/config/PerSiteBindingCheck.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/src/test/resources/message-binding-check.yml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/MessageChannel.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/SendException.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/SendResult.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/AliyunSmsConfig.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/MessageConfigValidator.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$MessagingInsert.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$ShiyiSms.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$ShiyiWechat.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$Sms.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$Wechat.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$Xinhua.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/MtSmsConfig.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/RootMessageProperties.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/config/XhsmsConfig.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/AbstractSmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/SmsSendRequest.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/SmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/SmsSenderFactory.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/aliyun/AliYunSmsApi.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/aliyun/AliYunSmsSender$1.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/aliyun/AliYunSmsSender$SendSmsRequestAdapter.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/aliyun/AliYunSmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/hzszlyy/ShiyiSmsApi.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/hzszlyy/ShiyiSmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/ls/LishuiMessageConfig.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/ls/LishuiMessageConfigProvider.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/ls/LishuiSmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/ls/LishuiStaticConfig.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/MessagingInsertSmsApi.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/MessagingInsertSmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsert.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertLocator.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap12Stub.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap_BindingStub.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap_PortType.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/nhfy/MtSubmitSmResp.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/nhfy/MtSubmitSmUtil.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/nhfy/NhfySmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/shiyi/ShiyiHisSmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsRequest.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsUtil.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/SltdResponseParser.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/SltdSmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/Hello.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/HelloResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GReply.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GReplyResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GReport.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GReportResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GSearchNumber.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GSearchNumberResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GSend.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GSendResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/MessageSend.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/ObjectFactory.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetNumber.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetNumberResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetTemplateList.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetTemplateListResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetUserList.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetUserListResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSSaveContent.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSSaveContentResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSVerify.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyPrivate.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyPrivateResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyResponse.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SmsService.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SmsServiceSoap.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/package-info.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/xh/XinHuaSmsSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatMpConfig.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatMpConfigProvider.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatSendRequest.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatSenderFactory.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/hzszlyy/ShiyiMpApi.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/hzszlyy/ShiyiMpSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/sdk/WxMpSdkSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/xh/XinHuaMpApi.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/xh/XinHuaMpSender.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/xh/XinHuaWXReq.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/com/ruoyi/message/wechat/support/WxMpUtils.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/message-binding-check.yml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/classes/wsdl/MessagingInsert.asmx 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/maven-archiver/pom.properties 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/ruoyi-message-3.8.5.jar 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/test-classes/com/ruoyi/message/config/MessagePropertiesBindingCheck.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/test-classes/com/ruoyi/message/config/PerSiteBindingCheck.class 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-message/target/test-classes/message-binding-check.yml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java 343 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/WebServiceClient/MessagingInsertTestCase.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SmsParamServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SmsRecordsServiceImpl.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/WebServiceClient/MessagingInsert.asmx 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/WebServiceClient/MessagingInsert.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/WebServiceClient/MessagingInsertLocator.java 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/WebServiceClient/MessagingInsertSoap12Stub.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/WebServiceClient/MessagingInsertSoap_BindingStub.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/WebServiceClient/MessagingInsertSoap_PortType.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/WebServiceClient/MessagingInsertTestCase.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/common/MtSubmitSmUtil.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/MtSubmitSmResp.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ShiyiSmsRequest.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ShiyiSmsResponse.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/XinHuaWXReq.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/ServicPhoneServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/SubtaskSmsServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/XHGatherPatArchiveServiceImpl.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -185,6 +185,13 @@
                <version>${ruoyi.version}</version>
            </dependency>
            <!-- æ¶ˆæ¯å‘送(短信/微信公众号)模块 -->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-message</artifactId>
                <version>${ruoyi.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
@@ -195,6 +202,7 @@
        <module>ruoyi-quartz</module>
        <module>ruoyi-generator</module>
        <module>ruoyi-common</module>
        <module>ruoyi-message</module>
        <module>smartor</module>
    </modules>
    <packaging>pom</packaging>
ruoyi-admin/pom.xml
@@ -87,6 +87,11 @@
            <version>3.8.5</version>
            <scope>compile</scope>
        </dependency>
        <!-- æ¶ˆæ¯å‘送(短信/微信公众号)模块 -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-message</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/net.glxn.qrgen/javase -->
        <dependency>
            <groupId>net.glxn.qrgen</groupId>
ruoyi-admin/src/main/java/com/ruoyi/web/component/RedisMqReceiver.java
@@ -5,23 +5,18 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.dx.MessageSend;
import com.ruoyi.common.enums.MsgLSEnum;
import com.ruoyi.common.enums.ServiceFromEnum;
import com.ruoyi.common.enums.WxGZHEnum;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.common.utils.sign.Md5Utils;
import com.ruoyi.common.utils.sms.smsUtils;
import com.ruoyi.message.MessageChannel;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.SmsSenderFactory;
import com.ruoyi.message.wechat.WechatSendRequest;
import com.ruoyi.message.wechat.WechatSenderFactory;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.smartor.common.LSHospTokenUtil;
import com.smartor.common.MtSubmitSmUtil;
import com.smartor.domain.*;
import com.smartor.domain.entity.ServiceSubtaskEntity;
import com.smartor.mapper.*;
@@ -38,17 +33,11 @@
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static cn.hutool.core.convert.Convert.toHex;
@Slf4j
@Component//监听此队列
@@ -100,26 +89,23 @@
    @Autowired
    IBaseSmsaccountService baseSmsaccountService;
    @Value("${xhsmsPath}")
    private String xhsmsPath;
    @Value("${xhsmsAccount}")
    private String xhsmsAccount;
    @Value("${xhsmsPwd}")
    private String xhsmsPwd;
    // è™šæ‹ŸæŽ¥å…¥ç 
    @Value("${xhsmsjrm}")
    private String xhsmsjrm;
    @Value("${server.port}")
    private String port;
    @Value("${spring.profiles.active}")
    private String active;
    /**
     * æ¶ˆæ¯å‘送方工厂(短信/微信公众号)。
     * <p>
     * åŽŸæ¥çŸ­ä¿¡ä¸Žå…¬ä¼—å·çš„å‘é€é€»è¾‘åœ¨æœ¬ç±»é‡Œç”¨ {@code if (active.equals("xx"))} ç¡¬åˆ†æ”¯äº†çº¦ 300 è¡Œï¼Œ
     * çŽ°å·²æ”¶å£åˆ° {@code ruoyi-message} æ¨¡å—,这里只负责按 {@code active} è·¯ç”±ã€‚
     */
    @Autowired
    private SmsSenderFactory smsSenderFactory;
    @Autowired
    private MtSubmitSmUtil mtSubmitSmUtil;
    private WechatSenderFactory wechatSenderFactory;
    @Value("${spring.profiles.active}")
    private String active;
    @Autowired
    private ISysConfigService configService;
@@ -426,120 +412,11 @@
                        iServiceOutPathService.updateServiceOutPath(serviceOutPath);
                    }
                    //短信还需要模板
                    String s = null;
                    if (active.equals("xh")) {
                        //新华的短信发送方式
                        Map<String, String> map2 = new HashMap<String, String>();
                        map2.put("action", "send");
                        map2.put("account", xhsmsAccount);
                        map2.put("password", xhsmsPwd);
                        map2.put("mobile", sendMagParam.getPhone());
                        map2.put("content", sendMagParam.getContent());
                        map2.put("extno", xhsmsjrm);
                        map2.put("rt", "json");
                        StringBuilder encodedParams = new StringBuilder();
                        for (Map.Entry<String, String> entry : map2.entrySet()) {
                            if (encodedParams.length() > 0) {
                                encodedParams.append("&");
                            }
                            // å¯¹é”®å’Œå€¼è¿›è¡ŒURL编码
                            String encodedKey = URLEncoder.encode(entry.getKey(), "UTF-8");
                            String encodedValue = URLEncoder.encode(entry.getValue(), "UTF-8");
                            encodedParams.append(encodedKey).append("=").append(encodedValue);
                        }
                        Boolean isSuccess = HttpUtil.postJsonRequest(xhsmsPath, encodedParams.toString());
                        if (isSuccess) s = "true";
                    } else if (active.equals("ls")) {
                        //丽水的短信发送方式
                        SysConfig config = new SysConfig();
                        config.setConfigKey("ls.msg.info");
                        config.setOrgid(serviceSubtask.getOrgid());
                        List<SysConfig> sysConfigs = configService.selectConfigList(config);
                        Map<String, String> msgInfo = new HashMap<>();
                        if (CollectionUtils.isNotEmpty(sysConfigs)) {
                            String configValue = sysConfigs.get(0).getConfigValue();
                            ObjectMapper objectMapper = new ObjectMapper();
                            if (StringUtils.isNotEmpty(configValue)) {
                                msgInfo = objectMapper.readValue(configValue, Map.class);
                            }
                        }
                        log.info("-----------map的值为:{}", msgInfo);
                        if (ObjectUtils.isNotEmpty(msgInfo) && StringUtils.isEmpty(msgInfo.get("address"))) {
                            serviceSubtask.setCurrentPreachform(sendPreachform);
                            serviceSubtask.setVisitTime(getNextVisitTime(serviceSubtask.getId(), serviceSubtask.getTaskid(), serviceSubtask.getVisitTime(), serviceSubtask.getCurrentPreachform()));
                            setFailPreachForm(serviceSubtask, descByCode, "短信发送失败,该机构没有配置短信地址", "5");
                            serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
                            continue;
                        }
                        String token = LSHospTokenUtil.getToken(serviceSubtask.getOrgid(), msgInfo.get("tokenUrl"));
                        String dxCode = getDXCode(msgInfo.get("address"), sendMagParam.getPhone(), sendMagParam.getContent(), msgInfo.get("sendPersonId"), msgInfo.get("sendPersonName"), MsgLSEnum.getHeaderByCode(msgInfo), token);
                        ObjectMapper objectMapper = new ObjectMapper();
                        Map<String, Object> textParam = objectMapper.readValue(dxCode, Map.class);
                        String code = textParam.get("Code").toString();
                        if (code.equals("0")) {
                            s = "true";
                        } else {
                            setFailPreachForm(serviceSubtask, sendPreachform, "短信发送失败", "5");
                            throw new BaseException("短信发送失败");
                        }
                    } else if (active.equals("sltd") || active.equals("nhfy")) {
                        //省立同德的短信发送方式
                        String data = null;
                        Map<String, Object> smsResult = null;
                        if (active.equals("sltd")) {
                            data = MessageSend.sendMsg(sendMagParam.getContent(), "6", sendMagParam.getPhone(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
                            if (StringUtils.isNotEmpty(data)) {
                                String[] pairs = data.split("&");
                                String resultVal = null;
                                for (String pair : pairs) {
                                    String[] kv = pair.split("=", 2);
                                    if ("result".equals(kv[0])) {
                                        resultVal = kv.length > 1 ? kv[1] : null;
                                        break;
                                    }
                                }
                                if ("0".equals(resultVal)) {
                                    s = "true";
                                }
                            } else {
                                setFailPreachForm(serviceSubtask, sendPreachform, "短信发送失败: " + data, "5");
                                throw new BaseException("短信发送失败");
                            }
                        } else if (active.equals("nhfy")) {
                            smsResult = mtSubmitSmUtil.submitSm(sendMagParam.getPhone(), sendMagParam.getContent());
                            if (smsResult != null && smsResult.get("result") != null && smsResult.get("result").toString().equals("0")) {
                                s = "true";
                            } else {
                                setFailPreachForm(serviceSubtask, sendPreachform, "短信发送失败", "5");
                                throw new BaseException("短信发送失败");
                            }
                        }
                    } else if (active.equals("hzszlyy")) {
                        //市一
                        long timestamp = new Date().getTime();
                        String sign = Md5Utils.hash(xhsmsAccount + timestamp + Md5Utils.hash(xhsmsPwd));
                        String content = sendMagParam.getContent();
                        String jsonMsg = "{" + "\"userName\": \"" + xhsmsAccount + "\"," + "\"content\": \"" + content + "\"," + "\"phoneList\": [" + sendMagParam.getPhone() + "]," + "\"timestamp\": " + timestamp + "," + "\"sign\": \"" + sign + "\"" + "}";
                        Boolean b = HttpUtil.postJsonRequest(xhsmsPath, jsonMsg);
                        if (b) s = "true";
                        log.info("------市一短信发送的值为:{}", s);
                        if (StringUtils.isNotEmpty(s)) {
                            JSONObject jsonObject = JSONObject.parseObject(s);
                            Object code = jsonObject.get("code");
                            if (code.equals(0)) {
                                s = "true";
                            } else {
                                setFailPreachForm(serviceSubtask, sendPreachform, "短信发送失败", "5");
                                throw new BaseException("短信发送失败");
                            }
                        }
                    }
                    if (s.equals("true")) {
                    //短信发送:各站点对接方式差异较大(报文/签名/token/返回判定都不同),
                    //已收口到 ruoyi-message æ¨¡å—,这里只负责组装入参并路由
                    log.info("----active的值为:{}", active);
                    SendResult smsSendResult = sendSmsByActive(serviceSubtask, sendMagParam);
                    if (smsSendResult.isOk()) {
                        //在子任务表里记录一下
                        setFailPreachForm(serviceSubtask, sendPreachform, "短信发送成功", "2");
                        //任务发送记录
@@ -553,10 +430,10 @@
                        serviceSubtaskRecord.setResult("success");
                        serviceSubtaskRecord.setRemark("短信发送成功");
                        serviceSubtaskRecordService.insertServiceSubtaskRecord(serviceSubtaskRecord);
                        log.error("serviceSubtaskRecord保存成功了吗?:{}", serviceSubtaskRecord);
                    } else {
                        //在子任务表里记录一下
                        setFailPreachForm(serviceSubtask, sendPreachform, "短信发送失败", "5");
                        continue;
                    }
                } catch (Exception e) {
                    ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord();
@@ -668,58 +545,28 @@
                        serviceOutPath.setUrl(url);
                        iServiceOutPathService.updateServiceOutPath(serviceOutPath);
                    }
                    if (StringUtils.isEmpty(patArchive.getPatidHis())) {
                        serviceSubtask.setCurrentPreachform(sendPreachform);
                        serviceSubtask.setVisitTime(getNextVisitTime(serviceSubtask.getId(), serviceSubtask.getTaskid(), serviceSubtask.getVisitTime(), serviceSubtask.getCurrentPreachform()));
                        serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
                        //失败记录
                        setFailPreachForm(serviceSubtask, sendPreachform, "公众号发送失败,his系统的患者id为空", "5");
                        continue;
                    }
                    //获取微信公众号请求信息根据机构ID
                    //机构配置校验(WxGZHEnum)与 his æ‚£è€…id æ ¡éªŒå·²æ”¶å£åˆ°
                    //ruoyi-message çš„ WechatMpConfigProvider / XinHuaMpSender,调用点不再重复校验
                    log.error("获取微信公众号请求信息根据机构ID:{}", patArchive.getOrgid());
                    List<String> wxqqxx = WxGZHEnum.getDescByCode(patArchive.getOrgid());
                    //市一医院 ä¸æ ¡éªŒ
                    if (CollectionUtils.isEmpty(wxqqxx) || wxqqxx.size() < 4) {
                        serviceSubtask.setCurrentPreachform(sendPreachform);
                        serviceSubtask.setVisitTime(getNextVisitTime(serviceSubtask.getId(), serviceSubtask.getTaskid(), serviceSubtask.getVisitTime(), serviceSubtask.getCurrentPreachform()));
                        serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
                        //失败记录
                        setFailPreachForm(serviceSubtask, sendPreachform, "该机构的公众号配置信息不全,无法通过公众号发送", "5");
                        continue;
                    }
                    String wxCode = "";
                    if (active.equals("hzszlyy")) {
                        wxCode = smsUtils.sendChat(url, patArchive.getTelcode(), serviceSubtask.getSfzh());
                        log.info("wxCode的值为:{}", wxCode);
                    if (!"xh".equals(active)) {
                        //市一:走自有公众号网关(不做机构配置校验、his患者id校验,与原实现一致)
                        sendWechatByActive(serviceSubtask, patArchive, url);
                    } else {
                        wxCode = getWXCode(serviceSubtask.getSfzh(), url, serviceSubtask.getTaskName(), serviceSubtask.getTaskName(), patArchive.getTelcode(), patArchive.getName(), patArchive.getPatidHis(), wxqqxx);
                        log.error("wxCode的值为:{}", wxCode);
                        Map<String, Object> map = JSONObject.parseObject(wxCode, Map.class);
                        log.error("map的值为:{}", map);
                        if (!map.isEmpty()) {
                            serviceSubtask.setResult("success");
                            serviceSubtask.setSendstate(3L);
                            log.error("aBoolean的值为:{}", aBoolean);
                            if (map.get("succ").toString().equals("true")) {
                                aBoolean = true;
                            }
                            if (aBoolean == false) {
                                ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord();
                                serviceSubtaskRecord.setTaskid(serviceSubtask.getTaskid().toString());
                                serviceSubtaskRecord.setSubtaskId(serviceSubtask.getId());
                                serviceSubtaskRecord.setUuid(UUID.randomUUID().toString());
                                serviceSubtaskRecord.setTasktype(serviceSubtask.getType());
                                serviceSubtaskRecord.setPreachform("5");
                                serviceSubtaskRecord.setStartTime(System.currentTimeMillis());
                                serviceSubtaskRecord.setRemark(ObjectUtils.isNotEmpty(map.get("msg")) ? map.get("msg").toString() : null);
                                serviceSubtaskRecordService.insertServiceSubtaskRecord(serviceSubtaskRecord);
                                //在子任务表里记录一下
                                setFailPreachForm(serviceSubtask, sendPreachform, "公众号发送失败", "5");
                                continue;
                            }
                        //新华:his患者id校验失败即记录并进入下一种发送方式
                        SendResult wechatSendResult = sendWechatByActive(serviceSubtask, patArchive, url);
                        if (!wechatSendResult.isOk()) {
                            ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord();
                            serviceSubtaskRecord.setTaskid(serviceSubtask.getTaskid().toString());
                            serviceSubtaskRecord.setSubtaskId(serviceSubtask.getId());
                            serviceSubtaskRecord.setUuid(UUID.randomUUID().toString());
                            serviceSubtaskRecord.setTasktype(serviceSubtask.getType());
                            serviceSubtaskRecord.setPreachform("5");
                            serviceSubtaskRecord.setStartTime(System.currentTimeMillis());
                            serviceSubtaskRecord.setRemark(wechatSendResult.getMessage());
                            serviceSubtaskRecordService.insertServiceSubtaskRecord(serviceSubtaskRecord);
                            //在子任务表里记录一下
                            setFailPreachForm(serviceSubtask, sendPreachform, "公众号发送失败", "5");
                            continue;
                        }
                    }
                } catch (Exception e) {
@@ -758,6 +605,52 @@
            }
        }
    }
    /**
     * æŒ‰ {@code active}(站点)路由到对应的短信发送方。
     * <p>
     * å…¥å‚沿用原分支里已经组装好的 {@code SendMagParam},保证内容与线上完全一致。
     * å„站点差异见 {@code ruoyi-message} æ¨¡å—各实现类。
     * <p>
     * âš ï¸ æ–°åŽï¼ˆ{@code xh})在本入口原本走「URL ç¼–码表单」,
     * ä¸Ž {@code RyTask}(发空报文)/ {@code smsSubTask}(JSON)并不一致;
     * è¿™é‡ŒæŒ‰åŽŸæ ·èµ° {@code XinHuaSmsSender#sendForm}(表单模式)。
     */
    private SendResult sendSmsByActive(ServiceSubtask serviceSubtask, SendMagParam sendMagParam) {
        SmsSendRequest request = SmsSendRequest.of(
                sendMagParam.getPhone(), sendMagParam.getContent(), serviceSubtask.getOrgid());
        return smsSenderFactory.send(active, request);
    }
    /**
     * æŒ‰ {@code active}(站点)路由到对应的微信公众号发送方。
     * <p>
     * âš ï¸ ç«™ç‚¹å·®å¼‚(与原实现一一对应):
     * <ul>
     *   <li>市一({@code hzszlyy}):不做 his æ‚£è€…id æ ¡éªŒï¼Œæ ‡é¢˜/内容都取任务名称;</li>
     *   <li>新华({@code xh}):要求 his æ‚£è€…id éžç©ºï¼Œæ‚£è€…姓名取 {@code patArchive.getName()};</li>
     *   <li>新华的 JSON æŠ¥æ–‡ä½“需要 {@code disableHtmlEscaping},与 {@code RyTask} ä¸åŒã€‚</li>
     * </ul>
     */
    private SendResult sendWechatByActive(ServiceSubtask serviceSubtask, PatArchive patArchive, String url) {
        if (StringUtils.isEmpty(patArchive.getPatidHis())) {
            return SendResult.fail(active, MessageChannel.WECHAT,
                    "公众号发送失败,his系统的患者id为空", null);
        }
        WechatSendRequest request = new WechatSendRequest();
        request.setOrgid(patArchive.getOrgid());
        request.setIdCard(serviceSubtask.getSfzh());
        request.setUrl(url);
        request.setTitle(serviceSubtask.getTaskName());
        //原实现标题与内容都传 serviceSubtask.getTaskName()
        request.setContent(serviceSubtask.getTaskName());
        request.setPhone(patArchive.getTelcode());
        request.setPatientName(patArchive.getName());
        request.setPatidHis(patArchive.getPatidHis());
        //原 RedisMqReceiver.getWXCode ä½¿ç”¨ disableHtmlEscaping,与 RyTask ä¸åŒï¼Œæ­¤å¤„保持
        request.setDisableHtmlEscaping(true);
        return wechatSenderFactory.send(active, request);
    }
    private String getSC(ServiceSubtask serviceSubtask, String scriptContent) {
@@ -995,106 +888,12 @@
        return targetKey;
    }
//    private String getKey() {
//        Map<String, List> map = new HashMap<>();
//        for (int i = 0; i < 6; i++) {
//            Boolean aBoolean = redisCache.hasKey("cache-" + i);
//            if (!aBoolean) {
//                redisCache.setCacheList("cache-" + i, new ArrayList<String>());
//                map.put("cache-" + i, new ArrayList<String>());
//            } else {
//                map.put("cache-" + i, redisCache.getCacheObject("cache-" + i));
//            }
//        }
//
//        String key = null;
//        List<String> value = new ArrayList<String>();
//        for (Map.Entry<String, List> entry : map.entrySet()) {
//            if (CollectionUtils.isEmpty(value)) {
//                value = entry.getValue();
//                key = entry.getKey();
//            } else {
//                value = entry.getValue();
//                key = entry.getKey();
//            }
//        }
//        return key;
//    }
    private String getWXCode(String idcard, String url, String taskName, String taskDesc, String phone, String patientname, String setPatientid, List<String> wxqqxx) {
        XinHuaWXReq xinHuaWXReq = new XinHuaWXReq();
        xinHuaWXReq.setIdcard(idcard);
        xinHuaWXReq.setUrl(url);
        xinHuaWXReq.setPatientid(setPatientid);
        xinHuaWXReq.setPatientname(patientname);
        xinHuaWXReq.setMobile(phone);
        xinHuaWXReq.setMedcardno(null);
        xinHuaWXReq.setTitlename(taskName);
        xinHuaWXReq.setContent(taskDesc);
        xinHuaWXReq.setAppid(wxqqxx.get(1));
        log.error("-----微信公众号的出参为----:{}", xinHuaWXReq);
//         new Gson().toJson(xinHuaWXReq);
        GsonBuilder gb = new GsonBuilder();
        gb.disableHtmlEscaping();
        String body = gb.create().toJson(xinHuaWXReq);
        log.error("encode的入参为wxqqxx ï¼š{},   body:{}", wxqqxx.get(2), body);
        String encode = encode(wxqqxx.get(2).concat(body));
        log.error("--------encode的值为:{}-----", encode);
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("sign", encode);
        String result = HttpUtils.sendPostByHeader(wxqqxx.get(3), body, headerMap);
        log.error("--------result的值为:{}-----", result);
        JSONObject jsonObject = JSONObject.parseObject(result);
        String code = (String) jsonObject.toString();
        return code;
    }
    private String getDXCode(String address, String ShouJiHM, String FaSongNR, String FaSongRID, String FaSongRXM, Map<String, Object> headerMap, String token) {
        Map<String, Object> map = new LinkedHashMap<>();
        Map<String, Object> YeWuXX = new LinkedHashMap<>();
        Map<String, Object> DuanXinXX = new LinkedHashMap<>();
        DuanXinXX.put("ShouJiHM", ShouJiHM);
        DuanXinXX.put("FaSongNR", FaSongNR);
        DuanXinXX.put("FaSongRID", FaSongRID);
        DuanXinXX.put("FaSongRXM", FaSongRXM);
        YeWuXX.put("DuanXinXX", DuanXinXX);
        map.put("XiaoXiTou", headerMap);
        map.put("YeWuXX", YeWuXX);
        String body = new Gson().toJson(map);
        String result = null;
        try {
            result = OkHttpExample.sendPostRequest(address, body, token);
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info("--------result的值为:{}-----", result);
        JSONObject jsonObject = JSONObject.parseObject(result);
        String code = (String) jsonObject.toString();
        return code;
    }
    public String encode(String arg) {
        if (arg == null) {
            arg = "";
        }
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            md5.update(arg.getBytes("UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return toHex(md5.digest());
    }
    // ---------------------------------------------------------------------
    // è¯´æ˜Žï¼šåŽŸ getWXCode / getDXCode / encode ä¸‰ä¸ªç§æœ‰æ–¹æ³•已随发送方一起收口到
    // ruoyi-message æ¨¡å—(XinHuaMpApi / LishuiSmsSender),此处已删除,避免重复实现。
    // æ³¨æ„ï¼šæœ¬ç±»çš„ getWXCode åŽŸä½¿ç”¨ new GsonBuilder().disableHtmlEscaping(),
    //      ä¸Ž RyTask.getWXCode çš„ new Gson() ä¸åŒï¼Œè¯¥å·®å¼‚已在 XinHuaMpApi ä¸­æŒ‰ç«™ç‚¹ä¿ç•™ã€‚
    // ---------------------------------------------------------------------
    /**
     * éšè®¿ä»»åŠ¡æ˜¯å¦è¶…æ—¶
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/SmsController.java
@@ -1,19 +1,17 @@
package com.ruoyi.web.controller.common;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.dx.MessageSend;
import com.ruoyi.common.utils.HttpUtil;
import com.ruoyi.common.utils.RSAPublicKeyExample;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.common.utils.sms.smsUtils;
import com.smartor.common.MtSubmitSmUtil;
import com.smartor.common.ShiyiSmsUtil;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.SmsSenderFactory;
import com.ruoyi.message.sms.provider.shiyi.ShiyiSmsRequest;
import com.ruoyi.message.sms.provider.shiyi.ShiyiSmsResponse;
import com.ruoyi.message.sms.provider.shiyi.ShiyiSmsUtil;
import com.ruoyi.message.wechat.WechatSendRequest;
import com.ruoyi.message.wechat.WechatSenderFactory;
import com.smartor.domain.*;
import com.smartor.mapper.UtilsMapper;
import com.smartor.service.IServiceOutPathService;
import com.smartor.service.IServiceSubtaskService;
import io.swagger.annotations.Api;
@@ -28,35 +26,28 @@
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
 * çŸ¥ä¿¡æŽ¥å£ï¼ˆå¯¹å¤–短信 / å¾®ä¿¡å…¬ä¼—号发送入口)。
 * <p>
 * è¯´æ˜Žï¼šçŸ­ä¿¡ä¸Žå…¬ä¼—号的站点差异(报文格式、签名、token、返回判定)已全部收口到
 * {@code ruoyi-message} æ¨¡å—,本类只负责参数组装与按 {@code active} è·¯ç”±ã€‚
 */
@RestController
@Api(description = "知信接口")
@RequestMapping("/sms")
@Slf4j
public class SmsController {
    @Value("${xhsmsPath}")
    private String xhsmsPath;
    @Autowired
    private MtSubmitSmUtil mtSubmitSmUtil;
    private SmsSenderFactory smsSenderFactory;
    @Autowired
    private WechatSenderFactory wechatSenderFactory;
    @Autowired
    private ShiyiSmsUtil shiyiSmsUtil;
    @Value("${xhsmsAccount}")
    private String xhsmsAccount;
    @Value("${xhsmsPwd}")
    private String xhsmsPwd;
    // è™šæ‹ŸæŽ¥å…¥ç 
    @Value("${xhsmsjrm}")
    private String xhsmsjrm;
    @Value("${localIP}")
    private String ip;
@@ -76,14 +67,16 @@
    private String active;
    /**
     * @param
     * @return
     * çŸ­ä¿¡å‘送(测试)
     */
    @ApiOperation("短信发送")
    @PostMapping("/sendTest")
    public AjaxResult send(@RequestBody smsVO vo) {
        String sendMsg = smsUtils.sendSms(xhsmsPath, xhsmsAccount, xhsmsPwd, vo.getPhone(), vo.getContent());
        return AjaxResult.success(sendMsg);
        //与原实现一致:测试接口固定走市一(smsUtils.sendSms,吴山签名)
        SmsSendRequest request = SmsSendRequest.of(vo.getPhone(), vo.getContent());
        request.setCampus("2");
        SendResult sendResult = smsSenderFactory.send("hzszlyy", request);
        return AjaxResult.success(sendResult.getRawResponse());
    }
    @ApiOperation("市一HIS短信XML测试")
@@ -99,8 +92,7 @@
    }
    /**
     * @param
     * @return
     * çŸ­ä¿¡å‘送
     */
    @ApiOperation("短信发送")
    @PostMapping("/send")
@@ -137,55 +129,14 @@
        String sendMsg = "false";
        try {
            if (active.equals("hzszlyy")) {
                sendMsg = smsUtils.sendSms(xhsmsPath, xhsmsAccount, xhsmsPwd, vo.getPhone(), content);
            } else if (active.equals("nhfy")) {
                Map<String, Object> smsResult = mtSubmitSmUtil.submitSm(vo.getPhone(), content);
                if (smsResult != null && smsResult.get("result") != null && smsResult.get("result").toString().equals("0")) {
                    sendMsg = "true";
                }
            } else if (active.equals("sltd")) {
                String data = MessageSend.sendMsg(content, "6", vo.getPhone(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
                if (StringUtils.isNotEmpty(data)) {
                    ObjectMapper mapper = new ObjectMapper();
                    JsonNode root = mapper.readTree(data);
                    String status = root.get("result").get(0).get("status").asText();
                    if (status.equals("00000")) {
                        sendMsg = "true";
                    }
                }
            } else if (active.equals("xh")) {
//                Map<String, String> req = new HashMap<>();
//                req.put("phone", vo.getPhone());
//                req.put("content", vo.getContent());
//                Boolean result = HttpUtil.postJsonRequest(xhsmsPath, new Gson().toJson(req));
                Map<String, String> map2 = new HashMap<String, String>();
                map2.put("action", "send");
                map2.put("account", xhsmsAccount);
                map2.put("password", xhsmsPwd);
                map2.put("mobile", vo.getPhone());
                map2.put("content", vo.getContent());
                map2.put("extno", xhsmsjrm);
                map2.put("rt", "json");
                StringBuilder encodedParams = new StringBuilder();
                for (Map.Entry<String, String> entry : map2.entrySet()) {
                    if (encodedParams.length() > 0) {
                        encodedParams.append("&");
                    }
                    // å¯¹é”®å’Œå€¼è¿›è¡ŒURL编码
                    String encodedKey = URLEncoder.encode(entry.getKey(), "UTF-8");
                    String encodedValue = URLEncoder.encode(entry.getValue(), "UTF-8");
                    encodedParams.append(encodedKey).append("=").append(encodedValue);
                }
                Boolean result = HttpUtils.sendPost(xhsmsPath, encodedParams.toString());
                if (result) {
                    sendMsg = "true";
                }
            } else if (active.equals("ls")) {
                //丽水13家医院,有点不方便,有空再接
            if ("ls".equals(active)) {
                //与原实现一致:丽水13家医院的短信功能标记为未开通
                return AjaxResult.error("该短信功能还未开通");
            }
            SmsSendRequest request = SmsSendRequest.of(vo.getPhone(), content, vo.getOrgid());
            //市一区分湖滨/吴山签名,此接口无院区信息,与原实现一致(非湖滨即吴山)
            SendResult sendResult = smsSenderFactory.send(active, request);
            sendMsg = sendResult.asLegacyFlag();
            log.info(sendMsg);
        } catch (Exception ex) {
            log.error(ex.getMessage());
@@ -194,8 +145,7 @@
    }
    /**
     * @param
     * @return
     * å¾®ä¿¡å‘送
     */
    @ApiOperation("微信发送")
    @PostMapping("/sendChat")
@@ -223,14 +173,20 @@
            serviceOutPath.setOrgid(vo.getOrgid());
            iServiceOutPathService.insertServiceOutPath(serviceOutPath);
            String url = StringUtils.isNotEmpty(req_path) ? ip + ":" + req_path + "/wt?p=" + format : ip + "/wt?p=" + format;
            sendMsg = smsUtils.sendChat(url, vo.getPhone(), vo.getIdCard());
            //与原实现一致:该接口固定走市一公众号
            WechatSendRequest request = new WechatSendRequest();
            request.setIdCard(vo.getIdCard());
            request.setPhone(vo.getPhone());
            request.setUrl(url);
            request.setOrgid(vo.getOrgid());
            SendResult sendResult = wechatSenderFactory.send("hzszlyy", request);
            sendMsg = sendResult.getRawResponse();
        }
        return AjaxResult.success(sendMsg);
    }
    /**
     * @param
     * @return
     * ç”Ÿæˆå¤–链
     */
    @ApiOperation("生成外链")
    @PostMapping("/getUrl")
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSLTDHealthcareRecordController.java
@@ -2,8 +2,6 @@
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.smartor.common.MtSubmitSmUtil;
import com.smartor.domain.MtSubmitSmResp;
import com.smartor.domain.ServiceSLTDDeptReqVO;
import com.smartor.domain.ServiceSLTDInhospReqVO;
import com.smartor.domain.ServiceSLTDInhospResDTO;
@@ -31,9 +29,6 @@
    @Autowired
    private IServiceSLTDHealthcareRecordService serviceSLTDHealthcareRecordService;
    @Autowired
    private MtSubmitSmUtil mtSubmitSmUtil;
    /**
     * æŸ¥è¯¢åœ¨é™¢æ‚£è€…健康记录列表
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceTelInfoController.java
@@ -1,7 +1,6 @@
package com.ruoyi.web.controller.smartor;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.sms.smsUtils;
import com.smartor.domain.smsVO;
import com.smartor.service.IServiceTaskoperService;
import com.smartor.service.IServiceTelInfoService;
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ShiyiSmsController.java
@@ -5,9 +5,9 @@
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.smartor.common.ShiyiSmsUtil;
import com.smartor.domain.ShiyiSmsRequest;
import com.smartor.domain.ShiyiSmsResponse;
import com.ruoyi.message.sms.provider.shiyi.ShiyiSmsRequest;
import com.ruoyi.message.sms.provider.shiyi.ShiyiSmsResponse;
import com.ruoyi.message.sms.provider.shiyi.ShiyiSmsUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/tools/BaseSmsaccountController.java
@@ -11,8 +11,9 @@
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.utils.sms.smsUtils;
import com.smartor.common.MtSubmitSmUtil;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.SmsSenderFactory;
import com.smartor.domain.BaseSmsRequest;
import com.smartor.domain.BaseSmsaccount;
import com.smartor.domain.BatchBaseSmsRequest;
@@ -43,20 +44,14 @@
    @Autowired
    private IBaseSmsaccountService baseSmsaccountService;
    @Value("${xhsmsPath}")
    private String xhsmsPath;
    @Value("${xhsmsAccount}")
    private String xhsmsAccount;
    @Value("${xhsmsPwd}")
    private String xhsmsPwd;
    @Value("${spring.profiles.active}")
    private String active;
    /**
     * æ¶ˆæ¯å‘送方工厂(短信),替代原来直接注入 xhsms* é…ç½® + å„站点分支的写法
     */
    @Autowired
    private MtSubmitSmUtil mtSubmitSmUtil;
    private SmsSenderFactory smsSenderFactory;
    /**
     * æŸ¥è¯¢çŸ­ä¿¡è´¦å·åˆ—表
@@ -135,14 +130,19 @@
            try {
                JSONObject jsTemp = JSON.parseObject(baseSmsRequest.getTemplateParam());
                String content = jsTemp.getString("name");
                sendMsg = smsUtils.sendSms(xhsmsPath, xhsmsAccount, xhsmsPwd, baseSmsRequest.getPhoneNumber(), content);
                //短信发送已收口到 ruoyi-message æ¨¡å—;原实现调用的是 smsUtils.sendSms(吴山签名)
                SmsSendRequest request = SmsSendRequest.of(baseSmsRequest.getPhoneNumber(), content);
                request.setCampus("2");
                SendResult sendResult = smsSenderFactory.send("hzszlyy", request);
                sendMsg = sendResult.getRawResponse();
            } catch (Exception ex) {
                return AjaxResult.error(ex.getMessage());
            }
            return AjaxResult.success(sendMsg);
        } else if (active.equals("nhfy")) {
            Map<String, Object> smsResult = mtSubmitSmUtil.submitSm(baseSmsRequest.getPhoneNumber(), baseSmsRequest.getTemplateCode());
            return AjaxResult.success(smsResult.get("result"));
            SmsSendRequest request = SmsSendRequest.of(baseSmsRequest.getPhoneNumber(), baseSmsRequest.getTemplateCode());
            SendResult sendResult = smsSenderFactory.send("nhfy", request);
            return AjaxResult.success(sendResult.getCode());
        } else {
            if (baseSmsaccountService.sendMsg(baseSmsRequest) == null) {
ruoyi-admin/src/main/java/com/ruoyi/web/test/WeChatService.java
ÎļþÒÑɾ³ý
ruoyi-common/src/main/java/com/ruoyi/common/dx/MessageSend.java
ÎļþÒÑɾ³ý
ruoyi-common/src/main/java/com/ruoyi/common/utils/sms/smsUtils.java
ÎļþÒÑɾ³ý
ruoyi-message/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>ruoyi</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>3.8.5</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>ruoyi-message</artifactId>
    <description>
        æ¶ˆæ¯å‘送模块:短信、微信公众号等发送方统一收口
    </description>
    <dependencies>
        <!-- é€šç”¨å·¥å…·ï¼šHttpUtil / HttpUtils / OkHttpExample / StringUtils / Md5Utils ç­‰ -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.34</version>
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <!-- gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <!-- jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <!-- å¾®ä¿¡å…¬ä¼—号 SDK(WxMpUtils) -->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-mp</artifactId>
            <version>3.0.0</version>
        </dependency>
        <!-- çœç«‹åŒå¾·çŸ­ä¿¡ï¼šApache Axis(WSDL ç”Ÿæˆå®¢æˆ·ç«¯ï¼‰ -->
        <dependency>
            <groupId>org.apache.axis</groupId>
            <artifactId>axis</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.axis</groupId>
            <artifactId>axis-jaxrpc</artifactId>
            <version>1.4</version>
        </dependency>
        <!-- ä¸½æ°´çŸ­ä¿¡ï¼šOkHttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.10.0</version>
        </dependency>
        <!-- é˜¿é‡Œäº‘短信 SDK -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dysmsapi20170525</artifactId>
            <version>2.0.24</version>
        </dependency>
        <!-- å·¥å…· -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.4</version>
        </dependency>
    </dependencies>
</project>
ruoyi-message/src/main/java/com/ruoyi/message/MessageChannel.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.ruoyi.message;
/**
 * æ¶ˆæ¯å‘送渠道类型。
 */
public enum MessageChannel {
    /**
     * çŸ­ä¿¡
     */
    SMS("4", "短信"),
    /**
     * å¾®ä¿¡å…¬ä¼—号
     */
    WECHAT("5", "微信公众号");
    private final String preachformCode;
    private final String desc;
    MessageChannel(String preachformCode, String desc) {
        this.preachformCode = preachformCode;
        this.desc = desc;
    }
    /**
     * å¯¹åº” ServiceSubtaskPreachform / ServiceFromEnum é‡Œçš„发送方式编码(4=短信,5=公众号)
     */
    public String getPreachformCode() {
        return preachformCode;
    }
    public String getDesc() {
        return desc;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/SendException.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package com.ruoyi.message;
/**
 * å‘送方异常。
 * <p>
 * æ—§ä»£ç åœ¨å‘送失败时直接抛 {@code new BaseException("短信发送失败")},为了保持行为一致,
 * æœ¬æ¨¡å—提供同语义的运行时异常,由调用方决定是否转换。
 */
public class SendException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    /**
     * å‘送方 key
     */
    private final String senderKey;
    /**
     * ç¬¬ä¸‰æ–¹åŽŸå§‹å“åº”
     */
    private final String rawResponse;
    public SendException(String message) {
        this(null, message, null);
    }
    public SendException(String senderKey, String message, String rawResponse) {
        super(message);
        this.senderKey = senderKey;
        this.rawResponse = rawResponse;
    }
    public SendException(String senderKey, String message, String rawResponse, Throwable cause) {
        super(message, cause);
        this.senderKey = senderKey;
        this.rawResponse = rawResponse;
    }
    public String getSenderKey() {
        return senderKey;
    }
    public String getRawResponse() {
        return rawResponse;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/SendResult.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,159 @@
package com.ruoyi.message;
import java.io.Serializable;
/**
 * ç»Ÿä¸€å‘送结果。
 * <p>
 * å„站点原始实现用字符串标记(如 {@code "true"} / {@code "success"} / {@code Boolean})表示成败,
 * è°ƒç”¨æ–¹å„自解析第三方返回格式,容易出错。本类把「是否成功 + æ¸ é“ + ç¬¬ä¸‰æ–¹åŽŸå§‹ç  + åŽŸå§‹å“åº”ã€ç»Ÿä¸€æ”¶å£ã€‚
 * <p>
 * æ³¨æ„ï¼š{@link #ok} ä¸º false æ—¶ï¼Œ{@link #message} å·²ç»åŒ…含可直接写入随访子任务 remark çš„中文描述,
 * ä½†ä¸ºäº†ä¿æŒå„站点线上行为不变,调用方仍可沿用原有 remark æ–‡æ¡ˆã€‚
 */
public class SendResult implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * æ˜¯å¦å‘送成功
     */
    private boolean ok;
    /**
     * æ¸ é“类型:短信 / å¾®ä¿¡å…¬ä¼—号
     */
    private MessageChannel channelType;
    /**
     * å…·ä½“发送方 key,例如 xh / ls / sltd / nhfy / hzszlyy
     */
    private String senderKey;
    /**
     * ç¬¬ä¸‰æ–¹è¿”回的业务码(有就带上,没有为 null),例如丽水的 Code、市一的 code
     */
    private String code;
    /**
     * å¤±è´¥åŽŸå›  / è¡¥å……说明
     */
    private String message;
    /**
     * ç¬¬ä¸‰æ–¹åŽŸå§‹å“åº”ï¼Œä¾¿äºŽæŽ’æŸ¥
     */
    private String rawResponse;
    public SendResult() {
    }
    /**
     * æž„造成功结果
     */
    public static SendResult success(String senderKey, MessageChannel channelType, String rawResponse) {
        SendResult result = new SendResult();
        result.ok = true;
        result.senderKey = senderKey;
        result.channelType = channelType;
        result.rawResponse = rawResponse;
        return result;
    }
    /**
     * æž„造失败结果
     */
    public static SendResult fail(String senderKey, MessageChannel channelType, String message, String rawResponse) {
        SendResult result = new SendResult();
        result.ok = false;
        result.senderKey = senderKey;
        result.channelType = channelType;
        result.message = message;
        result.rawResponse = rawResponse;
        return result;
    }
    /**
     * è¡¥å……第三方业务码,返回自身便于链式调用
     */
    public SendResult withCode(String code) {
        this.code = code;
        return this;
    }
    /**
     * è¡¥å……说明信息,返回自身便于链式调用
     */
    public SendResult withMessage(String message) {
        this.message = message;
        return this;
    }
    /**
     * è¿”回与旧代码等价的布尔标记,便于渐进式替换
     *
     * @return æˆåŠŸè¿”å›ž "true",失败返回 "false"
     */
    public String asLegacyFlag() {
        return ok ? "true" : "false";
    }
    public boolean isOk() {
        return ok;
    }
    public void setOk(boolean ok) {
        this.ok = ok;
    }
    public MessageChannel getChannelType() {
        return channelType;
    }
    public void setChannelType(MessageChannel channelType) {
        this.channelType = channelType;
    }
    public String getSenderKey() {
        return senderKey;
    }
    public void setSenderKey(String senderKey) {
        this.senderKey = senderKey;
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public String getRawResponse() {
        return rawResponse;
    }
    public void setRawResponse(String rawResponse) {
        this.rawResponse = rawResponse;
    }
    @Override
    public String toString() {
        return "SendResult{ok=" + ok
                + ", channelType=" + channelType
                + ", senderKey='" + senderKey + '\''
                + ", code='" + code + '\''
                + ", message='" + message + '\''
                + ", rawResponse='" + rawResponse + '\''
                + '}';
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/config/AliyunSmsConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.message.config;
import lombok.Data;
/**
 * é˜¿é‡Œäº‘短信配置(aliyun é€šé“)。
 * <p>
 * ç”± {@link MessageProperties#getSms()} ä»Ž {@link RootMessageProperties} çš„æ‰å¹³å­—段组装而来,
 * å­—段与 yml key çš„对应关系:{@code accessKeyId} / {@code accessKeySecret} / {@code signName}。
 */
@Data
public class AliyunSmsConfig {
    /**
     * å¯¹åº” yml key {@code accessKeyId}
     */
    private String accessKeyId;
    /**
     * å¯¹åº” yml key {@code accessKeySecret}
     */
    private String accessKeySecret;
    /**
     * å¯¹åº” yml key {@code signName}
     */
    private String signName;
}
ruoyi-message/src/main/java/com/ruoyi/message/config/MessageConfigValidator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
package com.ruoyi.message.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
 * æ¶ˆæ¯å‘送配置自检。
 * <p>
 * å­˜åœ¨çš„æ„ä¹‰ï¼šè¿™äº› key è¿‡åŽ»æ˜¯æ•£è½åœ¨å„å¤„çš„ {@code @Value},改成 {@code @ConfigurationProperties}
 * ä¹‹åŽå¦‚果前缀/层级写错,**编译期完全发现不了**,直到线上发短信时才发现参数是 null。
 * å› æ­¤å¯åŠ¨æ—¶æŠŠå½“å‰ profile çœŸæ­£ç”¨åˆ°çš„配置项打印出来(密钥脱敏),并对空值告警。
 */
@Slf4j
@Component
public class MessageConfigValidator implements ApplicationRunner {
    private final MessageProperties properties;
    @Value("${spring.profiles.active:}")
    private String active;
    public MessageConfigValidator(MessageProperties properties) {
        this.properties = properties;
    }
    @Override
    public void run(ApplicationArguments args) {
        MessageProperties.Sms sms = properties.getSms();
        XhsmsConfig xhsms = sms.getXhsms();
        MtSmsConfig mt = sms.getMt();
        AliyunSmsConfig aliyun = sms.getAliyun();
        log.info("【消息配置自检】active={},xhsms: path={}, account={}, pwd={}, extno={}",
                active, mask(xhsms.getPath()), mask(xhsms.getAccount()), maskSecret(xhsms.getPwd()),
                mask(xhsms.getExtno()));
        log.info("【消息配置自检】mt(nhfy): url={}, accountName={}, password={}, sourceAddr={}, serviceCode={}, appkey={}, flag={}",
                mask(mt.getUrl()), mask(mt.getAccountName()), maskSecret(mt.getPassword()),
                mask(mt.getSourceAddr()), mask(mt.getServiceCode()), mask(mt.getAppkey()), mask(mt.getFlag()));
        log.info("【消息配置自检】aliyun: accessKeyId={}, accessKeySecret={}, signName={}",
                mask(aliyun.getAccessKeyId()), maskSecret(aliyun.getAccessKeySecret()), mask(aliyun.getSignName()));
        log.info("【消息配置自检】wechat: shiyi.url={}, shiyi.key={}, xinhua.service={}",
                mask(properties.getWechat().getShiyi().getUrl()),
                maskSecret(properties.getWechat().getShiyi().getKey()),
                mask(properties.getWechat().getXinhua().getService()));
        check("active", active);
        if ("xh".equals(active) || "hzszlyy".equals(active)) {
            check("xhsmsPath", xhsms.getPath());
            check("xhsmsAccount", xhsms.getAccount());
            check("xhsmsPwd", xhsms.getPwd());
        } else if ("nhfy".equals(active)) {
            check("sms_url", mt.getUrl());
            check("sms_accountName", mt.getAccountName());
            check("sms_password", mt.getPassword());
            check("sms_sourceAddr", mt.getSourceAddr());
            check("sms_serviceCode", mt.getServiceCode());
            check("sms_flag", mt.getFlag());
        }
    }
    private void check(String key, String value) {
        if (value == null || value.trim().isEmpty()) {
            log.warn("【消息配置自检】⚠️ å½“前 profile({})需要 {},但该配置为空/未绑定!"
                    + "请检查 application-{}.yml ä¸Ž MessageProperties/RootMessageProperties çš„绑定层级。",
                    active, key, active);
        }
    }
    private String mask(String value) {
        if (value == null) {
            return "<null>";
        }
        if (value.isEmpty()) {
            return "<空>";
        }
        return value;
    }
    private String maskSecret(String value) {
        if (value == null) {
            return "<null>";
        }
        if (value.isEmpty()) {
            return "<空>";
        }
        if (value.length() <= 2) {
            return "**";
        }
        return value.substring(0, 1) + "***" + value.substring(value.length() - 1);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/config/MessageProperties.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,222 @@
package com.ruoyi.message.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * æ¶ˆæ¯å‘送模块配置(统一入口)。
 * <p>
 * ç”±ä¸¤å—拼成,<b>不要合并</b>:
 * <ul>
 *   <li>存量配置({@code xhsmsPath} / {@code sms_accountName} / {@code accessKeyId} / {@code signName} ...)
 *       â†’ {@link RootMessageProperties},字段名与 yml key å®Œå…¨åŒåï¼›</li>
 *   <li>新增项(原先硬编码在代码里、本次提为可配置的地址/密钥/文案)→ æœ¬ç±»çš„
 *       {@code message.sms.*} / {@code message.wechat.*} å‰ç¼€é¡¹ã€‚</li>
 * </ul>
 * å¯¹ä¸šåŠ¡ä»£ç åªæš´éœ²åˆ†ç»„çš„ {@code getSms()} / {@code getWechat()},屏蔽两种绑定的差异。
 * <p>
 * âš ï¸ {@code getSms()} æ˜¯æ‰‹å†™ getter,每次调用会把存量扁平字段合并进分组对象;这是幂等的,
 * å› æ­¤å³ä½¿è¢«è°ƒç”¨å¤šæ¬¡ä¹Ÿæ²¡æœ‰å‰¯ä½œç”¨ã€‚
 */
@Component
@ConfigurationProperties(prefix = "message")
public class MessageProperties {
    private final RootMessageProperties root;
    /** ç»‘定 {@code message.sms.*} */
    private Sms sms = new Sms();
    /** ç»‘定 {@code message.wechat.*} */
    private Wechat wechat = new Wechat();
    @Autowired
    public MessageProperties(RootMessageProperties root) {
        this.root = root;
    }
    public RootMessageProperties getRoot() {
        return root;
    }
    /**
     * çŸ­ä¿¡é…ç½®ç»Ÿä¸€å…¥å£ï¼šå­˜é‡æ‰å¹³é¡¹ + æ–°å¢žé¡¹
     */
    public Sms getSms() {
        this.sms.setXhsms(buildXhsms());
        this.sms.setMt(buildMt());
        this.sms.setAliyun(buildAliyun());
        return this.sms;
    }
    public void setSms(Sms sms) {
        this.sms = sms;
    }
    /**
     * å¾®ä¿¡å…¬ä¼—号配置统一入口
     */
    public Wechat getWechat() {
        return this.wechat;
    }
    public void setWechat(Wechat wechat) {
        this.wechat = wechat;
    }
    private XhsmsConfig buildXhsms() {
        XhsmsConfig xhsms = new XhsmsConfig();
        xhsms.setPath(root.getXhsmsPath());
        xhsms.setAccount(root.getXhsmsAccount());
        xhsms.setPwd(root.getXhsmsPwd());
        xhsms.setExtno(root.getXhsmsjrm());
        return xhsms;
    }
    private MtSmsConfig buildMt() {
        MtSmsConfig mt = new MtSmsConfig();
        mt.setAccountName(root.getSmsAccountName());
        mt.setPassword(root.getSmsPassword());
        mt.setUrl(root.getSmsUrl());
        mt.setSourceAddr(root.getSmsSourceAddr());
        mt.setServiceCode(root.getSmsServiceCode());
        mt.setAppkey(root.getSmsAppkey());
        mt.setFlag(root.getSmsFlag());
        return mt;
    }
    private AliyunSmsConfig buildAliyun() {
        AliyunSmsConfig aliyun = new AliyunSmsConfig();
        aliyun.setAccessKeyId(root.getAccessKeyId());
        aliyun.setAccessKeySecret(root.getAccessKeySecret());
        aliyun.setSignName(root.getSignName());
        return aliyun;
    }
    /**
     * çŸ­ä¿¡é…ç½®åˆ†ç»„
     */
    @Data
    public static class Sms {
        private XhsmsConfig xhsms = new XhsmsConfig();
        private MtSmsConfig mt = new MtSmsConfig();
        private AliyunSmsConfig aliyun = new AliyunSmsConfig();
        private ShiyiSms shiyi = new ShiyiSms();
        private MessagingInsert messagingInsert = new MessagingInsert();
    }
    /**
     * æ­å·žå¸‚一 HIS æœåŠ¡å¹³å°çŸ­ä¿¡ï¼ˆshiyi-his é€šé“)
     */
    @Data
    public static class ShiyiSms {
        /**
         * HIS RunService åœ°å€ã€‚
         * <p>
         * TODO å¾…确认:原实现把 {@code http://192.178.0.104:7790/MediInfoHis.svc} ç¡¬ç¼–码在
         * {@code ShiyiSmsUtil.invokeRunService} é‡Œï¼Œè¿™é‡Œæä¸ºå¯é…ç½®ï¼Œé»˜è®¤å€¼ä¸ŽåŽŸç¡¬ç¼–ç ä¿æŒä¸€è‡´ã€‚
         */
        private String wsUrl = "http://192.178.0.104:7790/MediInfoHis.svc";
        /**
         * é»˜è®¤æ“ä½œå‘˜ä»£ç 
         */
        private String defaultCaozuoydm;
        /**
         * é»˜è®¤æ“ä½œå‘˜å§“名
         */
        private String defaultCaozuoyxm;
        /**
         * çŸ­ä¿¡ç­¾åå‰ç¼€ï¼ˆå½“前未使用,留给后续需要时)
         */
        private String sign = "";
    }
    /**
     * é¢„约挂号短信平台(MessagingInsert,Axis SOAP12)
     */
    @Data
    public static class MessagingInsert {
        /**
         * SOAP æœåŠ¡åœ°å€ã€‚
         * <p>
         * TODO å¾…确认:原实现把 {@code http://218.108.11.29:8081/MessagingInsert.asmx} ç¡¬ç¼–码在
         * {@code MessagingInsertLocator} ç”Ÿæˆçš„代码里,这里提为可配置,默认值与原硬编码一致。
         */
        private String wsUrl = "http://218.108.11.29:8081/MessagingInsert.asmx";
    }
    /**
     * å¾®ä¿¡å…¬ä¼—号相关配置
     */
    @Data
    public static class Wechat {
        private ShiyiWechat shiyi = new ShiyiWechat();
        private Xinhua xinhua = new Xinhua();
    }
    /**
     * å¸‚一微信公众号(hzszlyy é€šé“)
     */
    @Data
    public static class ShiyiWechat {
        /**
         * æŽ¥å£åœ°å€ã€‚
         * <p>
         * TODO å¾…确认:原 {@code smsUtils.sendChat} æŠŠ {@code http://192.200.54.14:5003/sfjk/SendMessage}
         * ç¡¬ç¼–码在方法里,这里提为可配置,默认值与原硬编码保持一致。
         */
        private String url = "http://192.200.54.14:5003/sfjk/SendMessage";
        /**
         * æŽ¥å£å¯†é’¥ã€‚
         * <p>
         * TODO å¾…确认:原 {@code smsUtils.sendChat} æŠŠ key
         * {@code ff76f8904f5f32b5ee1739e8ea46e60g} ç¡¬ç¼–码在方法里,此值疑似为占位/测试密钥,请业务确认后替换。
         */
        private String key = "ff76f8904f5f32b5ee1739e8ea46e60g";
        /**
         * æ¶ˆæ¯æ ‡é¢˜ã€‚
         * <p>
         * TODO å¾…确认:原实现把「您好,邀请您填写出院调查表,请点击填写。」写死在 URL é‡Œï¼Œ
         * æ‰€æœ‰å¤–部链接(随访/问卷/宣教)都用同一句文案,这里提为可配置,默认值与原文案一致。
         */
        private String title = "您好,邀请您填写出院调查表,请点击填写。";
        /**
         * æ¶ˆæ¯å†…容占位。
         * <p>
         * TODO å¾…确认:原实现写死为「待随访」,此处默认值与原值一致。
         */
        private String content = "待随访";
    }
    /**
     * æ–°åŽå¾®ä¿¡å…¬ä¼—号(xh é€šé“)
     */
    @Data
    public static class Xinhua {
        /**
         * æŽ¥å£æœåŠ¡åï¼Œå›ºå®šå€¼ã€‚
         * <p>
         * åŽŸ {@code XinHuaWXReq.service} é»˜è®¤ {@code smarthos.pat.notice}
         */
        private String service = "smarthos.pat.notice";
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/config/MtSmsConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package com.ruoyi.message.config;
import lombok.Data;
/**
 * å—华附一「行业汇聚平台 MT æ¶ˆæ¯ã€é…ç½®ï¼ˆnhfy é€šé“)。
 * <p>
 * ç”± {@link MessageProperties#getSms()} ä»Ž {@link RootMessageProperties} çš„æ‰å¹³å­—段组装而来,
 * å­—段与 yml key çš„对应关系见各字段注释。
 */
@Data
public class MtSmsConfig {
    /**
     * å¯¹åº” yml key {@code sms_accountName}
     */
    private String accountName;
    /**
     * å¯¹åº” yml key {@code sms_password}
     */
    private String password;
    /**
     * å¯¹åº” yml key {@code sms_url}
     */
    private String url;
    /**
     * å¯¹åº” yml key {@code sms_sourceAddr}
     */
    private String sourceAddr;
    /**
     * å¯¹åº” yml key {@code sms_serviceCode}
     */
    private String serviceCode;
    /**
     * å¯¹åº” yml key {@code sms_appkey}
     */
    private String appkey;
    /**
     * çŸ­ä¿¡ç­¾åå‰ç¼€ï¼Œå¯¹åº” yml key {@code sms_flag}
     */
    private String flag;
}
ruoyi-message/src/main/java/com/ruoyi/message/config/RootMessageProperties.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,187 @@
package com.ruoyi.message.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * å­˜é‡é…ç½®ï¼ˆå­—段名与各站点 {@code application-<profile>.yml} çš„ key <b>完全同名</b>)。
 * <p>
 * âš ï¸ ä¸ºä»€ä¹ˆå¿…须同名、必须扁平:
 * <pre>
 * Spring Boot çš„æ¾æ•£ç»‘定是按「点分路径 + é€æ®µè§„范化后缀匹配」工作的。
 * ä¸€ä¸ªå« {@code xhsmsPath} çš„ key,只能绑定到「字段路径为 xhsmsPath」的位置;
 * å®ƒ<b>不可能</b>落到 {@code sms.xhsms.path} ä¸Šâ€”—即使 {@code xhsms} æ˜¯ {@code sms} çš„子字段、
 * {@code path} æ˜¯ {@code xhsms} çš„子字段也不行(实测已确认:嵌套绑定恒为 null)。
 * </pre>
 * æ‰€ä»¥æœ¬ç±»åˆ»æ„ä¿æŒæ‰å¹³ï¼Œå­—段名 = yml key:
 * <ul>
 *   <li>{@code xhsmsPath} / {@code xhsmsAccount} / {@code xhsmsPwd} / {@code xhsmsjrm}</li>
 *   <li>{@code sms_accountName} / {@code sms_password} / {@code sms_url} / {@code sms_sourceAddr}
 *       / {@code sms_serviceCode} / {@code sms_appkey} / {@code sms_flag}</li>
 *   <li>{@code accessKeyId} / {@code accessKeySecret} / {@code signName}</li>
 * </ul>
 * éœ€è¦åˆ†ç»„语义时由 {@link MessageProperties} è´Ÿè´£è½¬æ¢ï¼Œä¸šåŠ¡ä»£ç ä¸ç›´æŽ¥ä¾èµ–å­—æ®µå¸ƒå±€ã€‚
 * <p>
 * å„站点 yml ä¸€å¾‹ä¸éœ€è¦æ”¹åŠ¨ã€‚
 */
@Component
@ConfigurationProperties
public class RootMessageProperties {
    // ------------------------------------------------------------------ æ–°åŽçŸ­ä¿¡ç½‘å…³
    /** å¯¹åº” {@code xhsmsPath} */
    private String xhsmsPath;
    /** å¯¹åº” {@code xhsmsAccount} */
    private String xhsmsAccount;
    /** å¯¹åº” {@code xhsmsPwd} */
    private String xhsmsPwd;
    /** å¯¹åº” {@code xhsmsjrm}(虚拟接入码) */
    private String xhsmsjrm;
    // ------------------------------------------------------------------ å—华附一 MT
    /** å¯¹åº” {@code sms_accountName} */
    private String smsAccountName;
    /** å¯¹åº” {@code sms_password} */
    private String smsPassword;
    /** å¯¹åº” {@code sms_url} */
    private String smsUrl;
    /** å¯¹åº” {@code sms_sourceAddr} */
    private String smsSourceAddr;
    /** å¯¹åº” {@code sms_serviceCode} */
    private String smsServiceCode;
    /** å¯¹åº” {@code sms_appkey} */
    private String smsAppkey;
    /** å¯¹åº” {@code sms_flag}(短信签名前缀) */
    private String smsFlag;
    // ------------------------------------------------------------------ é˜¿é‡Œäº‘短信
    /** å¯¹åº” {@code accessKeyId} */
    private String accessKeyId;
    /** å¯¹åº” {@code accessKeySecret} */
    private String accessKeySecret;
    /** å¯¹åº” {@code signName} */
    private String signName;
    public String getXhsmsPath() {
        return xhsmsPath;
    }
    public void setXhsmsPath(String xhsmsPath) {
        this.xhsmsPath = xhsmsPath;
    }
    public String getXhsmsAccount() {
        return xhsmsAccount;
    }
    public void setXhsmsAccount(String xhsmsAccount) {
        this.xhsmsAccount = xhsmsAccount;
    }
    public String getXhsmsPwd() {
        return xhsmsPwd;
    }
    public void setXhsmsPwd(String xhsmsPwd) {
        this.xhsmsPwd = xhsmsPwd;
    }
    public String getXhsmsjrm() {
        return xhsmsjrm;
    }
    public void setXhsmsjrm(String xhsmsjrm) {
        this.xhsmsjrm = xhsmsjrm;
    }
    public String getSmsAccountName() {
        return smsAccountName;
    }
    public void setSmsAccountName(String smsAccountName) {
        this.smsAccountName = smsAccountName;
    }
    public String getSmsPassword() {
        return smsPassword;
    }
    public void setSmsPassword(String smsPassword) {
        this.smsPassword = smsPassword;
    }
    public String getSmsUrl() {
        return smsUrl;
    }
    public void setSmsUrl(String smsUrl) {
        this.smsUrl = smsUrl;
    }
    public String getSmsSourceAddr() {
        return smsSourceAddr;
    }
    public void setSmsSourceAddr(String smsSourceAddr) {
        this.smsSourceAddr = smsSourceAddr;
    }
    public String getSmsServiceCode() {
        return smsServiceCode;
    }
    public void setSmsServiceCode(String smsServiceCode) {
        this.smsServiceCode = smsServiceCode;
    }
    public String getSmsAppkey() {
        return smsAppkey;
    }
    public void setSmsAppkey(String smsAppkey) {
        this.smsAppkey = smsAppkey;
    }
    public String getSmsFlag() {
        return smsFlag;
    }
    public void setSmsFlag(String smsFlag) {
        this.smsFlag = smsFlag;
    }
    public String getAccessKeyId() {
        return accessKeyId;
    }
    public void setAccessKeyId(String accessKeyId) {
        this.accessKeyId = accessKeyId;
    }
    public String getAccessKeySecret() {
        return accessKeySecret;
    }
    public void setAccessKeySecret(String accessKeySecret) {
        this.accessKeySecret = accessKeySecret;
    }
    public String getSignName() {
        return signName;
    }
    public void setSignName(String signName) {
        this.signName = signName;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/config/XhsmsConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.message.config;
import lombok.Data;
/**
 * æ–°åŽçŸ­ä¿¡ç½‘关配置(xh é€šé“,市一 hzszlyy é€šé“复用同一套地址/账号)。
 * <p>
 * ç”± {@link MessageProperties#getSms()} ä»Ž {@link RootMessageProperties} çš„æ‰å¹³å­—段组装而来,
 * å­—段与 yml key çš„对应关系:{@code xhsmsPath} / {@code xhsmsAccount} / {@code xhsmsPwd} / {@code xhsmsjrm}。
 */
@Data
public class XhsmsConfig {
    /**
     * ç½‘关地址,对应 yml key {@code xhsmsPath}
     */
    private String path;
    /**
     * è´¦å·ï¼Œå¯¹åº” yml key {@code xhsmsAccount}
     */
    private String account;
    /**
     * å¯†ç ï¼Œå¯¹åº” yml key {@code xhsmsPwd}
     */
    private String pwd;
    /**
     * è™šæ‹ŸæŽ¥å…¥ç ï¼Œå¯¹åº” yml key {@code xhsmsjrm}
     */
    private String extno;
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/AbstractSmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package com.ruoyi.message.sms;
import com.ruoyi.message.MessageChannel;
import com.ruoyi.message.SendResult;
import org.apache.commons.lang3.StringUtils;
/**
 * çŸ­ä¿¡å‘送方公共基类:统一日志、参数校验、异常兜底。
 */
public abstract class AbstractSmsSender implements SmsSender {
    @Override
    public SendResult send(SmsSendRequest request) {
        if (request == null || StringUtils.isBlank(request.getPhone())) {
            return SendResult.fail(key(), MessageChannel.SMS, "手机号为空", null);
        }
        if (StringUtils.isBlank(request.getContent())) {
            return SendResult.fail(key(), MessageChannel.SMS, "短信内容为空", null);
        }
        try {
            return doSend(request);
        } catch (Exception e) {
            logError(request, e);
            return SendResult.fail(key(), MessageChannel.SMS, "短信发送异常:" + e.getMessage(), null);
        }
    }
    /**
     * çœŸæ­£çš„发送逻辑,由各渠道实现
     */
    protected abstract SendResult doSend(SmsSendRequest request) throws Exception;
    /**
     * å¼‚常日志,子类可覆写补充上下文
     */
    protected void logError(SmsSendRequest request, Exception e) {
        org.slf4j.LoggerFactory.getLogger(getClass())
                .error("【{}】短信发送异常,手机号:{},错误:{}", key(), request.getPhone(), e.getMessage(), e);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/SmsSendRequest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
package com.ruoyi.message.sms;
import lombok.Data;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * çŸ­ä¿¡å‘送请求(与具体发送方无关的统一入参)。
 * <p>
 * å„发送方需要的差异参数(丽水的机构配置、市一的院区、新华的模板等)通过
 * {@link #extras} é€ä¼ ï¼Œé¿å…ä¸ºäº†å°‘数站点污染公共接口。
 */
@Data
public class SmsSendRequest implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * æ‰‹æœºå·ï¼ˆå¤šä¸ªç”¨è‹±æ–‡é€—号分隔,如 13500000001,13500000002)
     */
    private String phone;
    /**
     * çŸ­ä¿¡å†…容(调用方已拼接好签名/模板变量)
     */
    private String content;
    /**
     * æœºæž„ID,部分发送方(丽水)按机构查配置
     */
    private String orgid;
    /**
     * é™¢åŒºæ ‡è¯†ï¼Œå¸‚一区分湖滨/吴山两套签名:
     * 1=湖滨(【杭州市第一人民医院】),其他/空=吴山(【杭州市肿瘤医院】)
     */
    private String campus;
    /**
     * çŸ­ä¿¡æ¨¡æ¿ç¼–码(阿里云等模板化发送方使用)
     */
    private String templateCode;
    /**
     * æ¨¡æ¿å‚æ•° JSON(阿里云等模板化发送方使用)
     */
    private String templateParam;
    /**
     * æ‰©å±•参数
     */
    private Map<String, Object> extras;
    public static SmsSendRequest of(String phone, String content) {
        SmsSendRequest request = new SmsSendRequest();
        request.setPhone(phone);
        request.setContent(content);
        return request;
    }
    public static SmsSendRequest of(String phone, String content, String orgid) {
        SmsSendRequest request = of(phone, content);
        request.setOrgid(orgid);
        return request;
    }
    /**
     * è¿½åŠ ä¸€ä¸ªæ‰©å±•å‚æ•°
     */
    public SmsSendRequest putExtra(String key, Object value) {
        if (this.extras == null) {
            this.extras = new LinkedHashMap<>();
        }
        this.extras.put(key, value);
        return this;
    }
    /**
     * è¯»å–扩展参数
     */
    public Object getExtra(String key) {
        return this.extras == null ? null : this.extras.get(key);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/SmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.ruoyi.message.sms;
import com.ruoyi.message.SendResult;
/**
 * çŸ­ä¿¡å‘送方(一个渠道一个实现)。
 * <p>
 * å„站点对接方式差异很大 â€”— æŠ¥æ–‡æ ¼å¼ã€é‰´æƒ/签名、token æ¢å–、返回码判定、配置来源都不同,
 * å› æ­¤ä¸è¦ç”¨ä¸€ä¸ªå®žçŽ°åŠ å‚æ•°åˆ†æ”¯æ¥åšï¼Œè€Œæ˜¯ä¸€ä¸ªæ¸ é“ä¸€ä¸ªç±»ï¼Œç±»å†…éƒ¨ä¿æŒä¸Žçº¿ä¸Šå®Œå…¨ä¸€è‡´çš„è¡Œä¸ºã€‚
 */
public interface SmsSender {
    /**
     * å‘送方唯一 key。
     * <p>
     * è¯¥ key åŒæ—¶æ˜¯ {@code spring.profiles.active} çš„取值(xh / ls / sltd / nhfy / hzszlyy ...),
     * ç”± {@code MessageSenderFactory} æŒ‰ key è·¯ç”±ã€‚
     */
    String key();
    /**
     * å‘送短信
     *
     * @param request ç»Ÿä¸€å…¥å‚
     * @return ç»Ÿä¸€ç»“果,不抛异常(网络/解析异常也会被包装成失败结果)
     */
    SendResult send(SmsSendRequest request);
    /**
     * å‘送方说明,用于日志与排查
     */
    default String description() {
        return key();
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/SmsSenderFactory.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
package com.ruoyi.message.sms;
import com.ruoyi.message.SendException;
import com.ruoyi.message.SendResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
 * çŸ­ä¿¡å‘送方工厂:按 key è·¯ç”±åˆ°ä¸€ä¸ªå…·ä½“实现,调用方不再需要 {@code if (active.equals("xx"))} ç¡¬åˆ†æ”¯ã€‚
 * <p>
 * Spring ä¼šè‡ªåŠ¨æŠŠæ‰€æœ‰ {@link SmsSender} æ³¨å…¥è¿›æ¥ï¼Œæ–°å¢žä¸€ä¸ªç«™ç‚¹åªéœ€æ–°å¢žä¸€ä¸ªå®žçŽ°ç±»ã€‚
 */
@Slf4j
@Component
public class SmsSenderFactory {
    /**
     * ç«™ç‚¹ profile â†’ çŸ­ä¿¡é€šé“ key çš„æ˜ å°„。
     * <p>
     * ç›®å‰ä¸¤è¾¹å–值完全一致({@code spring.profiles.active} ç›´æŽ¥å°±æ˜¯æ¸ é“名),
     * å•独留出映射表是为了将来 profile åä¸Žæ¸ é“名解耦时不改调用方。
     */
    private static final Map<String, String> PROFILE_ALIAS = new LinkedHashMap<>();
    static {
        PROFILE_ALIAS.put("xh", "xh");
        PROFILE_ALIAS.put("ls", "ls");
        PROFILE_ALIAS.put("sltd", "sltd");
        PROFILE_ALIAS.put("nhfy", "nhfy");
        PROFILE_ALIAS.put("hzszlyy", "hzszlyy");
    }
    private final Map<String, SmsSender> senders = new LinkedHashMap<>();
    public SmsSenderFactory(List<SmsSender> senderList) {
        for (SmsSender sender : senderList) {
            SmsSender exists = senders.put(sender.key(), sender);
            if (exists != null) {
                throw new IllegalStateException("短信发送方 key é‡å¤ï¼š" + sender.key()
                        + ",冲突实现:" + exists.getClass().getName() + " ä¸Ž " + sender.getClass().getName());
            }
        }
        log.info("【SmsSenderFactory】已注册短信发送方:{}", senders.keySet());
    }
    /**
     * æŒ‰ key èŽ·å–å‘é€æ–¹
     *
     * @param key æ¸ é“ key(即 {@code spring.profiles.active} å–值)
     * @return å‘送方实现
     * @throws SendException æ‰¾ä¸åˆ°å¯¹åº”实现时抛出
     */
    public SmsSender get(String key) {
        String resolved = resolveSmsKey(key);
        SmsSender sender = resolved == null ? null : senders.get(resolved);
        if (sender == null) {
            throw new SendException("未找到短信发送方,key=" + key + ",已注册:" + senders.keySet());
        }
        return sender;
    }
    /**
     * æŒ‰ key èŽ·å–å‘é€æ–¹ï¼Œæ‰¾ä¸åˆ°è¿”å›ž {@code null}
     */
    public SmsSender getOrNull(String key) {
        String resolved = resolveSmsKey(key);
        return resolved == null ? null : senders.get(resolved);
    }
    /**
     * æŒ‰ key å‘送短信(等价于旧代码里那 200 è¡Œ if-else çš„结果)
     */
    public SendResult send(String key, SmsSendRequest request) {
        return get(key).send(request);
    }
    /**
     * åˆ¤æ–­æ˜¯å¦å­˜åœ¨è¯¥ key çš„发送方
     */
    public boolean supports(String key) {
        return getOrNull(key) != null;
    }
    /**
     * å·²æ³¨å†Œçš„æ¸ é“ key
     */
    public Set<String> keys() {
        return new TreeSet<>(senders.keySet());
    }
    /**
     * ç«™ç‚¹ profile â†’ æ¸ é“ key
     */
    public static String resolveSmsKey(String profile) {
        if (StringUtils.isEmpty(profile)) {
            return null;
        }
        String alias = PROFILE_ALIAS.get(profile);
        return alias == null ? profile : alias;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/aliyun/AliYunSmsApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,204 @@
package com.ruoyi.message.sms.provider.aliyun;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.AddSmsTemplateRequest;
import com.aliyun.dysmsapi20170525.models.AddSmsTemplateResponse;
import com.aliyun.dysmsapi20170525.models.AddSmsTemplateResponseBody;
import com.aliyun.dysmsapi20170525.models.ModifySmsTemplateRequest;
import com.aliyun.dysmsapi20170525.models.ModifySmsTemplateResponse;
import com.aliyun.dysmsapi20170525.models.ModifySmsTemplateResponseBody;
import com.aliyun.dysmsapi20170525.models.QuerySmsTemplateListRequest;
import com.aliyun.dysmsapi20170525.models.QuerySmsTemplateListResponse;
import com.aliyun.dysmsapi20170525.models.QuerySmsTemplateListResponseBody;
import com.aliyun.dysmsapi20170525.models.QuerySmsTemplateRequest;
import com.aliyun.dysmsapi20170525.models.QuerySmsTemplateResponse;
import com.aliyun.dysmsapi20170525.models.QuerySmsTemplateResponseBody;
import com.aliyun.dysmsapi20170525.models.SendBatchSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendBatchSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendBatchSmsResponseBody;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.Common;
import com.aliyun.teautil.models.RuntimeOptions;
import com.ruoyi.message.config.MessageProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
 * é˜¿é‡Œäº‘短信 API。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šé€»è¾‘取自 {@code com.smartor.service.impl.BaseSmsaccountServiceImpl},
 * ä½†<b>不再依赖 smartor çš„ domain å¯¹è±¡</b>({@code BaseSmsRequest} / {@code BatchBaseSmsRequest} /
 * {@code TemplateSmsRequest}),改为直接接收基础参数,从而让本模块可以放在
 * {@code ruoyi-system} ä¹‹å‰ã€è¢«æ‰€æœ‰ä¸šåŠ¡æ¨¡å—å¤ç”¨ã€‚
 * <p>
 * åŽŸ {@code BaseSmsaccountServiceImpl} ä¿ç•™ä¸ºã€ŒçŸ­ä¿¡è´¦å·ç®¡ç† + DB è¯»å†™ã€çš„ Service,
 * å…¶ä¸Šçš„阿里云调用改为委托本类(见改造说明)。
 */
@Slf4j
@Component
public class AliYunSmsApi {
    private final MessageProperties properties;
    public AliYunSmsApi(MessageProperties properties) {
        this.properties = properties;
    }
    /**
     * å‘送短信
     *
     * @param phoneNumbers  æ‰‹æœºå·ï¼Œå¤šä¸ªç”¨é€—号分隔
     * @param templateCode  æ¨¡æ¿ç¼–码
     * @param templateParam æ¨¡æ¿å‚æ•° JSON
     */
    public SendSmsResponseBody sendSms(String phoneNumbers, String templateCode, String templateParam) {
        try {
            Client client = createClient();
            SendSmsRequest sendSmsRequest = new SendSmsRequest()
                    .setPhoneNumbers(phoneNumbers)
                    .setSignName(properties.getSms().getAliyun().getSignName())
                    .setTemplateCode(templateCode)
                    .setTemplateParam(templateParam);
            SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, new RuntimeOptions());
            return sendSmsResponse.getBody();
        } catch (Exception e) {
            log.error("阿里云短信发送失败,phoneNumbers:{},templateCode:{}", phoneNumbers, templateCode, e);
        }
        return null;
    }
    /**
     * æ‰¹é‡å‘送短信
     */
    public SendBatchSmsResponseBody batchSendMsg(List<String> phoneNumbers, String templateCode) {
        try {
            Client client = createClient();
            List<String> signNameJson = new ArrayList<>();
            for (int i = 0; i < phoneNumbers.size(); i++) {
                signNameJson.add("\"" + properties.getSms().getAliyun().getSignName() + "\"");
            }
            SendBatchSmsRequest sendBatchSmsRequest = new SendBatchSmsRequest()
                    .setSignNameJson(signNameJson.toString())
                    .setPhoneNumberJson(phoneNumbers.toString())
                    .setTemplateCode(templateCode);
            RuntimeOptions runtime = new RuntimeOptions();
            try {
                SendBatchSmsResponse sendBatchSmsResponse = client.sendBatchSmsWithOptions(sendBatchSmsRequest, runtime);
                return sendBatchSmsResponse.getBody();
            } catch (TeaException error) {
                Common.assertAsString(error.message);
            } catch (Exception innerError) {
                TeaException error = new TeaException(innerError.getMessage(), innerError);
                Common.assertAsString(error.message);
            }
        } catch (Exception e) {
            log.error("阿里云短信批量发送失败,templateCode:{}", templateCode, e);
        }
        return null;
    }
    /**
     * æ–°å¢žçŸ­ä¿¡æ¨¡æ¿
     */
    public AddSmsTemplateResponseBody addSmsTemplate(Integer templateType, String templateName,
                                                     String templateContent, String remark) {
        try {
            Client client = createClient();
            AddSmsTemplateRequest addSmsTemplateRequest = new AddSmsTemplateRequest()
                    .setTemplateType(templateType)
                    .setTemplateName(templateName)
                    .setTemplateContent(templateContent)
                    .setRemark(remark);
            RuntimeOptions runtime = new RuntimeOptions();
            AddSmsTemplateResponse response = client.addSmsTemplateWithOptions(addSmsTemplateRequest, runtime);
            return response.getBody();
        } catch (TeaException error) {
            Common.assertAsString(error.message);
        } catch (Exception innerError) {
            TeaException error = new TeaException(innerError.getMessage(), innerError);
            Common.assertAsString(error.message);
        }
        return null;
    }
    /**
     * ä¿®æ”¹çŸ­ä¿¡æ¨¡æ¿
     */
    public ModifySmsTemplateResponseBody modifySmsTemplate(Integer templateType, String templateName,
                                                          String templateCode, String templateContent, String remark) {
        try {
            Client client = createClient();
            ModifySmsTemplateRequest modifySmsTemplateRequest = new ModifySmsTemplateRequest();
            modifySmsTemplateRequest.setTemplateType(templateType);
            modifySmsTemplateRequest.setTemplateName(templateName);
            modifySmsTemplateRequest.setTemplateCode(templateCode);
            modifySmsTemplateRequest.setTemplateContent(templateContent);
            modifySmsTemplateRequest.setRemark(remark);
            RuntimeOptions runtime = new RuntimeOptions();
            ModifySmsTemplateResponse response = client.modifySmsTemplateWithOptions(modifySmsTemplateRequest, runtime);
            return response.getBody();
        } catch (TeaException error) {
            Common.assertAsString(error.message);
        } catch (Exception innerError) {
            TeaException error = new TeaException(innerError.getMessage(), innerError);
            Common.assertAsString(error.message);
        }
        return null;
    }
    /**
     * æŸ¥è¯¢çŸ­ä¿¡æ¨¡æ¿åˆ—表
     */
    public QuerySmsTemplateListResponseBody querySmsTemplate(Integer pageIndex, Integer pageSize) {
        try {
            Client client = createClient();
            QuerySmsTemplateListRequest request = new QuerySmsTemplateListRequest()
                    .setPageIndex(pageIndex)
                    .setPageSize(pageSize);
            RuntimeOptions runtime = new RuntimeOptions();
            QuerySmsTemplateListResponse response = client.querySmsTemplateListWithOptions(request, runtime);
            return response.getBody();
        } catch (TeaException error) {
            Common.assertAsString(error.message);
        } catch (Exception innerError) {
            TeaException error = new TeaException(innerError.getMessage(), innerError);
            Common.assertAsString(error.message);
        }
        return null;
    }
    /**
     * æŸ¥è¯¢çŸ­ä¿¡æ¨¡æ¿å®¡æ ¸çŠ¶æ€
     */
    public QuerySmsTemplateResponseBody querySmsTemplateStatus(String templateCode) {
        try {
            Client client = createClient();
            QuerySmsTemplateRequest request = new QuerySmsTemplateRequest().setTemplateCode(templateCode);
            RuntimeOptions runtime = new RuntimeOptions();
            QuerySmsTemplateResponse response = client.querySmsTemplateWithOptions(request, runtime);
            return response.getBody();
        } catch (TeaException error) {
            Common.assertAsString(error.message);
        } catch (Exception innerError) {
            TeaException error = new TeaException(innerError.getMessage(), innerError);
            Common.assertAsString(error.message);
        }
        return null;
    }
    private Client createClient() throws Exception {
        Config config = new Config()
                .setAccessKeyId(properties.getSms().getAliyun().getAccessKeyId())
                .setAccessKeySecret(properties.getSms().getAliyun().getAccessKeySecret());
        // è®¿é—®çš„域名
        config.endpoint = "dysmsapi.aliyuncs.com";
        return new Client(config);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/aliyun/AliYunSmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
package com.ruoyi.message.sms.provider.aliyun;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
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.springframework.stereotype.Component;
/**
 * é˜¿é‡Œäº‘短信发送方({@code aliyun} é€šé“)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šé˜¿é‡Œäº‘ dysmsapi SDK,{@code SignName + TemplateCode + TemplateParam},
 * æˆåŠŸåˆ¤å®šä¸ºå“åº” {@code Code == "OK"}。
 * <p>
 * è¯´æ˜Žï¼šè¯¥é€šé“目前只在 {@code BaseSmsaccountServiceImpl}(短信账号/模板管理)里使用,
 * ä¸šåŠ¡å‘é€æµç¨‹æœªèµ°å®ƒã€‚æ”¶å£åŽå¯ç”±å·¥åŽ‚ç»Ÿä¸€è°ƒåº¦ã€‚
 */
@Slf4j
@Component
public class AliYunSmsSender extends AbstractSmsSender {
    /**
     * æ¸ é“ key
     */
    public static final String KEY = "aliyun";
    private final AliYunSmsApi aliYunSmsApi;
    public AliYunSmsSender(AliYunSmsApi aliYunSmsApi) {
        this.aliYunSmsApi = aliYunSmsApi;
    }
    @Override
    public String key() {
        return KEY;
    }
    @Override
    public String description() {
        return "阿里云短信(dysmsapi,模板化)";
    }
    @Override
    protected SendResult doSend(SmsSendRequest request) {
        //阿里云短信不走内容直发,而是 æ¨¡æ¿ç¼–码 + æ¨¡æ¿å‚æ•°
        SendSmsRequestAdapter adapter = new SendSmsRequestAdapter(request);
        if (adapter.templateParam == null) {
            return SendResult.fail(KEY, MessageChannel.SMS, "短信模板参数为空", null);
        }
        SendSmsResponseBody body = aliYunSmsApi.sendSms(
                adapter.phone, adapter.templateCode, adapter.templateParam);
        if (body == null) {
            return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败", null);
        }
        log.info("【{}】阿里云短信发送结果:code={}, message={}", KEY, body.getCode(), body.getMessage());
        if ("OK".equals(body.getCode())) {
            return SendResult.success(KEY, MessageChannel.SMS, body.getMessage())
                    .withCode(body.getCode())
                    .withMessage(body.getBizId());
        }
        return SendResult.fail(KEY, MessageChannel.SMS, body.getMessage(), body.getMessage())
                .withCode(body.getCode());
    }
    /**
     * ç»•过基类「内容不能为空」的校验:阿里云用模板参数,不用 content ç›´å‘。
     */
    @Override
    public SendResult send(SmsSendRequest request) {
        if (request == null || request.getPhone() == null || request.getPhone().trim().isEmpty()) {
            return SendResult.fail(KEY, MessageChannel.SMS, "手机号为空", null);
        }
        try {
            return doSend(request);
        } catch (Exception e) {
            log.error("【{}】阿里云短信发送异常:{}", KEY, e.getMessage(), e);
            return SendResult.fail(KEY, MessageChannel.SMS, "短信发送异常:" + e.getMessage(), null);
        }
    }
    /**
     * å°é€‚配器:把统一入参映射成阿里云需要的三个字段。
     * <p>
     * å…¼å®¹æ—§è°ƒç”¨æ–¹ï¼ˆ{@code BaseSmsRequest})传参习惯:手机号走 {@code phone},
     * æ¨¡æ¿ç¼–码/参数走 {@code templateCode} / {@code templateParam}。
     */
    private static final class SendSmsRequestAdapter {
        private final String phone;
        private final String templateCode;
        private final String templateParam;
        private SendSmsRequestAdapter(SmsSendRequest request) {
            this.phone = request.getPhone();
            this.templateCode = request.getTemplateCode();
            this.templateParam = request.getTemplateParam() != null
                    ? request.getTemplateParam()
                    : request.getContent();
        }
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/hzszlyy/ShiyiSmsApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
package com.ruoyi.message.sms.provider.hzszlyy;
import com.ruoyi.common.utils.HttpUtil;
import com.ruoyi.common.utils.sign.Md5Utils;
import com.ruoyi.message.config.MessageProperties;
import com.ruoyi.message.config.XhsmsConfig;
import com.ruoyi.message.config.RootMessageProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
 * æ­å·žå¸‚一短信网关 API(新华网关地址 + å¸‚一自有签名算法)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šJSON æŠ¥æ–‡ {@code userName/content/phoneList/timestamp/sign},
 * ç­¾åä¸º {@code MD5(userName + timestamp + MD5(passWord))}。
 * <p>
 * âš ï¸ ç«™ç‚¹å·®å¼‚(必须区分,不能合并):
 * <ul>
 *   <li>吴山院区:签名前缀 {@code ã€æ­å·žå¸‚肿瘤医院】};</li>
 *   <li>湖滨院区:签名前缀 {@code ã€æ­å·žå¸‚第一人民医院】}。</li>
 * </ul>
 * åŽŸå®žçŽ°åˆ†ä¸¤ä¸ªæ–¹æ³• {@code smsUtils.sendSms} / {@code smsUtils.sendSms1} æ‰¿è½½è¿™ä¸€å·®å¼‚。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸç±»ä¸º {@code com.ruoyi.common.utils.sms.smsUtils},签名前缀保持不变。
 */
@Slf4j
@Component
public class ShiyiSmsApi {
    /**
     * å´å±±é™¢åŒºçŸ­ä¿¡ç­¾åå‰ç¼€
     */
    public static final String PREFIX_WUSHAN = "【杭州市肿瘤医院】";
    /**
     * æ¹–滨院区短信签名前缀
     */
    public static final String PREFIX_HIBIN = "【杭州市第一人民医院】";
    private final MessageProperties properties;
    public ShiyiSmsApi(MessageProperties properties) {
        this.properties = properties;
    }
    /**
     * å´å±±é™¢åŒºå‘送(等价 {@code smsUtils.sendSms})
     *
     * @return ç½‘关返回的字符串,含「处理成功」表示成功
     */
    public String sendWushan(String phone, String content) {
        return send(phone, content, PREFIX_WUSHAN);
    }
    /**
     * æ¹–滨院区发送(等价 {@code smsUtils.sendSms1})
     *
     * @return ç½‘关返回的字符串,含「处理成功」表示成功
     */
    public String sendHibin(String phone, String content) {
        return send(phone, content, PREFIX_HIBIN);
    }
    private String send(String phone, String content, String prefix) {
        XhsmsConfig cfg = properties.getSms().getXhsms();
        long timestamp = new Date().getTime();
        String sign = Md5Utils.hash(cfg.getAccount() + timestamp + Md5Utils.hash(cfg.getPwd()));
        String fullContent = prefix + content;
        String jsonMsg = "{"
                + "\"userName\": \"" + cfg.getAccount() + "\","
                + "\"content\": \"" + fullContent + "\","
                + "\"phoneList\": [" + phone + "],"
                + "\"timestamp\": " + timestamp + ","
                + "\"sign\": \"" + sign + "\""
                + "}";
        log.info("【ShiyiSmsApi】市一短信发送,地址:{},签名前缀:{},手机号:{}", cfg.getPath(), prefix, phone);
        Boolean sendMsg = HttpUtil.postJsonRequest(cfg.getPath(), jsonMsg);
        return String.valueOf(sendMsg);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/hzszlyy/ShiyiSmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
package com.ruoyi.message.sms.provider.hzszlyy;
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.stereotype.Component;
/**
 * æ­å·žå¸‚一短信发送方({@code hzszlyy} é€šé“)。
 * <p>
 * âš ï¸ è¯¥é€šé“有两个院区、两套短信签名,必须区分开:
 * <ul>
 *   <li>{@link SmsSendRequest#getCampus()} ä¸º {@code "1"} â†’ æ¹–滨院区({@code smsUtils.sendSms1});</li>
 *   <li>其他(含为空)→ å´å±±é™¢åŒºï¼ˆ{@code smsUtils.sendSms})。</li>
 * </ul>
 * åˆ¤å®šä¾æ®æ¥è‡ª {@code RyTask} ä¸­é€šè¿‡ {@code SysDept.campusid} æŸ¥åˆ°çš„院区编码,
 * è°ƒç”¨æ–¹è´Ÿè´£æŠŠ {@code campus} ä¼ è¿›æ¥ï¼›ä¸ä¼ åˆ™ä¿æŒä¸Žçº¿ä¸Šã€Œéžæ¹–滨即吴山」一致的默认行为。
 * <p>
 * æˆåŠŸåˆ¤å®šï¼šç½‘å…³è¿”å›žå­—ç¬¦ä¸²åŒ…å«ã€Œå¤„ç†æˆåŠŸã€ã€‚
 * ä»£ç æ¥æºï¼š{@code RyTask.hzszlyy åˆ†æ”¯}(行为保持不变)。
 */
@Slf4j
@Component
public class ShiyiSmsSender extends AbstractSmsSender {
    /**
     * æ¸ é“ key,与 {@code spring.profiles.active=hzszlyy} å¯¹åº”
     */
    public static final String KEY = "hzszlyy";
    /**
     * é™¢åŒºç¼–码:湖滨
     */
    public static final String CAMPUS_HIBIN = "1";
    private final ShiyiSmsApi shiyiSmsApi;
    public ShiyiSmsSender(ShiyiSmsApi shiyiSmsApi) {
        this.shiyiSmsApi = shiyiSmsApi;
    }
    @Override
    public String key() {
        return KEY;
    }
    @Override
    public String description() {
        return "杭州市一短信(吴山/湖滨两套签名)";
    }
    @Override
    protected SendResult doSend(SmsSendRequest request) {
        boolean hibin = CAMPUS_HIBIN.equals(request.getCampus());
        String result = hibin
                ? shiyiSmsApi.sendHibin(request.getPhone(), request.getContent())
                : shiyiSmsApi.sendWushan(request.getPhone(), request.getContent());
        log.info("【{}】市一短信发送结果({}):{}", KEY, hibin ? "湖滨" : "吴山", result);
        if (StringUtils.isNotEmpty(result) && result.contains("处理成功")) {
            return SendResult.success(KEY, MessageChannel.SMS, result).withCode(result);
        }
        return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败", result).withCode(result);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/ls/LishuiMessageConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,114 @@
package com.ruoyi.message.sms.provider.ls;
/**
 * ä¸½æ°´çŸ­ä¿¡æœºæž„级配置。
 * <p>
 * åŽŸå®žçŽ°æœ‰ä¸‰å¥—æŸ¥è¯¢æ–¹å¼ï¼ˆ{@code RyTask} æŒ‰æœºæž„查 {@code ls.msg.info}、
 * {@code ServiceSubtaskServiceImpl} ç”¨ {@code MsgLSEnum}、
 * {@code SmsRecordsServiceImpl} åˆæ˜¯ç¬¬ä¸‰å¥—),这里统一为一个值对象。
 */
public class LishuiMessageConfig {
    /**
     * æ¶ˆæ¯å‘送地址
     */
    private String address;
    /**
     * ç§Ÿæˆ·ID(报文 XiaoXiTou.ZuHuID)
     */
    private String zuHuId;
    /**
     * ç§Ÿæˆ·åç§°ï¼ˆæŠ¥æ–‡ XiaoXiTou.ZuHuMC / FaSongJGMC)
     */
    private String zuHuMc;
    /**
     * å‘送机构ID(报文 XiaoXiTou.FaSongJGID)
     */
    private String orgid;
    /**
     * å‘送人ID
     */
    private String sendPersonId;
    /**
     * å‘送人姓名
     */
    private String sendPersonName;
    /**
     * token èŽ·å–åœ°å€
     */
    private String tokenUrl;
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getZuHuId() {
        return zuHuId;
    }
    public void setZuHuId(String zuHuId) {
        this.zuHuId = zuHuId;
    }
    public String getZuHuMc() {
        return zuHuMc;
    }
    public void setZuHuMc(String zuHuMc) {
        this.zuHuMc = zuHuMc;
    }
    public String getOrgid() {
        return orgid;
    }
    public void setOrgid(String orgid) {
        this.orgid = orgid;
    }
    public String getSendPersonId() {
        return sendPersonId;
    }
    public void setSendPersonId(String sendPersonId) {
        this.sendPersonId = sendPersonId;
    }
    public String getSendPersonName() {
        return sendPersonName;
    }
    public void setSendPersonName(String sendPersonName) {
        this.sendPersonName = sendPersonName;
    }
    public String getTokenUrl() {
        return tokenUrl;
    }
    public void setTokenUrl(String tokenUrl) {
        this.tokenUrl = tokenUrl;
    }
    @Override
    public String toString() {
        return "LishuiMessageConfig{address='" + address + '\''
                + ", zuHuId='" + zuHuId + '\''
                + ", zuHuMc='" + zuHuMc + '\''
                + ", orgid='" + orgid + '\''
                + ", sendPersonId='" + sendPersonId + '\''
                + ", sendPersonName='" + sendPersonName + '\''
                + ", tokenUrl='" + tokenUrl + '\''
                + '}';
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/ls/LishuiMessageConfigProvider.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.message.sms.provider.ls;
/**
 * ä¸½æ°´æœºæž„级配置 / token æä¾›æ–¹ï¼ˆæ‰©å±•点 SPI)。
 * <p>
 * çŽ°çŠ¶ï¼šä¸½æ°´çš„ã€ŒçŸ­ä¿¡åœ°å€ / ç§Ÿæˆ·ä¿¡æ¯ / å‘送人」存在数据库表 {@code sys_config} çš„ {@code ls.msg.info} é‡Œï¼Œ
 * è€Œè¡¨è®¿é—®åœ¨ {@code ruoyi-system} / {@code smartor} æ¨¡å—ï¼›token æ¢å–逻辑在 {@code smartor} çš„
 * {@code LSHospTokenUtil}。
 * <p>
 * ä¸ºäº†è®© {@code ruoyi-message} ä¿æŒã€Œä¸ä¾èµ–业务模块」的干净分层,这里定义 SPI,
 * ç”±ä¸šåŠ¡æ¨¡å—ï¼ˆsmartor)实现并注册为 Spring Bean;若容器里没有实现,本模块会退化为使用
 * ä»£ç å†…静态配置(等价于 {@code MsgLSEnum})且不换 token。
 * <p>
 * æŽ¨èå®žçŽ°æ”¾åœ¨ smartor æ¨¡å—,内部通过 {@code ISysConfigService} æŸ¥è¯¢ {@code ls.msg.info}。
 */
public interface LishuiMessageConfigProvider {
    /**
     * æŸ¥è¯¢æŒ‡å®šæœºæž„的丽水短信配置
     *
     * @param orgid æœºæž„ID
     * @return é…ç½®ï¼›æŸ¥ä¸åˆ°è¿”回 {@code null}
     */
    LishuiMessageConfig getConfig(String orgid);
    /**
     * èŽ·å–ä¸½æ°´ 360 å¹³å° token
     *
     * @param orgid    æœºæž„ID
     * @param tokenUrl token èŽ·å–åœ°å€
     * @return token;获取失败返回 {@code null}
     */
    String getToken(String orgid, String tokenUrl);
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/ls/LishuiSmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,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;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/ls/LishuiStaticConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package com.ruoyi.message.sms.provider.ls;
/**
 * ä¸½æ°´æœºæž„配置的静态兜底。
 * <p>
 * è¯´æ˜Žï¼šåŽŸ {@code com.ruoyi.common.enums.MsgLSEnum} é‡Œä¸¤ä¸ªæœºæž„(景宁、丽水市中医院)
 * çš„具体地址/租户/发送人配置已被整段注释掉,线上实际依赖 {@code sys_config} è¡¨çš„
 * {@code ls.msg.info}(见 {@code RyTask.ls åˆ†æ”¯} ä¸Ž {@code SmsRecordsServiceImpl.sendSMS})。
 * <p>
 * å› æ­¤è¿™é‡Œä¸åšé™æ€å…œåº•:静态兜底一旦给出「地址为空」的配置,反而会把线上原本能正常
 * æ¢å– token çš„æµç¨‹æ‰“断(见 {@link LishuiSmsSender#resolveConfig})。
 * è‹¥ç¡®å®žéœ€è¦é™æ€é…ç½®ï¼Œè¯·å®žçް {@link LishuiMessageConfigProvider}(推荐,走 {@code sys_config})。
 * <p>
 * ä¿ç•™æœ¬ç±»æ˜¯ä¸ºäº†æ ‡æ³¨è¯¥åŽ†å²é—ç•™ç‚¹ï¼Œé¿å…åŽäººé‡å¤è¸©å‘ã€‚
 */
final class LishuiStaticConfig {
    private LishuiStaticConfig() {
    }
    /**
     * æ°¸è¿œè¿”回 {@code null},表示「没有静态兜底」,由调用方决定如何处理。
     */
    static LishuiMessageConfig fallback(String orgid) {
        return null;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/MessagingInsertSmsApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
package com.ruoyi.message.sms.provider.messaginginsert;
import com.ruoyi.message.config.MessageProperties;
import com.ruoyi.message.sms.provider.messaginginsert.ws.MessagingInsertLocator;
import com.ruoyi.message.sms.provider.messaginginsert.ws.MessagingInsertSoap12Stub;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.xml.rpc.holders.IntHolder;
import javax.xml.rpc.holders.StringHolder;
import java.util.Date;
/**
 * é¢„约挂号短信平台(MessagingInsert)API。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šAxis SOAP12,{@code SMSMessageAccept(xmlReq, out int, out string)},
 * è¯·æ±‚报文为 {@code &lt;Request&gt;&lt;operate&gt;S001&lt;/operate&gt;...} çš„ XML。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸé€»è¾‘åœ¨ {@code com.ruoyi.system.service.impl.SmsParamServiceImpl.sendSmsInfo},
 * çŽ°æ”¶å£åˆ°æœ¬æ¨¡å—ï¼›åŽŸå®žçŽ°æŠŠæœåŠ¡åœ°å€ç¡¬ç¼–ç åœ¨ç”Ÿæˆçš„ {@code MessagingInsertLocator} é‡Œï¼Œ
 * è¿™é‡Œæ”¹ä¸ºæŒ‰ {@code message.sms.messaging-insert.ws-url} æ˜¾å¼è®¾ç½®ï¼ˆé»˜è®¤å€¼ä¸ŽåŽŸç¡¬ç¼–ç ä¸€è‡´ï¼‰ã€‚
 */
@Slf4j
@Component
public class MessagingInsertSmsApi {
    private final MessageProperties properties;
    public MessagingInsertSmsApi(MessageProperties properties) {
        this.properties = properties;
    }
    /**
     * å‘送预约挂号类短信(operate=S001,Type=预约挂号)
     *
     * @param content çŸ­ä¿¡å†…容
     * @param phone   æ‰‹æœºå·
     * @return å¹³å°è¿”回码
     */
    public Integer sendSmsInfo(String content, String phone) {
        MessagingInsertSoap12Stub binding;
        try {
            MessagingInsertLocator locator = new MessagingInsertLocator();
            String wsUrl = properties.getSms().getMessagingInsert().getWsUrl();
            if (wsUrl != null && !wsUrl.trim().isEmpty()) {
                locator.setMessagingInsertSoap12EndpointAddress(wsUrl);
                locator.setMessagingInsertSoapEndpointAddress(wsUrl);
            }
            binding = (MessagingInsertSoap12Stub) locator.getMessagingInsertSoap12();
        } catch (Exception e) {
            log.error("【MessagingInsertSmsApi】初始化 SOAP å®¢æˆ·ç«¯å¤±è´¥ï¼š{}", e.getMessage(), e);
            return null;
        }
        String xmlReq = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                + "<Request>\n"
                + "<operate>S001</operate>\n"
                + "<Sheets>\n"
                + "<Sheet>\n"
                + "<Content>" + content + "</Content>\n"
                + "<Mobile>" + phone + "</Mobile>\n"
                + "<DateTime>" + new Date() + "</DateTime>\n"
                + "<Type>预约挂号</Type>\n"
                + "</Sheet>\n"
                + "</Sheets>\n"
                + "</Request>";
        Integer result = null;
        try {
            result = binding.SMSMessageAccept(xmlReq, new IntHolder(), new StringHolder());
        } catch (Exception e) {
            log.error("【MessagingInsertSmsApi】短信发送失败:{}", e.getMessage(), e);
        }
        return result;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/MessagingInsertSmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package com.ruoyi.message.sms.provider.messaginginsert;
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.springframework.stereotype.Component;
/**
 * é¢„约挂号短信平台发送方({@code messaging-insert} é€šé“)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šAxis SOAP12 {@code SMSMessageAccept(XML)},返回 int。
 * <p>
 * è¯´æ˜Žï¼šè¯¥é€šé“目前只在 {@code SmsParamServiceImpl.sendSmsInfo} é‡Œä½¿ç”¨ï¼ˆé¢„约挂号通知短信),
 * ä¸šåŠ¡éšè®¿æµç¨‹æœªèµ°å®ƒã€‚æ”¶å£åŽå¯ç”±å·¥åŽ‚ç»Ÿä¸€è°ƒåº¦ã€‚
 */
@Slf4j
@Component
public class MessagingInsertSmsSender extends AbstractSmsSender {
    /**
     * æ¸ é“ key
     */
    public static final String KEY = "messaging-insert";
    private final MessagingInsertSmsApi messagingInsertSmsApi;
    public MessagingInsertSmsSender(MessagingInsertSmsApi messagingInsertSmsApi) {
        this.messagingInsertSmsApi = messagingInsertSmsApi;
    }
    @Override
    public String key() {
        return KEY;
    }
    @Override
    public String description() {
        return "预约挂号短信平台(MessagingInsert,Axis SOAP12)";
    }
    @Override
    protected SendResult doSend(SmsSendRequest request) {
        Integer code = messagingInsertSmsApi.sendSmsInfo(request.getContent(), request.getPhone());
        String codeStr = code == null ? null : code.toString();
        //TODO å¾…确认:原实现未对返回 int åšæˆåŠŸåˆ¤å®šï¼ŒåªæŠŠ int å‘上返回。
        //     è¿™é‡ŒæŒ‰ã€Œéžç©ºå³æˆåŠŸã€å¤„ç†ä»¥ä¿æŒè¡Œä¸ºï¼ˆè°ƒç”¨æ–¹åŽŸæœ¬ä¹Ÿä¸åˆ¤æ–­ï¼‰ï¼Œç¡®è®¤è¿”å›žç è¯­ä¹‰åŽåº”æ”¹ä¸ºç²¾ç¡®åˆ¤å®šã€‚
        if (code != null) {
            return SendResult.success(KEY, MessageChannel.SMS, codeStr).withCode(codeStr);
        }
        return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败", null);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsert.java
ÎļþÃû´Ó ruoyi-system/src/main/java/WebServiceClient/MessagingInsert.java ÐÞ¸Ä
@@ -5,7 +5,7 @@
 * by the Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java emitter.
 */
package WebServiceClient;
package com.ruoyi.message.sms.provider.messaginginsert.ws;
public interface MessagingInsert extends javax.xml.rpc.Service {
    public String getMessagingInsertSoap12Address();
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertLocator.java
ÎļþÃû´Ó ruoyi-system/src/main/java/WebServiceClient/MessagingInsertLocator.java ÐÞ¸Ä
@@ -5,7 +5,7 @@
 * by the Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java emitter.
 */
package WebServiceClient;
package com.ruoyi.message.sms.provider.messaginginsert.ws;
public class MessagingInsertLocator extends org.apache.axis.client.Service implements MessagingInsert {
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap12Stub.java
ÎļþÃû´Ó ruoyi-system/src/main/java/WebServiceClient/MessagingInsertSoap12Stub.java ÐÞ¸Ä
@@ -5,7 +5,7 @@
 * by the Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java emitter.
 */
package WebServiceClient;
package com.ruoyi.message.sms.provider.messaginginsert.ws;
public class MessagingInsertSoap12Stub extends org.apache.axis.client.Stub implements MessagingInsertSoap_PortType {
    private java.util.Vector cachedSerClasses = new java.util.Vector();
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap_BindingStub.java
ÎļþÃû´Ó ruoyi-system/src/main/java/WebServiceClient/MessagingInsertSoap_BindingStub.java ÐÞ¸Ä
@@ -5,7 +5,7 @@
 * by the Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java emitter.
 */
package WebServiceClient;
package com.ruoyi.message.sms.provider.messaginginsert.ws;
import org.apache.axis.client.Stub;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap_PortType.java
ÎļþÃû´Ó ruoyi-system/src/main/java/WebServiceClient/MessagingInsertSoap_PortType.java ÐÞ¸Ä
@@ -5,7 +5,7 @@
 * by the Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java emitter.
 */
package WebServiceClient;
package com.ruoyi.message.sms.provider.messaginginsert.ws;
public interface MessagingInsertSoap_PortType extends java.rmi.Remote {
    public Integer SMSMessageAccept(String xmlReq, javax.xml.rpc.holders.IntHolder SMSMessageAcceptResult, javax.xml.rpc.holders.StringHolder xmlResp) throws java.rmi.RemoteException;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/nhfy/MtSubmitSmResp.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package com.ruoyi.message.sms.provider.nhfy;
/**
 * MT æ¶ˆæ¯å‘送响应参数(行业汇聚平台 {@code /submitsm} çš„返回体)。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸç±»ä¸º {@code com.smartor.domain.MtSubmitSmResp},随发送方收口到 {@code ruoyi-message}。
 */
public class MtSubmitSmResp {
    /**
     * ä¸‹å‘状态,0 æˆåŠŸï¼Œå…¶ä»–å¤±è´¥
     */
    private Integer code;
    /**
     * å¤±è´¥æ—¶çš„错误提示
     */
    private String message;
    /**
     * å¤±è´¥æ—¶çš„错误提示
     */
    private String success;
    /**
     * å“åº”结果
     */
    private String result;
    /**
     * ä¸‹å‘状态=0时有效,为系统产生的 msgid,如发生了长短信拆分,则返回多条 msgid
     */
    private java.util.List<String> mtSubmitSm;
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public String getSuccess() {
        return success;
    }
    public void setSuccess(String success) {
        this.success = success;
    }
    public String getResult() {
        return result;
    }
    public void setResult(String result) {
        this.result = result;
    }
    public java.util.List<String> getMtSubmitSm() {
        return mtSubmitSm;
    }
    public void setMtSubmitSm(java.util.List<String> mtSubmitSm) {
        this.mtSubmitSm = mtSubmitSm;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/nhfy/MtSubmitSmUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
package com.ruoyi.message.sms.provider.nhfy;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.message.config.MessageProperties;
import com.ruoyi.message.config.MtSmsConfig;
import com.ruoyi.message.config.RootMessageProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
 * MT消息发送工具类(SP å‘行业汇聚平台提交 MT æ¶ˆæ¯ï¼‰ã€‚
 * <p>
 * æŽ¥å£åœ°å€ï¼š{@code http://ip:port/submitsm};编码:UTF-8;鉴权算法:MD5。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸç±»ä¸º {@code com.smartor.common.MtSubmitSmUtil}({@code @Component} + {@code @Value} æ³¨å…¥ï¼‰ï¼Œ
 * è¿ç§»åŽé…ç½®æ”¹ä¸ºä»Ž {@link MessageProperties} è¯»å–(底层仍是同一批 configuration key),行为不变。
 */
@Slf4j
@Component
public class MtSubmitSmUtil {
    private static final RestTemplate REST_TEMPLATE = new RestTemplate();
    private final MessageProperties properties;
    public MtSubmitSmUtil(MessageProperties properties) {
        this.properties = properties;
    }
    /**
     * å‘送 MT æ¶ˆæ¯
     *
     * @param destAddr ç›®æ ‡å·ç 
     * @param content  çŸ­ä¿¡å†…容
     * @return å“åº”结果
     */
    public Map<String, Object> submitSm(String destAddr, String content) {
        MtSmsConfig mt = properties.getSms().getMt();
        try {
            log.info("南华附一发送短信,destAddr:{},content:{}", destAddr, content);
            String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            String hashHex = buildHashHex(mt.getAccountName(), mt.getPassword(), date);
            Map<String, Object> authMap = new HashMap<>();
            authMap.put("Algorithm", "MD5");
            authMap.put("Date", date);
            authMap.put("HASHHEX", hashHex);
            Map<String, Object> reqMap = new HashMap<>();
            reqMap.put("accountName", mt.getAccountName());
            reqMap.put("Authorization", authMap);
            reqMap.put("DestAddr", destAddr);
            reqMap.put("SourceAddr", mt.getSourceAddr());
            reqMap.put("Content", mt.getFlag() + content);
            reqMap.put("ServiceCode", mt.getServiceCode());
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(new MediaType("application", "json", StandardCharsets.UTF_8));
            headers.set("Accept-Language", "UTF-8");
            //TODO å¾…确认:原实现把 app-key å†™æ­»ä¸º ak-BFq4NN0TMf92J7KAioNYGz74,
            //     è€Œ yml é‡ŒåŒæ—¶å­˜åœ¨ sms_appkey é…ç½®é¡¹å´æœªè¢«ä½¿ç”¨ã€‚这里为保持线上行为仍用原硬编码值,
            //     å»ºè®®æ”¹ä¸ºè¯»å– mt.getAppkey()(已支持)并确认哪个才是正确的 app-key。
            headers.set("app-key", "ak-BFq4NN0TMf92J7KAioNYGz74");
            HttpEntity<String> entity = new HttpEntity<>(JSON.toJSONString(reqMap), headers);
            log.info("MtSubmitSm è¯·æ±‚地址:{},请求参数:{}", mt.getUrl() + "/submitsm", JSON.toJSONString(reqMap));
            ResponseEntity<String> response = REST_TEMPLATE.postForEntity(mt.getUrl(), entity, String.class);
            log.info("MtSubmitSm å“åº”结果:{}", response.getBody());
            return JSON.parseObject(response.getBody(), Map.class);
        } catch (Exception e) {
            log.error("MtSubmitSm å‘送失败,url:{},destAddr:{},error:{}", mt.getUrl(), destAddr, e.getMessage(), e);
            Map<String, Object> resp = new HashMap<>();
            resp.put("code", -1);
            resp.put("message", "发送异常:" + e.getMessage());
            return resp;
        }
    }
    /**
     * è®¡ç®— HASHHEX:MD5(Algorithm:帐号名:密码:Date)
     * <p>
     * ä¾‹ï¼š{@code MD5:95555:12345:2016-10-09 16:19:43}
     */
    private static String buildHashHex(String accountName, String password, String date) throws Exception {
        String plain = "MD5:" + accountName + ":" + password + ":" + date;
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] bytes = md.digest(plain.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
    /**
     * åˆ¤æ–­ MT å“åº”是否成功({@code result == "0"})
     */
    public static boolean isSuccess(Map<String, Object> resp) {
        if (resp == null) {
            return false;
        }
        Object result = resp.get("result");
        return result != null && "0".equals(result.toString());
    }
    /**
     * åˆ¤æ–­å“åº”çš„ result å­—段是否为空
     */
    public static boolean hasResult(Map<String, Object> resp) {
        return resp != null && StringUtils.isNotEmpty(String.valueOf(resp.get("result")));
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/nhfy/NhfySmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package com.ruoyi.message.sms.provider.nhfy;
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.springframework.stereotype.Component;
import java.util.Map;
/**
 * å—华附一短信发送方({@code nhfy} é€šé“)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šREST + è¡Œä¸šæ±‡èšå¹³å° {@code /submitsm},鉴权 {@code HASHHEX = MD5(MD5:账号:密码:时间)},
 * æˆåŠŸåˆ¤å®šä¸ºå“åº” {@code result == "0"}。
 * ä»£ç æ¥æºï¼š{@code RyTask.nhfy åˆ†æ”¯} + {@code RedisMqReceiver.nhfy åˆ†æ”¯} + {@code SmsController} + {@code smsSubTask}。
 */
@Slf4j
@Component
public class NhfySmsSender extends AbstractSmsSender {
    /**
     * æ¸ é“ key,与 {@code spring.profiles.active=nhfy} å¯¹åº”
     */
    public static final String KEY = "nhfy";
    private final MtSubmitSmUtil mtSubmitSmUtil;
    public NhfySmsSender(MtSubmitSmUtil mtSubmitSmUtil) {
        this.mtSubmitSmUtil = mtSubmitSmUtil;
    }
    @Override
    public String key() {
        return KEY;
    }
    @Override
    public String description() {
        return "南华附一短信(行业汇聚平台 /submitsm)";
    }
    @Override
    protected SendResult doSend(SmsSendRequest request) {
        Map<String, Object> resp = mtSubmitSmUtil.submitSm(request.getPhone(), request.getContent());
        log.info("【{}】南华附一短信发送结果:{}", KEY, resp);
        Object result = resp == null ? null : resp.get("result");
        String code = result == null ? null : result.toString();
        if (MtSubmitSmUtil.isSuccess(resp)) {
            return SendResult.success(KEY, MessageChannel.SMS, String.valueOf(resp)).withCode(code);
        }
        return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败", String.valueOf(resp)).withCode(code);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/shiyi/ShiyiHisSmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package com.ruoyi.message.sms.provider.shiyi;
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.springframework.stereotype.Component;
/**
 * æ­å·žå¸‚一 HIS å¹³å°çŸ­ä¿¡å‘送方({@code shiyi-his} é€šé“)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šWCF SOAP {@code RunService(TradeType=FASONGDX)},报文体为 XML,
 * æˆåŠŸåˆ¤å®šä¸ºå“åº” {@code ERRNO == "0"}。
 * <p>
 * è¯´æ˜Žï¼šè¯¥é€šé“目前<b>还没有业务调用点</b>(只有 {@code /sms/sendShiyiTest} æµ‹è¯•接口),
 * æ”¶å£åˆ°æœ¬æ¨¡å—后可由工厂统一调度。
 * ä»£ç æ¥æºï¼šåŽŸ {@code com.smartor.common.ShiyiSmsUtil}(行为保持不变)。
 */
@Slf4j
@Component
public class ShiyiHisSmsSender extends AbstractSmsSender {
    /**
     * æ¸ é“ key(不是 profile åï¼Œå› ä¸ºå½“前站点都会走自己的通道)
     */
    public static final String KEY = "shiyi-his";
    private final ShiyiSmsUtil shiyiSmsUtil;
    public ShiyiHisSmsSender(ShiyiSmsUtil shiyiSmsUtil) {
        this.shiyiSmsUtil = shiyiSmsUtil;
    }
    @Override
    public String key() {
        return KEY;
    }
    @Override
    public String description() {
        return "杭州市一 HIS å¹³å°çŸ­ä¿¡ï¼ˆWCF SOAP FASONGDX)";
    }
    @Override
    protected SendResult doSend(SmsSendRequest request) {
        ShiyiSmsRequest smsRequest = new ShiyiSmsRequest();
        smsRequest.setShoujihao(request.getPhone());
        smsRequest.setDuanxinnr(request.getContent());
        smsRequest.setJigoudm(request.getOrgid());
        if (request.getTemplateCode() != null) {
            smsRequest.setDuanxinlx(request.getTemplateCode());
        }
        ShiyiSmsResponse response = shiyiSmsUtil.sendSms(smsRequest);
        if (response.isSuccess()) {
            return SendResult.success(KEY, MessageChannel.SMS, response.getRawXml())
                    .withCode(response.getErrno())
                    .withMessage(response.getErrmsg());
        }
        return SendResult.fail(KEY, MessageChannel.SMS,
                "短信发送失败:" + response.getErrmsg(), response.getRawXml())
                .withCode(response.getErrno());
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsRequest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,197 @@
package com.ruoyi.message.sms.provider.shiyi;
/**
 * æ­å·žå¸‚一 HIS æœåŠ¡å¹³å° 5.2 çŸ­ä¿¡ä¸šåŠ¡ è¯·æ±‚对象 (FASONGDX)。
 * <p>
 * å¯¹åº” TradeMsg XML æ ¹èŠ‚ç‚¹ {@code FASONGDX_IN}。
 * è¿ç§»è¯´æ˜Žï¼šåŽŸç±»ä¸º {@code com.smartor.domain.ShiyiSmsRequest}。
 */
public class ShiyiSmsRequest {
    /** æ“ä½œå‘˜ä»£ç  */
    private String caozuoydm;
    /** æ“ä½œå‘˜å§“名 */
    private String caozuoyxm;
    /** æ“ä½œæ—¥æœŸ yyyy-MM-dd HH:mm:ss */
    private String caozuorq;
    /** ç³»ç»Ÿæ ‡è¯† */
    private String xitongbs;
    /** åˆ†é™¢ä»£ç  */
    private String fenyuandm;
    /** æœºæž„代码 */
    private String jigoudm;
    /** æœºæž„名称 */
    private String jigoumc;
    /** æœºæž„验证码 */
    private String jigouyzm;
    /** æŽ¥æ”¶æœºæž„代码 */
    private String jieshoujgdm;
    /** ç»ˆç«¯çº§åˆ« */
    private String zhongduanjb;
    /** ç»ˆç«¯æµæ°´ */
    private String zhongduanls;
    /** IP åœ°å€ */
    private String ipaddress;
    /** ä¸šåŠ¡ç±»åž‹ */
    private String yewulx;
    /** æŽ¥å…¥åœºæ‰€ä»£ç  */
    private String jierucsdm;
    /** çŸ­ä¿¡ç±»åž‹ 0:普通短信 */
    private String duanxinlx;
    /** æ‰‹æœºå· */
    private String shoujihao;
    /** çŸ­ä¿¡å†…容 */
    private String duanxinnr;
    public String getCaozuoydm() {
        return caozuoydm;
    }
    public void setCaozuoydm(String caozuoydm) {
        this.caozuoydm = caozuoydm;
    }
    public String getCaozuoyxm() {
        return caozuoyxm;
    }
    public void setCaozuoyxm(String caozuoyxm) {
        this.caozuoyxm = caozuoyxm;
    }
    public String getCaozuorq() {
        return caozuorq;
    }
    public void setCaozuorq(String caozuorq) {
        this.caozuorq = caozuorq;
    }
    public String getXitongbs() {
        return xitongbs;
    }
    public void setXitongbs(String xitongbs) {
        this.xitongbs = xitongbs;
    }
    public String getFenyuandm() {
        return fenyuandm;
    }
    public void setFenyuandm(String fenyuandm) {
        this.fenyuandm = fenyuandm;
    }
    public String getJigoudm() {
        return jigoudm;
    }
    public void setJigoudm(String jigoudm) {
        this.jigoudm = jigoudm;
    }
    public String getJigoumc() {
        return jigoumc;
    }
    public void setJigoumc(String jigoumc) {
        this.jigoumc = jigoumc;
    }
    public String getJigouyzm() {
        return jigouyzm;
    }
    public void setJigouyzm(String jigouyzm) {
        this.jigouyzm = jigouyzm;
    }
    public String getJieshoujgdm() {
        return jieshoujgdm;
    }
    public void setJieshoujgdm(String jieshoujgdm) {
        this.jieshoujgdm = jieshoujgdm;
    }
    public String getZhongduanjb() {
        return zhongduanjb;
    }
    public void setZhongduanjb(String zhongduanjb) {
        this.zhongduanjb = zhongduanjb;
    }
    public String getZhongduanls() {
        return zhongduanls;
    }
    public void setZhongduanls(String zhongduanls) {
        this.zhongduanls = zhongduanls;
    }
    public String getIpaddress() {
        return ipaddress;
    }
    public void setIpaddress(String ipaddress) {
        this.ipaddress = ipaddress;
    }
    public String getYewulx() {
        return yewulx;
    }
    public void setYewulx(String yewulx) {
        this.yewulx = yewulx;
    }
    public String getJierucsdm() {
        return jierucsdm;
    }
    public void setJierucsdm(String jierucsdm) {
        this.jierucsdm = jierucsdm;
    }
    public String getDuanxinlx() {
        return duanxinlx;
    }
    public void setDuanxinlx(String duanxinlx) {
        this.duanxinlx = duanxinlx;
    }
    public String getShoujihao() {
        return shoujihao;
    }
    public void setShoujihao(String shoujihao) {
        this.shoujihao = shoujihao;
    }
    public String getDuanxinnr() {
        return duanxinnr;
    }
    public void setDuanxinnr(String duanxinnr) {
        this.duanxinnr = duanxinnr;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsResponse.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
package com.ruoyi.message.sms.provider.shiyi;
/**
 * æ­å·žå¸‚一 HIS æœåŠ¡å¹³å° 5.2 çŸ­ä¿¡ä¸šåŠ¡ å“åº”对象 (FASONGDX_OUT)。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸç±»ä¸º {@code com.smartor.domain.ShiyiSmsResponse}。
 */
public class ShiyiSmsResponse {
    /** é”™è¯¯å·ï¼Œ0 æˆåŠŸï¼›1 å¤±è´¥ï¼›2 HIS å¼‚常;99 å‚数异常;&lt;0 ç³»ç»Ÿå¼‚常 */
    private String errno;
    /** é”™è¯¯æè¿° */
    private String errmsg;
    /** æ‰©å±•错误描述 */
    private String errmsgex;
    /** HIS ä¾§æ¶ˆæ¯ ID */
    private String messageId;
    /** çŸ­ä¿¡ ID */
    private String duanxinid;
    /** åŽŸå§‹å“åº” XML(调试用) */
    private String rawXml;
    public boolean isSuccess() {
        return "0".equals(errno);
    }
    public String getErrno() {
        return errno;
    }
    public void setErrno(String errno) {
        this.errno = errno;
    }
    public String getErrmsg() {
        return errmsg;
    }
    public void setErrmsg(String errmsg) {
        this.errmsg = errmsg;
    }
    public String getErrmsgex() {
        return errmsgex;
    }
    public void setErrmsgex(String errmsgex) {
        this.errmsgex = errmsgex;
    }
    public String getMessageId() {
        return messageId;
    }
    public void setMessageId(String messageId) {
        this.messageId = messageId;
    }
    public String getDuanxinid() {
        return duanxinid;
    }
    public void setDuanxinid(String duanxinid) {
        this.duanxinid = duanxinid;
    }
    public String getRawXml() {
        return rawXml;
    }
    public void setRawXml(String rawXml) {
        this.rawXml = rawXml;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsUtil.java
ÎļþÃû´Ó smartor/src/main/java/com/smartor/common/ShiyiSmsUtil.java ÐÞ¸Ä
@@ -1,66 +1,64 @@
package com.smartor.common;
package com.ruoyi.message.sms.provider.shiyi;
import com.smartor.domain.ShiyiSmsRequest;
import com.smartor.domain.ShiyiSmsResponse;
import com.ruoyi.message.config.MessageProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * æ­å·žå¸‚一 HIS æœåŠ¡å¹³å°å¯¹æŽ¥å·¥å…·ç±»ã€‚
 * æ­å·žå¸‚一 HIS æœåŠ¡å¹³å°å¯¹æŽ¥å·¥å…·ç±»ï¼ˆçŸ­ä¿¡ï¼‰ã€‚
 * <p>
 * æŒ‰ã€ŠHIS æœåŠ¡å¹³å°æŽ¥å£è¯´æ˜Žæ–‡æ¡£ V1.1》3.1 èŠ‚çº¦å®šï¼ŒHIS å¯¹å¤–以 WCF Web Service å½¢å¼å‘布,
 * ç»Ÿä¸€å…¥å£ï¼š{@code int RunService(string TradeType, string TradeMsg, ref string TradeOut)},
 * å…¶ä¸­ {@code TradeType} æ ‡è¯†ä¸šåŠ¡ç¼–ç ï¼Œ{@code TradeMsg} ä¸º UTF-8 ç¼–码的 XML æŠ¥æ–‡ã€‚
 * æœ¬å·¥å…·ç±»ç›®å‰å®žçް 5.2 çŸ­ä¿¡ä¸šåŠ¡ (TradeType=FASONGDX)。
 * æœ¬ç±»å®žçް 5.2 çŸ­ä¿¡ä¸šåŠ¡ (TradeType=FASONGDX)。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸç±»ä¸º {@code com.smartor.common.ShiyiSmsUtil}。差异点:
 * <ul>
 *   <li>配置由 {@code @Value("${his.service.defaultCaozuoydm:}")} æ”¹ä¸ºä»Ž {@link MessageProperties} è¯»å–
 *       ï¼ˆå‰ç¼€ {@code message.sms.shiyi.*}),原 key ä¸å†éœ€è¦ï¼›</li>
 *   <li>接口地址由硬编码改为 {@code message.sms.shiyi.ws-url},默认值与原硬编码一致
 *       ï¼ˆ{@code http://192.178.0.104:7790/MediInfoHis.svc})。</li>
 * </ul>
 */
@Slf4j
@Component
public class ShiyiSmsUtil {
    /** 5.2 çŸ­ä¿¡ä¸šåŠ¡ TradeType */
    /**
     * 5.2 çŸ­ä¿¡ä¸šåŠ¡ TradeType
     */
    public static final String TRADE_TYPE_FASONGDX = "FASONGDX";
    /** WCF å‘½åç©ºé—´ï¼Œé»˜è®¤éšæ–‡æ¡£ç¤ºä¾‹ */
    private String namespace = "http://tempuri.org/";
    /**
     * WCF å‘½åç©ºé—´ï¼Œé»˜è®¤éšæ–‡æ¡£ç¤ºä¾‹
     */
    private final String namespace = "http://tempuri.org/";
    /** é»˜è®¤æ“ä½œå‘˜ä»£ç  */
    @Value("${his.service.defaultCaozuoydm:}")
    private String defaultCaozuoydm;
    /** é»˜è®¤æ“ä½œå‘˜å§“名 */
    @Value("${his.service.defaultCaozuoyxm:}")
    private String defaultCaozuoyxm;
    /** é»˜è®¤ç³»ç»Ÿæ ‡è¯† */
    private String defaultXitongbs = "0";
    /** é»˜è®¤åˆ†é™¢ä»£ç  */
    private String defaultFenyuandm = "1";
    /** é»˜è®¤æœºæž„代码 */
    private String defaultJigoudm = "1";
    private final MessageProperties properties;
    private final RestTemplate restTemplate = new RestTemplate();
    public ShiyiSmsUtil(MessageProperties properties) {
        this.properties = properties;
    }
    /**
     * å‘送短信 (FASONGDX)
@@ -88,13 +86,13 @@
    }
    /**
     * è°ƒç”¨ å¸‚一 WCF RunService æŽ¥å£ã€‚SOAP 1.1 åŸºç¡€é‰´æƒæŒ‰æ–‡æ¡£ä¸è¦æ±‚。
     * è°ƒç”¨å¸‚一 WCF RunService æŽ¥å£ã€‚SOAP 1.1 åŸºç¡€é‰´æƒæŒ‰æ–‡æ¡£ä¸è¦æ±‚。
     *
     * @return TradeOut æŠ¥æ–‡ï¼ˆæœåŠ¡ç«¯ ref å‚数)
     */
    public String invokeRunService(String tradeType, String tradeMsg) {
        String soapEnvelope = buildSoapEnvelope(tradeType, tradeMsg);
        String hisServiceUrl = "http://192.178.0.104:7790/MediInfoHis.svc";
        String hisServiceUrl = properties.getSms().getShiyi().getWsUrl();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(new MediaType("text", "xml", StandardCharsets.UTF_8));
@@ -115,23 +113,24 @@
    }
    private void applyDefaults(ShiyiSmsRequest request) {
        MessageProperties.ShiyiSms shiyi = properties.getSms().getShiyi();
        if (StringUtils.isBlank(request.getCaozuoydm())) {
            request.setCaozuoydm(defaultCaozuoydm);
            request.setCaozuoydm(shiyi.getDefaultCaozuoydm());
        }
        if (StringUtils.isBlank(request.getCaozuoyxm())) {
            request.setCaozuoyxm(defaultCaozuoyxm);
            request.setCaozuoyxm(shiyi.getDefaultCaozuoyxm());
        }
        if (StringUtils.isBlank(request.getCaozuorq())) {
            request.setCaozuorq(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        }
        if (StringUtils.isBlank(request.getXitongbs())) {
            request.setXitongbs(defaultXitongbs);
            request.setXitongbs("0");
        }
        if (StringUtils.isBlank(request.getFenyuandm())) {
            request.setFenyuandm(defaultFenyuandm);
            request.setFenyuandm("1");
        }
        if (StringUtils.isBlank(request.getJigoudm())) {
            request.setJigoudm(defaultJigoudm);
            request.setJigoudm("1");
        }
        if (StringUtils.isBlank(request.getDuanxinlx())) {
            request.setDuanxinlx("0");
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/SltdResponseParser.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
package com.ruoyi.message.sms.provider.sltd;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
/**
 * çœç«‹åŒå¾·çŸ­ä¿¡å“åº”解析。
 * <p>
 * çŽ°çŠ¶ï¼ˆåŒä¸€ä¾›åº”å•†ã€ä¸¤å¥—å†™æ³•ï¼Œå¿…é¡»éƒ½å…¼å®¹ï¼Œå¦åˆ™æ”¹äº†å°±æ˜¯å›žå½’ï¼‰ï¼š
 * <ul>
 *   <li>{@code RyTask} / {@code RedisMqReceiver}:按 {@code result=0&description=成功&faillist=} è§£æžï¼Œ
 *       å– {@code result == "0"} ä¸ºæˆåŠŸï¼›</li>
 *   <li>{@code SmsController./sms/send}:按 JSON è§£æž {@code result[0].status == "00000"} ä¸ºæˆåŠŸã€‚</li>
 * </ul>
 * TODO å¾…确认:以上两种返回判定到底哪个才是省立同德网关的真实返回格式,需业务确认后统一。
 * æœ¬è§£æžå™¨ä¸¤ç§éƒ½å…¼å®¹ï¼Œä¿è¯è¿ç§»åŽè¡Œä¸ºä¸Žçº¿ä¸Šä¸€è‡´ã€‚
 */
final class SltdResponseParser {
    private SltdResponseParser() {
    }
    /**
     * è§£æžç»“果值(用于日志与排查)
     */
    static String resultValue(String raw) {
        if (StringUtils.isEmpty(raw)) {
            return null;
        }
        String keyValue = readKeyValue(raw, "result");
        if (keyValue != null) {
            return keyValue;
        }
        return readJsonStatus(raw);
    }
    /**
     * æ˜¯å¦å‘送成功:兼容 {@code result=0} ä¸Ž {@code result[0].status=00000} ä¸¤ç§åˆ¤å®š
     */
    static boolean isSuccess(String raw) {
        if (StringUtils.isEmpty(raw)) {
            return false;
        }
        String keyValue = readKeyValue(raw, "result");
        if ("0".equals(keyValue)) {
            return true;
        }
        return "00000".equals(readJsonStatus(raw));
    }
    /**
     * ä»Ž {@code a=1&b=2} å½¢å¼çš„响应里取值
     */
    private static String readKeyValue(String raw, String key) {
        String[] pairs = raw.split("&");
        for (String pair : pairs) {
            String[] kv = pair.split("=", 2);
            if (key.equals(kv[0])) {
                return kv.length > 1 ? kv[1] : null;
            }
        }
        return null;
    }
    /**
     * ä»Ž JSON å“åº”里取 {@code result[0].status}
     */
    private static String readJsonStatus(String raw) {
        try {
            JSONObject root = JSON.parseObject(raw);
            if (root == null) {
                return null;
            }
            JSONArray result = root.getJSONArray("result");
            if (result == null || result.isEmpty()) {
                return null;
            }
            JSONObject first = result.getJSONObject(0);
            return first == null ? null : first.getString("status");
        } catch (Exception e) {
            //不是 JSON,属于正常情况(取决于网关返回格式),交由 key-value åˆ†æ”¯å¤„理
            return null;
        }
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/SltdSmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package com.ruoyi.message.sms.provider.sltd;
import com.ruoyi.message.MessageChannel;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.AbstractSmsSender;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.provider.sltd.ws.MessageSend;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * çœç«‹åŒå¾·çŸ­ä¿¡å‘送方({@code sltd} é€šé“)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šJAX-WS SOAP ç½‘关({@code i5GSend}),短信类型固定 {@code 6}(内容含链接和电话),
 * æˆåŠŸåˆ¤å®šè§ {@link SltdResponseParser}。
 * ä»£ç æ¥æºï¼š{@code RyTask.sltd åˆ†æ”¯} + {@code RedisMqReceiver.sltd åˆ†æ”¯}(行为保持不变)。
 */
@Slf4j
@Component
public class SltdSmsSender extends AbstractSmsSender {
    /**
     * æ¸ é“ key,与 {@code spring.profiles.active=sltd} å¯¹åº”
     */
    public static final String KEY = "sltd";
    /**
     * çŸ­ä¿¡ç±»åž‹ï¼šå†…容有链接和电话
     */
    private static final String MESSAGE_TYPE_SMS = "6";
    @Override
    public String key() {
        return KEY;
    }
    @Override
    public String description() {
        return "省立同德短信(SOAP i5GSend,类型6)";
    }
    @Override
    protected SendResult doSend(SmsSendRequest request) {
        String raw = MessageSend.sendMsg(
                request.getContent(),
                MESSAGE_TYPE_SMS,
                request.getPhone(),
                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
        log.info("【{}】省立同德短信发送返回:{}", KEY, raw);
        String code = SltdResponseParser.resultValue(raw);
        if (SltdResponseParser.isSuccess(raw)) {
            return SendResult.success(KEY, MessageChannel.SMS, raw).withCode(code);
        }
        return SendResult.fail(KEY, MessageChannel.SMS, "短信发送失败", raw).withCode(code);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/Hello.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/Hello.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/HelloResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/HelloResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GReply.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/I5GReply.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GReplyResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/I5GReplyResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GReport.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/I5GReport.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GReportResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/I5GReportResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GSearchNumber.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/I5GSearchNumber.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GSearchNumberResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/I5GSearchNumberResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GSend.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/I5GSend.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/I5GSendResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/I5GSendResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/MessageSend.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package com.ruoyi.message.sms.provider.sltd.ws;
import com.ruoyi.common.utils.uuid.UUID;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * çœç«‹åŒå¾·ä¿¡æ¯å‘送接口。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸç±»ä¸º {@code com.ruoyi.common.dx.MessageSend},随「短信发送方」一起收口到
 * {@code ruoyi-message} æ¨¡å—。方法签名与行为保持不变,便于调用方零改动切换。
 */
@Slf4j
public class MessageSend {
    private static final Logger logger = LoggerFactory.getLogger(MessageSend.class);
    /**
     * åŽ‚å®¶è®¸å¯å·ï¼ˆç¡¬ç¼–ç ï¼‰ã€‚
     * <p>
     * TODO å¾…确认:原实现把厂家许可号写死在代码里,未走配置,此处保持原值不变。
     */
    private static final String SERVICE_NUMBER = "202508291634227086";
    /**
     * çŸ­ä¿¡æ¨¡æ¿è®¸å¯å·ï¼ˆå†…容带链接和电话)
     */
    private static final String SMS_TEMPLATE_NUMBER = "T202508291635259440";
    /**
     * é’‰é’‰æ¶ˆæ¯æ¨¡æ¿è®¸å¯å·
     */
    private static final String DINGTALK_TEMPLATE_NUMBER = "T202607211306131891";
    /**
     * çœç«‹åŒå¾·ä¿¡æ¯å‘送
     *
     * @param sMessageContent çŸ­ä¿¡å†…容
     * @param sMessageType    çŸ­ä¿¡ç±»åž‹ 1=验证码、2=通知、3=广告 6=内容有链接和电话,7=钉钉消息
     * @param sUserNumber     æ‰‹æœºå·ç 
     * @param sScheduleTime   é¢„约发送时间
     * @return ç¬¬ä¸‰æ–¹åŽŸå§‹å“åº”
     */
    public static String sendMsg(String sMessageContent, String sMessageType, String sUserNumber, String sScheduleTime) {
        log.info("--sMessageContent çš„值为:{}   ,--sMessageType çš„值为:{},   --sUserNumber çš„值为:{},   --sScheduleTime çš„值为:{}", sMessageContent, sMessageType, sUserNumber, sScheduleTime);
        SmsService service = new SmsService();
        SmsServiceSoap soap = service.getSmsServiceSoap();
        String result = null;
        try {
            if ("6".equals(sMessageType)) {
                result = soap.i5GSend(SERVICE_NUMBER, SMS_TEMPLATE_NUMBER, UUID.randomUUID().toString(), sMessageContent, sMessageType, sUserNumber, sScheduleTime);
                log.info("----短信返回的数据为:{},    ----sMessageContent çš„值为:{}", result, UUID.randomUUID().toString());
            } else if ("7".equals(sMessageType)) {
                result = soap.i5GSend(SERVICE_NUMBER, DINGTALK_TEMPLATE_NUMBER,
                        UUID.randomUUID().toString(), sMessageContent, sMessageType, sUserNumber, sScheduleTime);
                log.info("----钉钉返回的原始数据为:{}", result);
            } else {
                log.warn("不支持的短信类型:{}", sMessageType);
            }
        } catch (Exception e) {
            //原实现为 e.getCause().getMessage(),当 cause ä¸º null æ—¶ä¼šæŠ› NPE æŽ©ç›–真实异常,这里做防御
            String cause = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
            logger.error("完整错误信息:{}", cause);
        }
        return result;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/ObjectFactory.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/ObjectFactory.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlRegistry;
@@ -7,7 +7,7 @@
/**
 * This object contains factory methods for each 
 * Java content interface and Java element interface 
 * generated in the com.ruoyi.common.dx package.
 * generated in the com.ruoyi.message.sms.provider.sltd.ws package.
 * <p>An ObjectFactory allows you to programatically 
 * construct new instances of the Java representation 
 * for XML content. The Java representation of XML 
@@ -23,7 +23,7 @@
    /**
     * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.ruoyi.common.dx
     * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.ruoyi.message.sms.provider.sltd.ws
     * 
     */
    public ObjectFactory() {
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetNumber.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSGetNumber.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetNumberResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSGetNumberResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetTemplateList.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSGetTemplateList.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetTemplateListResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSGetTemplateListResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetUserList.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSGetUserList.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSGetUserListResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSGetUserListResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSSaveContent.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSSaveContent.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSSaveContentResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSSaveContentResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSVerify.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSVerify.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyPrivate.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSVerifyPrivate.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyPrivateResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSVerifyPrivateResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyResponse.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SMSVerifyResponse.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SmsService.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SmsService.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import java.net.MalformedURLException;
import java.net.URL;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/SmsServiceSoap.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/SmsServiceSoap.java ÐÞ¸Ä
@@ -1,5 +1,5 @@
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
import javax.jws.WebMethod;
import javax.jws.WebParam;
@@ -30,8 +30,8 @@
     */
    @WebMethod(operationName = "Hello", action = "http://tempuri.org/Hello")
    @WebResult(name = "HelloResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "Hello", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.Hello")
    @ResponseWrapper(localName = "HelloResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.HelloResponse")
    @RequestWrapper(localName = "Hello", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.Hello")
    @ResponseWrapper(localName = "HelloResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.HelloResponse")
    public String hello();
    /**
@@ -43,8 +43,8 @@
     */
    @WebMethod(operationName = "SMSVerifyPrivate", action = "http://tempuri.org/SMSVerifyPrivate")
    @WebResult(name = "SMSVerifyPrivateResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "SMSVerifyPrivate", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSVerifyPrivate")
    @ResponseWrapper(localName = "SMSVerifyPrivateResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSVerifyPrivateResponse")
    @RequestWrapper(localName = "SMSVerifyPrivate", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSVerifyPrivate")
    @ResponseWrapper(localName = "SMSVerifyPrivateResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSVerifyPrivateResponse")
    public String smsVerifyPrivate(
            @WebParam(name = "sServerNumber", targetNamespace = "http://tempuri.org/")
            String sServerNumber);
@@ -59,8 +59,8 @@
     */
    @WebMethod(operationName = "SMSVerify", action = "http://tempuri.org/SMSVerify")
    @WebResult(name = "SMSVerifyResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "SMSVerify", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSVerify")
    @ResponseWrapper(localName = "SMSVerifyResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSVerifyResponse")
    @RequestWrapper(localName = "SMSVerify", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSVerify")
    @ResponseWrapper(localName = "SMSVerifyResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSVerifyResponse")
    public String smsVerify(
            @WebParam(name = "sServerNumber", targetNamespace = "http://tempuri.org/")
            String sServerNumber,
@@ -76,8 +76,8 @@
     */
    @WebMethod(operationName = "SMSGetUserList", action = "http://tempuri.org/SMSGetUserList")
    @WebResult(name = "SMSGetUserListResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "SMSGetUserList", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSGetUserList")
    @ResponseWrapper(localName = "SMSGetUserListResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSGetUserListResponse")
    @RequestWrapper(localName = "SMSGetUserList", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSGetUserList")
    @ResponseWrapper(localName = "SMSGetUserListResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSGetUserListResponse")
    public String smsGetUserList(
            @WebParam(name = "sUserName", targetNamespace = "http://tempuri.org/")
            String sUserName);
@@ -91,8 +91,8 @@
     */
    @WebMethod(operationName = "SMSGetTemplateList", action = "http://tempuri.org/SMSGetTemplateList")
    @WebResult(name = "SMSGetTemplateListResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "SMSGetTemplateList", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSGetTemplateList")
    @ResponseWrapper(localName = "SMSGetTemplateListResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSGetTemplateListResponse")
    @RequestWrapper(localName = "SMSGetTemplateList", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSGetTemplateList")
    @ResponseWrapper(localName = "SMSGetTemplateListResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSGetTemplateListResponse")
    public String smsGetTemplateList(
            @WebParam(name = "sUserName", targetNamespace = "http://tempuri.org/")
            String sUserName);
@@ -105,8 +105,8 @@
     */
    @WebMethod(operationName = "SMSGetNumber", action = "http://tempuri.org/SMSGetNumber")
    @WebResult(name = "SMSGetNumberResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "SMSGetNumber", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSGetNumber")
    @ResponseWrapper(localName = "SMSGetNumberResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSGetNumberResponse")
    @RequestWrapper(localName = "SMSGetNumber", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSGetNumber")
    @ResponseWrapper(localName = "SMSGetNumberResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSGetNumberResponse")
    public String smsGetNumber();
    /**
@@ -128,8 +128,8 @@
     */
    @WebMethod(operationName = "SMSSaveContent", action = "http://tempuri.org/SMSSaveContent")
    @WebResult(name = "SMSSaveContentResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "SMSSaveContent", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSSaveContent")
    @ResponseWrapper(localName = "SMSSaveContentResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.SMSSaveContentResponse")
    @RequestWrapper(localName = "SMSSaveContent", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSSaveContent")
    @ResponseWrapper(localName = "SMSSaveContentResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.SMSSaveContentResponse")
    public int smsSaveContent(
            @WebParam(name = "sSUseq", targetNamespace = "http://tempuri.org/")
            String sSUseq,
@@ -169,8 +169,8 @@
     */
    @WebMethod(action = "http://tempuri.org/i5GSend")
    @WebResult(name = "i5GSendResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "i5GSend", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.I5GSend")
    @ResponseWrapper(localName = "i5GSendResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.I5GSendResponse")
    @RequestWrapper(localName = "i5GSend", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.I5GSend")
    @ResponseWrapper(localName = "i5GSendResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.I5GSendResponse")
    public String i5GSend(
            @WebParam(name = "sServerNumber", targetNamespace = "http://tempuri.org/")
            String sServerNumber,
@@ -195,8 +195,8 @@
     */
    @WebMethod(action = "http://tempuri.org/i5GReport")
    @WebResult(name = "i5GReportResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "i5GReport", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.I5GReport")
    @ResponseWrapper(localName = "i5GReportResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.I5GReportResponse")
    @RequestWrapper(localName = "i5GReport", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.I5GReport")
    @ResponseWrapper(localName = "i5GReportResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.I5GReportResponse")
    public String i5GReport();
    /**
@@ -207,8 +207,8 @@
     */
    @WebMethod(action = "http://tempuri.org/i5GSearchNumber")
    @WebResult(name = "i5GSearchNumberResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "i5GSearchNumber", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.I5GSearchNumber")
    @ResponseWrapper(localName = "i5GSearchNumberResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.I5GSearchNumberResponse")
    @RequestWrapper(localName = "i5GSearchNumber", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.I5GSearchNumber")
    @ResponseWrapper(localName = "i5GSearchNumberResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.I5GSearchNumberResponse")
    public String i5GSearchNumber();
    /**
@@ -223,8 +223,8 @@
     */
    @WebMethod(action = "http://tempuri.org/i5GReply")
    @WebResult(name = "i5GReplyResult", targetNamespace = "http://tempuri.org/")
    @RequestWrapper(localName = "i5GReply", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.I5GReply")
    @ResponseWrapper(localName = "i5GReplyResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.common.dx.I5GReplyResponse")
    @RequestWrapper(localName = "i5GReply", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.I5GReply")
    @ResponseWrapper(localName = "i5GReplyResponse", targetNamespace = "http://tempuri.org/", className = "com.ruoyi.message.sms.provider.sltd.ws.I5GReplyResponse")
    public String i5GReply(
            @WebParam(name = "sServerNumber", targetNamespace = "http://tempuri.org/")
            String sServerNumber,
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/sltd/ws/package-info.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/dx/package-info.java ÐÞ¸Ä
@@ -1,2 +1,2 @@
@javax.xml.bind.annotation.XmlSchema(namespace = "http://tempuri.org/", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.ruoyi.common.dx;
package com.ruoyi.message.sms.provider.sltd.ws;
ruoyi-message/src/main/java/com/ruoyi/message/sms/provider/xh/XinHuaSmsSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
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} é€šé“,通用短信网关)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šHTTP POST è¡¨å• {@code action/account/password/mobile/content/extno/rt},无签名。
 * <p>
 * âš ï¸ çŽ°æœ‰ä¸‰ä¸ªå…¥å£å¯¹åŒä¸€ä¸ªç½‘å…³çš„è°ƒç”¨æ–¹å¼å¹¶ä¸ä¸€è‡´ï¼Œä¸”å…¶ä¸­ä¸¤ä¸ªæ˜¯åçš„ã€‚ä¸ºæ»¡è¶³ã€ŒæŒ‰ç«™ç‚¹åŽŸæ ·å°è£…ã€
 * ä¸æ”¹çº¿ä¸Šè¡Œä¸ºã€çš„要求,这里把三种模式都保留为独立方法,由调用方按原站点选择:
 * <ul>
 *   <li>{@link #sendJsonLegacy} â€”— ç­‰ä»· {@code RyTask}:拼好表单参数却发送 {@code new Gson().toJson(req)}
 *       ï¼ˆ{@code req} æ˜¯ç©º map,实际发出 {@code {}}),且把 {@code Boolean} å½“ {@code String} è¿”回值,
 *       å¿…然触发 NPE è¢«å¼‚常分支吞掉。结果:**新华站点长期任务短信实际发不出去**;</li>
 *   <li>{@link #sendForm} â€”— ç­‰ä»· {@code RedisMqReceiver}:发送 URL ç¼–码后的表单字符串(正确),
 *       ä½†åŽŸå®žçŽ°å¿½ç•¥äº†è¿”å›žå€¼ï¼Œä»£ç é‡Œå›ºå®šæŒ‰æˆåŠŸå¤„ç†ï¼›</li>
 *   <li>{@link #sendJson} â€”— ç­‰ä»· {@code ServiceSubtaskServiceImpl.smsSubTask} / {@code SmsController}:
 *       å‘送 {@code Gson.toJson(map)} çš„ JSON。</li>
 * </ul>
 * 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<String, String> 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,必然失败)。
     * <p>
     * ä¿ç•™æ­¤æ–¹æ³•是为了让 {@code RyTask} çš„æ”¹é€ å¯ä»¥åšåˆ°ã€Œè¡Œä¸ºä¸€å­—不差」;确认网关格式后应删除。
     */
    public SendResult sendJsonLegacy(SmsSendRequest request) {
        XhsmsConfig cfg = properties.getSms().getXhsms();
        //原实现:Map<String,String> 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<String, String> buildParams(SmsSendRequest request, XhsmsConfig cfg) {
        Map<String, String> 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<String, String> 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;
        }
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatMpConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
package com.ruoyi.message.wechat;
/**
 * å¾®ä¿¡å…¬ä¼—号机构级配置(appid / appSecret / ç½‘关地址)。
 * <p>
 * çŽ°çŠ¶ï¼šè¿™äº›å€¼ç¡¬ç¼–ç åœ¨ {@code com.ruoyi.common.enums.WxGZHEnum} æžšä¸¾é‡Œï¼ˆä¸½æ°´å¸‚中医院、景宁、湖滨、吴山),
 * ç”± {@code WxGZHEnum.getDescByCode(orgid)} è¿”回一个按固定顺序排列的 List,
 * è°ƒç”¨æ–¹é ä¸‹æ ‡ {@code get(1)/get(2)/get(3)} å–值 â€”— è¿™æ˜¯å¾ˆè„†çš„写法(一旦某个机构缺 appid,
 * ä¸‹æ ‡å°±ä¼šæ•´ä½“错位;景宁就是因为 appid ä¸ºç©ºå¯¼è‡´ {@code size()<4} è¢«è·³è¿‡ï¼‰ã€‚
 * <p>
 * æœ¬ç±»æŠŠä¸‹æ ‡è¯­ä¹‰æ˜¾å¼åŒ–,避免继续用 List ä¸‹æ ‡ä¼ é€’。
 */
public class WechatMpConfig {
    /**
     * æœºæž„ID
     */
    private String orgid;
    /**
     * å…¬ä¼—号 AppID
     */
    private String appid;
    /**
     * å…¬ä¼—号 AppSecret(参与签名)
     */
    private String appSecret;
    /**
     * å…¬ä¼—号网关地址
     */
    private String address;
    /**
     * æ˜¯å¦é…ç½®å®Œæ•´ï¼ˆappid/appSecret/address éƒ½ä¸ä¸ºç©ºï¼‰
     */
    public boolean isComplete() {
        return isNotEmpty(appid) && isNotEmpty(appSecret) && isNotEmpty(address);
    }
    private static boolean isNotEmpty(String value) {
        return value != null && !value.trim().isEmpty();
    }
    public String getOrgid() {
        return orgid;
    }
    public void setOrgid(String orgid) {
        this.orgid = orgid;
    }
    public String getAppid() {
        return appid;
    }
    public void setAppid(String appid) {
        this.appid = appid;
    }
    public String getAppSecret() {
        return appSecret;
    }
    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @Override
    public String toString() {
        return "WechatMpConfig{orgid='" + orgid + '\''
                + ", appid='" + appid + '\''
                + ", appSecret='" + (appSecret == null ? null : "***") + '\''
                + ", address='" + address + '\''
                + '}';
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatMpConfigProvider.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.message.wechat;
/**
 * å¾®ä¿¡å…¬ä¼—号机构级配置提供方(扩展点 SPI)。
 * <p>
 * ç”±ä¸šåŠ¡æ¨¡å—å®žçŽ°ï¼ˆå½“å‰åº”å§”æ‰˜ç»™ {@code com.ruoyi.common.enums.WxGZHEnum} ä»¥ä¿æŒé…ç½®æºä¸å˜ï¼Œ
 * åŽç»­å¯æ”¹ä¸ºæ•°æ®åº“/配置中心)。若容器中没有实现,则视为「该机构没有配置公众号」,
 * ä¸Žçº¿ä¸Š {@code wxqqxx} ä¸ºç©ºæ—¶çš„失败表现一致。
 */
public interface WechatMpConfigProvider {
    /**
     * æŒ‰æœºæž„ID查询公众号配置
     *
     * @param orgid æœºæž„ID
     * @return é…ç½®ï¼›æœªé…ç½®è¿”回 {@code null}
     */
    WechatMpConfig getConfig(String orgid);
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatSendRequest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
package com.ruoyi.message.wechat;
import lombok.Data;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * å¾®ä¿¡å…¬ä¼—号发送请求(与具体发送方无关的统一入参)。
 * <p>
 * å„医院公众号的入参差异很大(市一只要 æ‰‹æœºå·+身份证+链接,新华要 æ‚£è€…ID+姓名+appid+签名),
 * å·®å¼‚字段通过 {@link #extras} é€ä¼ ã€‚
 */
@Data
public class WechatSendRequest implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * æ‰‹æœºå·
     */
    private String phone;
    /**
     * èº«ä»½è¯å·ï¼ˆå¸‚一、新华都要)
     */
    private String idCard;
    /**
     * å¤–链地址(点击消息后跳转)
     */
    private String url;
    /**
     * æ ‡é¢˜ / ä»»åŠ¡åç§°
     */
    private String title;
    /**
     * å†…容 / ä»»åŠ¡æè¿°
     */
    private String content;
    /**
     * æœºæž„ID
     */
    private String orgid;
    /**
     * HIS ä¾§æ‚£è€…ID(新华要求非空)
     */
    private String patidHis;
    /**
     * æ‚£è€…姓名
     */
    private String patientName;
    /**
     * openid(微信公众号 SDK é€šé“使用)
     */
    private String openid;
    /**
     * æ¨¡æ¿ID(微信公众号 SDK æ¨¡æ¿æ¶ˆæ¯é€šé“使用)
     */
    private String templateId;
    /**
     * æ˜¯å¦å…³é—­ Gson çš„ HTML è½¬ä¹‰ï¼ˆæ–°åŽå…¬ä¼—号站点间存在差异,见 {@code XinHuaMpApi.sendMessage})
     */
    private boolean disableHtmlEscaping;
    /**
     * æ‰©å±•参数
     */
    private Map<String, Object> extras;
    /**
     * è¿½åŠ ä¸€ä¸ªæ‰©å±•å‚æ•°
     */
    public WechatSendRequest putExtra(String key, Object value) {
        if (this.extras == null) {
            this.extras = new LinkedHashMap<>();
        }
        this.extras.put(key, value);
        return this;
    }
    /**
     * è¯»å–扩展参数
     */
    public Object getExtra(String key) {
        return this.extras == null ? null : this.extras.get(key);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.message.wechat;
import com.ruoyi.message.SendResult;
/**
 * å¾®ä¿¡å…¬ä¼—号发送方(一个渠道一个实现)。
 * <p>
 * çŽ°çŠ¶ï¼šå¸‚ä¸€ï¼ˆsmsUtils.sendChat)和新华(getWXCode)走的是两套完全不同的第三方网关,
 * è¿”回体判定也不一样(市一看 {@code success==1},新华看 {@code succ==true}),因此分开实现。
 */
public interface WechatSender {
    /**
     * å‘送方唯一 key
     */
    String key();
    /**
     * å‘送公众号消息
     *
     * @param request ç»Ÿä¸€å…¥å‚
     * @return ç»Ÿä¸€ç»“果,不抛异常
     */
    SendResult send(WechatSendRequest request);
    /**
     * å‘送方说明,用于日志与排查
     */
    default String description() {
        return key();
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/WechatSenderFactory.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
package com.ruoyi.message.wechat;
import com.ruoyi.message.SendException;
import com.ruoyi.message.SendResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
 * å¾®ä¿¡å…¬ä¼—号发送方工厂:按 key è·¯ç”±åˆ°ä¸€ä¸ªå…·ä½“实现。
 * <p>
 * æ³¨æ„ï¼šå…¬ä¼—号通道的 key ä¸ŽçŸ­ä¿¡é€šé“çš„ key å¯ä»¥åŒåï¼ˆä¾‹å¦‚ {@code hzszlyy} æ—¢æœ‰çŸ­ä¿¡å‘送方
 * ä¹Ÿæœ‰å…¬ä¼—号发送方),因此两个工厂各自维护自己的注册表,互不干扰。
 */
@Slf4j
@Component
public class WechatSenderFactory {
    private final Map<String, WechatSender> senders = new LinkedHashMap<>();
    public WechatSenderFactory(List<WechatSender> senderList) {
        for (WechatSender sender : senderList) {
            WechatSender exists = senders.put(sender.key(), sender);
            if (exists != null) {
                throw new IllegalStateException("公众号发送方 key é‡å¤ï¼š" + sender.key()
                        + ",冲突实现:" + exists.getClass().getName() + " ä¸Ž " + sender.getClass().getName());
            }
        }
        log.info("【WechatSenderFactory】已注册公众号发送方:{}", senders.keySet());
    }
    /**
     * æŒ‰ key èŽ·å–å‘é€æ–¹
     *
     * @param key æ¸ é“ key(即 {@code spring.profiles.active} å–值)
     * @throws SendException æ‰¾ä¸åˆ°å¯¹åº”实现时抛出
     */
    public WechatSender get(String key) {
        WechatSender sender = key == null ? null : senders.get(key);
        if (sender == null) {
            throw new SendException("未找到公众号发送方,key=" + key + ",已注册:" + senders.keySet());
        }
        return sender;
    }
    /**
     * æŒ‰ key èŽ·å–å‘é€æ–¹ï¼Œæ‰¾ä¸åˆ°è¿”å›ž {@code null}
     */
    public WechatSender getOrNull(String key) {
        return key == null ? null : senders.get(key);
    }
    /**
     * æŒ‰ key å‘送公众号消息
     */
    public SendResult send(String key, WechatSendRequest request) {
        return get(key).send(request);
    }
    /**
     * åˆ¤æ–­æ˜¯å¦å­˜åœ¨è¯¥ key çš„发送方
     */
    public boolean supports(String key) {
        return getOrNull(key) != null;
    }
    /**
     * å·²æ³¨å†Œçš„æ¸ é“ key
     */
    public Set<String> keys() {
        return new TreeSet<>(senders.keySet());
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/hzszlyy/ShiyiMpApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package com.ruoyi.message.wechat.provider.hzszlyy;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.message.config.MessageProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
 * æ­å·žå¸‚一微信公众号 API。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šGET è¯·æ±‚ {@code /sfjk/SendMessage},参数 {@code sfzh/title/content/phone/url/key},
 * è¿”回 JSON(判定字段 {@code success == 1})。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸå®žçŽ°ä¸º {@code com.ruoyi.common.utils.sms.smsUtils.sendChat},其中地址、密钥、
 * æ ‡é¢˜æ–‡æ¡ˆã€å†…容文案都是硬编码;这里全部提为可配置(默认值与原硬编码一致),
 * ä¾¿äºŽåŽç»­æŒ‰æœºæž„/任务类型区分文案,见 {@link MessageProperties.ShiyiWechat}。
 */
@Slf4j
@Component
public class ShiyiMpApi {
    private final MessageProperties properties;
    public ShiyiMpApi(MessageProperties properties) {
        this.properties = properties;
    }
    /**
     * å‘送公众号消息
     *
     * @param url    å¤–链地址
     * @param phone  æ‰‹æœºå·
     * @param idCard èº«ä»½è¯å·
     * @return ç¬¬ä¸‰æ–¹åŽŸå§‹å“åº”
     */
    public String sendMessage(String url, String phone, String idCard) {
        MessageProperties.ShiyiWechat cfg = properties.getWechat().getShiyi();
        String urlTemp = cfg.getUrl()
                + "?sfzh=" + idCard
                + "&title=" + cfg.getTitle()
                + "&content=" + cfg.getContent()
                + "&phone=" + phone
                + "&url=" + url
                + "&key=" + cfg.getKey();
        String sendMsg = "";
        try {
            log.info("【ShiyiMpApi】市一微信公众号请求参数:{}", urlTemp);
            sendMsg = HttpUtils.sendPost(urlTemp);
            log.info("【ShiyiMpApi】市一微信公众号返回值:{}", sendMsg);
        } catch (Exception ex) {
            sendMsg = ex.getMessage();
            log.error("【ShiyiMpApi】市一微信公众号发送异常:{}", ex.getMessage(), ex);
        }
        return sendMsg;
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/hzszlyy/ShiyiMpSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package com.ruoyi.message.wechat.provider.hzszlyy;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.message.MessageChannel;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.wechat.WechatSendRequest;
import com.ruoyi.message.wechat.WechatSender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
 * æ­å·žå¸‚一微信公众号发送方({@code hzszlyy} é€šé“)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šå¸‚一自有网关,返回 JSON çš„ {@code success == 1} ä¸ºæˆåŠŸã€‚
 * ä»£ç æ¥æºï¼š{@code RyTask.hzszlyy å…¬ä¼—号分支} + {@code RedisMqReceiver.hzszlyy åˆ†æ”¯} +
 * {@code SmsController./sms/sendChat}(行为保持不变)。
 * <p>
 * æ³¨æ„ï¼šå¸‚一公众号<b>不做机构配置校验</b>(原代码注释「市一医院 ä¸æ ¡éªŒã€ï¼‰ï¼Œ
 * è°ƒç”¨æ–¹éœ€ä¿ç•™è¯¥å·®å¼‚,不要套用新华那套 {@code wxqqxx} æ ¡éªŒã€‚
 */
@Slf4j
@Component
public class ShiyiMpSender implements WechatSender {
    /**
     * æ¸ é“ key,微信公众号通道下与短信通道同名({@code hzszlyy}),由各自的工厂区分
     */
    public static final String KEY = "hzszlyy";
    private final ShiyiMpApi shiyiMpApi;
    public ShiyiMpSender(ShiyiMpApi shiyiMpApi) {
        this.shiyiMpApi = shiyiMpApi;
    }
    @Override
    public String key() {
        return KEY;
    }
    @Override
    public String description() {
        return "杭州市一微信公众号(success==1 åˆ¤å®šï¼‰";
    }
    @Override
    public SendResult send(WechatSendRequest request) {
        if (request == null || request.getPhone() == null || request.getPhone().trim().isEmpty()) {
            return SendResult.fail(KEY, MessageChannel.WECHAT, "手机号为空", null);
        }
        try {
            String raw = shiyiMpApi.sendMessage(request.getUrl(), request.getPhone(), request.getIdCard());
            Map map = JSONObject.parseObject(raw, Map.class);
            Boolean success = false;
            if (map != null && !map.isEmpty() && map.get("success") != null) {
                Integer value = (Integer) map.get("success");
                success = value != null && value == 1;
            }
            if (success) {
                return SendResult.success(KEY, MessageChannel.WECHAT, raw);
            }
            Object msg = map == null ? null : map.get("msg");
            return SendResult.fail(KEY, MessageChannel.WECHAT,
                    msg == null ? "【公众号】公众号发送失败" : msg.toString(), raw);
        } catch (Exception e) {
            log.error("【{}】市一公众号发送失败:{}", KEY, e.getMessage(), e);
            return SendResult.fail(KEY, MessageChannel.WECHAT, "【公众号】公众号发送失败", null);
        }
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/sdk/WxMpSdkSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
package com.ruoyi.message.wechat.provider.sdk;
import com.ruoyi.message.MessageChannel;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.wechat.WechatSendRequest;
import com.ruoyi.message.wechat.WechatSender;
import com.ruoyi.message.wechat.support.WxMpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
 * å¾®ä¿¡å…¬ä¼—号 SDK å‘送方({@code wxmp-sdk} é€šé“)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šç›´æŽ¥ä½¿ç”¨å¾®ä¿¡å®˜æ–¹ SDK({@code weixin-java-mp}),支持文本客服消息与模板消息,
 * éœ€è¦ <b>openid</b>(而不是手机号),因此入参走 {@link WechatSendRequest#getOpenid()}。
 * <p>
 * çŽ°çŠ¶ï¼šè¯¥èƒ½åŠ›åŽŸå…ˆåªæœ‰ {@code WxMpUtils} ä¸€ä¸ªå·¥å…·ç±»ï¼Œ<b>没有任何业务调用点</b>。
 * æ”¶å£åŽç”±å·¥åŽ‚ç»Ÿä¸€è°ƒåº¦ï¼ŒåŽç»­è‹¥è¦ç»™ã€Œå¾®ä¿¡å°ç¨‹åº/公众号」类任务用官方 SDK å‘送,直接走这里。
 * <p>
 * æ‰©å±•参数:
 * <ul>
 *   <li>{@code appId} / {@code appSecret}:公众号凭据(必填);</li>
 *   <li>{@code templateData}:模板数据 {@code Map<String,String>}(模板消息用)。</li>
 * </ul>
 */
@Slf4j
@Component
public class WxMpSdkSender implements WechatSender {
    /**
     * æ¸ é“ key
     */
    public static final String KEY = "wxmp-sdk";
    @Override
    public String key() {
        return KEY;
    }
    @Override
    public String description() {
        return "微信公众号官方 SDK(客服文本消息 / æ¨¡æ¿æ¶ˆæ¯ï¼ŒæŒ‰ openid å‘送)";
    }
    @Override
    public SendResult send(WechatSendRequest request) {
        if (request == null || StringUtils.isEmpty(request.getOpenid())) {
            return SendResult.fail(KEY, MessageChannel.WECHAT, "openid ä¸ºç©º", null);
        }
        Object appId = request.getExtra("appId");
        Object appSecret = request.getExtra("appSecret");
        if (appId == null || appSecret == null) {
            return SendResult.fail(KEY, MessageChannel.WECHAT, "公众号 appId/appSecret æœªé…ç½®", null);
        }
        try {
            WxMpUtils wxMpUtils = WxMpUtils.of(String.valueOf(appId), String.valueOf(appSecret));
            if (StringUtils.isNotEmpty(request.getTemplateId())) {
                return sendTemplate(wxMpUtils, request);
            }
            boolean ok = wxMpUtils.sendTextMessage(request.getOpenid(), request.getContent());
            if (ok) {
                return SendResult.success(KEY, MessageChannel.WECHAT, null);
            }
            return SendResult.fail(KEY, MessageChannel.WECHAT, "公众号消息发送失败", null);
        } catch (Exception e) {
            log.error("【{}】公众号消息发送异常:{}", KEY, e.getMessage(), e);
            return SendResult.fail(KEY, MessageChannel.WECHAT, "公众号消息发送异常:" + e.getMessage(), null);
        }
    }
    @SuppressWarnings("unchecked")
    private SendResult sendTemplate(WxMpUtils wxMpUtils, WechatSendRequest request) {
        Object data = request.getExtra("templateData");
        Map<String, String> dataMap = data instanceof Map ? (Map<String, String>) data : null;
        String msgId = wxMpUtils.sendTemplateMessage(request.getOpenid(), request.getTemplateId(),
                request.getUrl(), dataMap, null);
        if (StringUtils.isNotEmpty(msgId)) {
            return SendResult.success(KEY, MessageChannel.WECHAT, msgId).withCode(msgId);
        }
        return SendResult.fail(KEY, MessageChannel.WECHAT, "公众号模板消息发送失败", null);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/xh/XinHuaMpApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,122 @@
package com.ruoyi.message.wechat.provider.xh;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.message.wechat.WechatMpConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
/**
 * æ–°åŽåŒ»é™¢å…¬ä¼—号 API。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šPOST JSON æŠ¥æ–‡ï¼Œç­¾å {@code sign = MD5(appSecret + body)} æ”¾åœ¨è¯·æ±‚头,
 * ç½‘关地址来自机构级配置({@code WxGZHEnum})。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸé€»è¾‘åˆ†æ•£åœ¨ {@code RyTask.getWXCode}、{@code RedisMqReceiver.getWXCode}、
 * {@code ServiceSubtaskServiceImpl.getWXCode} ä¸‰ä»½æ‹·è´é‡Œï¼Œæ­¤å¤„合并为一份(行为保持不变)。
 */
@Slf4j
@Component
public class XinHuaMpApi {
    private final com.ruoyi.message.config.MessageProperties properties;
    public XinHuaMpApi(com.ruoyi.message.config.MessageProperties properties) {
        this.properties = properties;
    }
    /**
     * å‘送公众号消息(默认不关闭 HTML è½¬ä¹‰ï¼Œç­‰ä»· {@code RyTask.getWXCode})
     */
    public String sendMessage(String idcard, String url, String taskName, String taskDesc,
                              String phone, String patientName, String patientId, WechatMpConfig config) {
        return sendMessage(idcard, url, taskName, taskDesc, phone, patientName, patientId, config, false);
    }
    /**
     * å‘送公众号消息
     *
     * @param idcard         èº«ä»½è¯å·
     * @param url            å¤–链地址
     * @param taskName       æ ‡é¢˜
     * @param taskDesc       å†…容
     * @param phone          æ‰‹æœºå·
     * @param patientName    æ‚£è€…姓名
     * @param patientId      HIS ä¾§æ‚£è€…ID
     * @param config         æœºæž„级公众号配置
     * @param disableHtmlEscaping æ˜¯å¦å…³é—­ Gson çš„ HTML è½¬ä¹‰
     *                      <p>
     *                      âš ï¸ ç«™ç‚¹å·®å¼‚(必须区分):{@code RedisMqReceiver.getWXCode} ç”¨çš„æ˜¯
     *                      {@code new GsonBuilder().disableHtmlEscaping()},而
     *                      {@code RyTask.getWXCode} / {@code ServiceSubtaskServiceImpl.getWXCode}
     *                      ç”¨çš„æ˜¯ {@code new Gson()}(会把 {@code < > & = '} è½¬æˆ unicode è½¬ä¹‰ï¼‰ã€‚
     *                      ä¸¤è€…产出的报文体不同,进而影响 sign ç­¾åã€‚此处按调用方区分,保持线上行为。
     * @return ç¬¬ä¸‰æ–¹åŽŸå§‹å“åº”
     */
    public String sendMessage(String idcard, String url, String taskName, String taskDesc,
                              String phone, String patientName, String patientId, WechatMpConfig config,
                              boolean disableHtmlEscaping) {
        XinHuaWXReq req = new XinHuaWXReq();
        req.setService(properties.getWechat().getXinhua().getService());
        req.setIdcard(idcard);
        req.setUrl(url);
        req.setPatientid(patientId);
        req.setPatientname(patientName);
        req.setMobile(phone);
        req.setMedcardno(null);
        req.setTitlename(taskName);
        req.setContent(taskDesc);
        req.setAppid(config.getAppid());
        log.info("【XinHuaMpApi】微信公众号请求参数:{}(disableHtmlEscaping={})", req, disableHtmlEscaping);
        String body;
        if (disableHtmlEscaping) {
            GsonBuilder gb = new GsonBuilder();
            gb.disableHtmlEscaping();
            body = gb.create().toJson(req);
        } else {
            body = new Gson().toJson(req);
        }
        String sign = md5(config.getAppSecret().concat(body));
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("sign", sign);
        String result = HttpUtils.sendPostByHeader(config.getAddress(), body, headerMap);
        log.info("【XinHuaMpApi】微信公众号返回参数:{}", result);
        return result;
    }
    /**
     * MD5 è½¬åå…­è¿›åˆ¶å°å†™ï¼ˆç­‰ä»·åŽŸ {@code getWXCode} é‡Œçš„ {@code encode})
     */
    private String md5(String arg) {
        if (arg == null) {
            arg = "";
        }
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(arg.getBytes(StandardCharsets.UTF_8));
            return toHex(md5.digest());
        } catch (Exception e) {
            log.error("【XinHuaMpApi】MD5 ç­¾åå¤±è´¥ï¼š{}", e.getMessage(), e);
            return "";
        }
    }
    private String toHex(byte[] hash) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/xh/XinHuaMpSender.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,113 @@
package com.ruoyi.message.wechat.provider.xh;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.message.MessageChannel;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.wechat.WechatMpConfig;
import com.ruoyi.message.wechat.WechatMpConfigProvider;
import com.ruoyi.message.wechat.WechatSendRequest;
import com.ruoyi.message.wechat.WechatSender;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
 * æ–°åŽåŒ»é™¢å¾®ä¿¡å…¬ä¼—号发送方({@code xh} é€šé“)。
 * <p>
 * å¯¹æŽ¥ç‰¹å¾ï¼šPOST JSON + {@code sign=MD5(appSecret+body)},成功判定为返回 JSON çš„ {@code succ == true}。
 * <p>
 * ç«™ç‚¹å·®å¼‚(调用方需保留):
 * <ul>
 *   <li>需要机构级公众号配置(appid/appSecret/地址),配置不全时直接失败;</li>
 *   <li>要求 HIS ä¾§æ‚£è€…ID éžç©ºã€‚</li>
 * </ul>
 * ä»£ç æ¥æºï¼š{@code RyTask å…¬ä¼—号分支} + {@code RedisMqReceiver å…¬ä¼—号分支} +
 * {@code ServiceSubtaskServiceImpl.wechatSubTask}(行为保持不变)。
 */
@Slf4j
@Component
public class XinHuaMpSender implements WechatSender {
    /**
     * æ¸ é“ key,与 {@code spring.profiles.active=xh} å¯¹åº”
     */
    public static final String KEY = "xh";
    private final ObjectProvider<WechatMpConfigProvider> configProvider;
    private final XinHuaMpApi xinHuaMpApi;
    public XinHuaMpSender(ObjectProvider<WechatMpConfigProvider> configProvider, XinHuaMpApi xinHuaMpApi) {
        this.configProvider = configProvider;
        this.xinHuaMpApi = xinHuaMpApi;
    }
    @Override
    public String key() {
        return KEY;
    }
    @Override
    public String description() {
        return "新华医院微信公众号(sign ç­¾åï¼Œsucc åˆ¤å®šï¼‰";
    }
    @Override
    public SendResult send(WechatSendRequest request) {
        if (request == null) {
            return SendResult.fail(KEY, MessageChannel.WECHAT, "【公众号】请求参数为空", null);
        }
        WechatMpConfig config = resolveConfig(request.getOrgid());
        if (config == null || !config.isComplete()) {
            return SendResult.fail(KEY, MessageChannel.WECHAT,
                    "【公众号】该机构的公众号配置信息不全,无法通过公众号发送", null);
        }
        if (StringUtils.isEmpty(request.getPatidHis())) {
            return SendResult.fail(KEY, MessageChannel.WECHAT,
                    "【公众号】公众号发送失败,his系统的患者id为空", null);
        }
        try {
            String raw = xinHuaMpApi.sendMessage(
                    request.getIdCard(),
                    request.getUrl(),
                    request.getTitle(),
                    request.getContent(),
                    request.getPhone(),
                    request.getPatientName(),
                    request.getPatidHis(),
                    config,
                    request.isDisableHtmlEscaping());
            Map map = JSONObject.parseObject(raw, Map.class);
            boolean success = map != null && !map.isEmpty()
                    && map.get("succ") != null
                    && Boolean.parseBoolean(map.get("succ").toString());
            if (success) {
                return SendResult.success(KEY, MessageChannel.WECHAT, raw);
            }
            Object msg = map == null ? null : map.get("msg");
            String message = msg == null ? "【公众号】公众号发送失败" : msg.toString();
            log.error("【{}】公众号发送失败,错误信息:{}", KEY, message);
            return SendResult.fail(KEY, MessageChannel.WECHAT, message, raw);
        } catch (Exception e) {
            log.error("【{}】公众号发送异常:{}", KEY, e.getMessage(), e);
            return SendResult.fail(KEY, MessageChannel.WECHAT,
                    "【公众号】公众号发送失败,原因:" + e.getMessage(), null);
        }
    }
    private WechatMpConfig resolveConfig(String orgid) {
        WechatMpConfigProvider provider = configProvider.getIfAvailable();
        if (provider == null) {
            log.warn("【{}】未找到 WechatMpConfigProvider å®žçŽ°ï¼Œæ— æ³•èŽ·å–æœºæž„å…¬ä¼—å·é…ç½®", KEY);
            return null;
        }
        return provider.getConfig(orgid);
    }
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/provider/xh/XinHuaWXReq.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package com.ruoyi.message.wechat.provider.xh;
import lombok.Data;
/**
 * æ–°åŽåŒ»é™¢å…¬ä¼—号入参。
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸç±»ä¸º {@code com.smartor.domain.XinHuaWXReq}(带 swagger/Excel æ³¨è§£ï¼‰ï¼Œ
 * è¿ç§»åˆ°æœ¬æ¨¡å—后只保留传输字段,字段名与 JSON æŠ¥æ–‡ä¿æŒä¸€è‡´ï¼ˆä¸èƒ½æ”¹åï¼Œå¦åˆ™å½±å“å¯¹æ–¹è§£æžï¼‰ã€‚
 */
@Data
public class XinHuaWXReq {
    /**
     * æŽ¥å£æœåŠ¡åï¼Œå›ºå®šå€¼
     */
    private String service = "smarthos.pat.notice";
    /**
     * åº”用编号
     */
    private String appid;
    /**
     * æ‚£è€…编号
     */
    private String patientid;
    /**
     * æ‚£è€…姓名
     */
    private String patientname;
    /**
     * è¯ä»¶å·
     */
    private String idcard;
    /**
     * æ‰‹æœºå·ç 
     */
    private String mobile;
    /**
     * å°±è¯Šå¡å·
     */
    private String medcardno;
    /**
     * é€šçŸ¥å†…容
     */
    private String titlename;
    /**
     * é“¾æŽ¥åœ°å€
     */
    private String content;
    /**
     * è¯¦æƒ…地址
     */
    private String url;
}
ruoyi-message/src/main/java/com/ruoyi/message/wechat/support/WxMpUtils.java
ÎļþÃû´Ó ruoyi-common/src/main/java/com/ruoyi/common/utils/WxMpUtils.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.ruoyi.common.utils;
package com.ruoyi.message.wechat.support;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.exception.WxErrorException;
@@ -8,13 +8,18 @@
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import org.springframework.stereotype.Component;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
/**
 * å¾®ä¿¡å…¬ä¼—号消息发送工具类
 * æ”¯æŒï¼šæ–‡æœ¬å®¢æœæ¶ˆæ¯ã€æ¨¡æ¿æ¶ˆæ¯
 * å¾®ä¿¡å…¬ä¼—号消息发送工具类(微信官方 SDK é€šé“)。
 * <p>
 * æ”¯æŒï¼šæ–‡æœ¬å®¢æœæ¶ˆæ¯ã€æ¨¡æ¿æ¶ˆæ¯ã€‚
 * <p>
 * è¿ç§»è¯´æ˜Žï¼šåŽŸç±»ä¸º {@code com.ruoyi.common.utils.WxMpUtils},行为未改动,仅换包。
 * çŽ°çŠ¶ï¼šå…¨é¡¹ç›®<b>没有业务调用点</b>(只有 {@code SysLoginController} ä¸Ž
 * {@code WeChatService} é‡Œçš„æ³¨é‡Š/测试代码),属于「已具备能力但未接入」的通道。
 */
@Slf4j
public class WxMpUtils {
@@ -40,8 +45,8 @@
    // -------------------------------------------------------------------------
    /**
     * å‘指定 openid çš„用户发送文本客服消息
     * æ³¨æ„ï¼šç”¨æˆ·éœ€åœ¨48小时内与公众号有过交互才可发送
     * å‘指定 openid çš„用户发送文本客服消息。
     * æ³¨æ„ï¼šç”¨æˆ·éœ€åœ¨48小时内与公众号有过交互才可发送。
     *
     * @param openid  ç”¨æˆ·çš„微信 openid
     * @param content æ¶ˆæ¯å†…容
@@ -78,7 +83,6 @@
     * @param templateId æ¨¡æ¿æ¶ˆæ¯ ID(在微信公众平台配置)
     * @param url        ç‚¹å‡»æ¶ˆæ¯åŽçš„跳转 URL,不需要跳转可传 null
     * @param dataMap    æ¨¡æ¿æ•°æ®ï¼Œkey ä¸ºæ¨¡æ¿å­—段名,value ä¸º WxMpTemplateData(含值和颜色)
     *                   ç¤ºä¾‹ï¼š{"first": new WxMpTemplateData("first", "你好", "#173177"), ...}
     * @return å‘送成功返回微信返回的 msgid,失败返回 null
     */
    public String sendTemplateMessage(String openid, String templateId, String url, Map<String, WxMpTemplateData> dataMap) {
@@ -112,13 +116,13 @@
    }
    /**
     * å‘指定 openid çš„用户发送模板消息(简化版,value ä¸è®¾ç½®é¢œè‰²ï¼‰
     * å‘指定 openid çš„用户发送模板消息(简化版,value ç»Ÿä¸€ä½¿ç”¨é»˜è®¤é¢œè‰²ï¼‰
     *
     * @param openid     ç”¨æˆ·çš„微信 openid
     * @param templateId æ¨¡æ¿æ¶ˆæ¯ ID
     * @param url        ç‚¹å‡»æ¶ˆæ¯åŽçš„跳转 URL,不需要跳转可传 null
     * @param dataMap    æ¨¡æ¿æ•°æ®ï¼Œkey ä¸ºæ¨¡æ¿å­—段名,value ä¸ºå­—段显示的文本内容
     *                   ç¤ºä¾‹ï¼š{"first": "你好", "keyword1": "2024-01-01", "remark": "感谢使用"}
     * @param openid       ç”¨æˆ·çš„微信 openid
     * @param templateId   æ¨¡æ¿æ¶ˆæ¯ ID
     * @param url          ç‚¹å‡»æ¶ˆæ¯åŽçš„跳转 URL,不需要跳转可传 null
     * @param dataMap      æ¨¡æ¿æ•°æ®ï¼Œkey ä¸ºæ¨¡æ¿å­—段名,value ä¸ºå­—段显示的文本内容
     * @param defaultColor é»˜è®¤é¢œè‰²
     * @return å‘送成功返回微信返回的 msgid,失败返回 null
     */
    public String sendTemplateMessage(String openid, String templateId, String url, Map<String, String> dataMap, String defaultColor) {
ruoyi-message/src/main/resources/wsdl/MessagingInsert.asmx
ruoyi-message/src/test/java/com/ruoyi/message/config/MessagePrefixBindingCheck.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
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)。
 * <p>
 * è¦†ç›–两件事:
 * <ol>
 *   <li>{@code message.sms.*} / {@code message.wechat.*} èƒ½ç»‘到对应的嵌套字段,未覆盖的项回落默认值;</li>
 *   <li>工厂按 key è·¯ç”±ç¡®å®žæ˜¯ã€Œè°ƒç”¨åˆ°å¯¹åº”发送方」——这是四个站点发送链路里本地唯一能验证的一环。</li>
 * </ol>
 */
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.<SmsSender>singletonList(new CountingSmsSender()));
        WechatSenderFactory wechatFactory = new WechatSenderFactory(Collections.<WechatSender>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<PropertySource<?>> 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");
        }
    }
}
ruoyi-message/src/test/java/com/ruoyi/message/config/MessagePropertiesBindingCheck.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
package com.ruoyi.message.config;
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.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ClassPathResource;
import java.util.List;
/**
 * é…ç½®ç»‘定校验程序(不依赖 JUnit,可直接用模块的运行时 classpath æ‰§è¡Œï¼‰ã€‚
 * <p>
 * å­˜åœ¨çš„æ„ä¹‰ï¼šæŠŠæ•£è½çš„ {@code @Value} æ”¹æˆ {@code @ConfigurationProperties} åŽï¼Œ
 * å‰ç¼€/层级写错<b>编译期发现不了</b>,只会在线上发短信时表现为参数为 null。
 * æœ¬ç¨‹åºç”¨ã€ŒçœŸå®žçš„æ‰å¹³ yml」跑一次绑定,把这些 key é”ä½ã€‚
 * <p>
 * æ‰§è¡Œæ–¹å¼è§ {@code ruoyi-message/README-配置绑定校验.md}。
 */
public final class MessagePropertiesBindingCheck {
    private static int failed = 0;
    private MessagePropertiesBindingCheck() {
    }
    public static void main(String[] args) throws Exception {
        StandardEnvironment environment = new StandardEnvironment();
        // åŽ»æŽ‰ systemProperties / systemEnvironment:Windows ä¸‹å®ƒä»¬çš„名字是大写驼峰(如 XHSMSPATH),
        // ConfigurationPropertyName ä¸æŽ¥å—大写,会干扰绑定诊断。生产用 SpringApplication æ—¶åå­—会被归一化。
        environment.getPropertySources()
                .remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
        environment.getPropertySources()
                .remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
        List<PropertySource<?>> sources = new YamlPropertySourceLoader()
                .load("message-binding-check.yml", new ClassPathResource("message-binding-check.yml"));
        environment.getPropertySources().addLast(sources.get(0));
        Binder binder = new Binder(ConfigurationPropertySources.get(environment));
        RootMessageProperties root = binder
                .bind(ConfigurationPropertyName.EMPTY, Bindable.of(RootMessageProperties.class))
                .orElseGet(RootMessageProperties::new);
        MessageProperties properties = new MessageProperties(root);
        // ---- æ–°åŽçŸ­ä¿¡ç½‘å…³ ----
        check("xhsmsPath", "http://192.16.4.220:8092/sendSms", properties.getSms().getXhsms().getPath());
        check("xhsmsAccount", "911124", properties.getSms().getXhsms().getAccount());
        check("xhsmsPwd", "zW5eXe", properties.getSms().getXhsms().getPwd());
        check("xhsmsjrm", "1069055", properties.getSms().getXhsms().getExtno());
        // ---- å—华附一 MT(用户特别指出的那批配置)----
        check("sms_accountName", "S200232", properties.getSms().getMt().getAccountName());
        check("sms_password", "lD94Yo", properties.getSms().getMt().getPassword());
        check("sms_url", "http://open.nhyfy.cn/kapi/gw-api/hntypt/ucpmsg/send", properties.getSms().getMt().getUrl());
        check("sms_sourceAddr", "1069100020341", properties.getSms().getMt().getSourceAddr());
        check("sms_serviceCode", "PUSH", properties.getSms().getMt().getServiceCode());
        check("sms_appkey", "ak-BFq4NN0TMf92J7KAioNYGz74", properties.getSms().getMt().getAppkey());
        check("sms_flag", "【南华大学附属第一医院】", properties.getSms().getMt().getFlag());
        // ---- é˜¿é‡Œäº‘ ----
        check("accessKeyId", "LTAI5tTestKeyId", properties.getSms().getAliyun().getAccessKeyId());
        check("accessKeySecret", "testSecret", properties.getSms().getAliyun().getAccessKeySecret());
        check("signName", "杭州利湖科技", properties.getSms().getAliyun().getSignName());
        // ---- æ–°å¢žé¡¹åº”回落到默认值 ----
        check("message.sms.shiyi.ws-url é»˜è®¤å€¼", "http://192.178.0.104:7790/MediInfoHis.svc",
                properties.getSms().getShiyi().getWsUrl());
        check("message.sms.messaging-insert.ws-url é»˜è®¤å€¼", "http://218.108.11.29:8081/MessagingInsert.asmx",
                properties.getSms().getMessagingInsert().getWsUrl());
        check("message.wechat.shiyi.url é»˜è®¤å€¼", "http://192.200.54.14:5003/sfjk/SendMessage",
                properties.getWechat().getShiyi().getUrl());
        check("message.wechat.xinhua.service é»˜è®¤å€¼", "smarthos.pat.notice",
                properties.getWechat().getXinhua().getService());
        System.out.println();
        if (failed == 0) {
            System.out.println("===== é…ç½®ç»‘定校验全部通过 =====");
        } else {
            System.out.println("===== é…ç½®ç»‘定校验失败 " + failed + " é¡¹ =====");
            System.exit(1);
        }
    }
    private static void check(String key, String expected, String actual) {
        boolean ok = expected == null ? actual == null : expected.equals(actual);
        if (!ok) {
            failed++;
        }
        System.out.println((ok ? "[OK]   " : "[FAIL] ") + key
                + "  æœŸæœ›=" + expected + "  å®žé™…=" + actual);
    }
}
ruoyi-message/src/test/java/com/ruoyi/message/config/PerSiteBindingCheck.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package com.ruoyi.message.config;
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.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.FileSystemResource;
import java.util.List;
/**
 * ç”¨ã€Œå„站点真实的 application-*.yml」逐站点校验存量扁平 key çš„绑定。
 * <p>
 * åªå…³å¿ƒçŸ­ä¿¡/公众号相关 key,其余 key å³ä½¿å¸¦å ä½ç¬¦ä¹Ÿä¸å½±å“ã€‚
 */
public final class PerSiteBindingCheck {
    private static int failed = 0;
    private PerSiteBindingCheck() {
    }
    public static void main(String[] args) throws Exception {
        String resDir = args.length > 0 ? args[0]
                : "ruoyi-admin/src/main/resources";
        checkSite(resDir, "xh", "新华",
                new String[]{"http://192.16.4.220:8092/sms", "911124", "zW5eXe", "1069055"},
                null, null);
        checkSite(resDir, "hzszlyy", "市一",
                new String[]{"http://192.169.140.240:8001/sms/api/sendMessageMass", "300044", "qj0NHDegxWhj", "1069055"},
                null, null);
        checkSite(resDir, "sltd", "省立同德",
                new String[]{"", "", "", ""},
                null, null);
        checkSite(resDir, "nhfy", "南华附一",
                new String[]{"", "", "", ""},
                new String[]{"S200232", "lD94Yo", "http://open.nhyfy.cn/kapi/gw-api/hntypt/ucpmsg/send",
                        "1069100020341", "PUSH", "ak-BFq4NN0TMf92J7KAioNYGz74", "【南华大学附属第一医院】"},
                null);
        System.out.println();
        if (failed == 0) {
            System.out.println("===== é€ç«™ç‚¹ç»‘定校验全部通过 =====");
        } else {
            System.out.println("===== é€ç«™ç‚¹ç»‘定校验失败 " + failed + " é¡¹ =====");
            System.exit(1);
        }
    }
    private static void checkSite(String resDir, String profile, String siteName,
                                  String[] xhsms, String[] mt, String[] aliyun) throws Exception {
        String file = resDir + "/application-" + profile + ".yml";
        System.out.println("========== " + siteName + "(" + file + ")==========");
        ConfigurableEnvironment environment = new StandardEnvironment();
        environment.getPropertySources()
                .remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
        environment.getPropertySources()
                .remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
        List<PropertySource<?>> sources = new YamlPropertySourceLoader()
                .load(file, new FileSystemResource(file));
        for (PropertySource<?> source : sources) {
            environment.getPropertySources().addLast(source);
        }
        Binder binder = new Binder(ConfigurationPropertySources.get(environment));
        RootMessageProperties root = binder
                .bind(ConfigurationPropertyName.EMPTY, Bindable.of(RootMessageProperties.class))
                .orElseGet(RootMessageProperties::new);
        MessageProperties p = new MessageProperties(root);
        if (xhsms != null) {
            check(siteName + ".xhsmsPath", xhsms[0], p.getSms().getXhsms().getPath());
            check(siteName + ".xhsmsAccount", xhsms[1], p.getSms().getXhsms().getAccount());
            check(siteName + ".xhsmsPwd", xhsms[2], p.getSms().getXhsms().getPwd());
            check(siteName + ".xhsmsjrm", xhsms[3], p.getSms().getXhsms().getExtno());
        }
        if (mt != null) {
            check(siteName + ".sms_accountName", mt[0], p.getSms().getMt().getAccountName());
            check(siteName + ".sms_password", mt[1], p.getSms().getMt().getPassword());
            check(siteName + ".sms_url", mt[2], p.getSms().getMt().getUrl());
            check(siteName + ".sms_sourceAddr", mt[3], p.getSms().getMt().getSourceAddr());
            check(siteName + ".sms_serviceCode", mt[4], p.getSms().getMt().getServiceCode());
            check(siteName + ".sms_appkey", mt[5], p.getSms().getMt().getAppkey());
            check(siteName + ".sms_flag", mt[6], p.getSms().getMt().getFlag());
        }
        if (aliyun != null) {
            check(siteName + ".accessKeyId", aliyun[0], p.getSms().getAliyun().getAccessKeyId());
        }
        System.out.println();
    }
    private static void check(String key, String expected, String actual) {
        boolean ok = expected == null ? actual == null : expected.equals(actual);
        if (!ok) {
            failed++;
        }
        System.out.println((ok ? "  [OK]   " : "  [FAIL] ") + key
                + "  æœŸæœ›=[" + expected + "]  å®žé™…=[" + actual + "]");
    }
}
ruoyi-message/src/test/resources/message-binding-check.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
# ä»…用于 MessagePropertiesBindingCheck:模拟各站点 application-<profile>.yml çš„「根级扁平 key」
# æ³¨æ„ï¼šè¿™äº› key åœ¨çœŸå®žé…ç½®æ–‡ä»¶é‡Œå°±æ˜¯æ ¹çº§çš„,不带任何前缀
xhsmsPath: http://192.16.4.220:8092/sendSms
xhsmsAccount: 911124
xhsmsPwd: zW5eXe
xhsmsjrm: 1069055
sms_accountName: S200232
sms_password: lD94Yo
sms_url: http://open.nhyfy.cn/kapi/gw-api/hntypt/ucpmsg/send
sms_sourceAddr: 1069100020341
sms_serviceCode: PUSH
sms_appkey: ak-BFq4NN0TMf92J7KAioNYGz74
sms_flag: ã€å—华大学附属第一医院】
accessKeyId: LTAI5tTestKeyId
accessKeySecret: testSecret
signName: æ­å·žåˆ©æ¹–科技
ruoyi-message/target/classes/com/ruoyi/message/MessageChannel.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/SendException.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/SendResult.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/AliyunSmsConfig.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/MessageConfigValidator.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$MessagingInsert.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$ShiyiSms.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$ShiyiWechat.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$Sms.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$Wechat.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties$Xinhua.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/MessageProperties.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/MtSmsConfig.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/RootMessageProperties.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/config/XhsmsConfig.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/AbstractSmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/SmsSendRequest.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/SmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/SmsSenderFactory.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/aliyun/AliYunSmsApi.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/aliyun/AliYunSmsSender$1.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/aliyun/AliYunSmsSender$SendSmsRequestAdapter.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/aliyun/AliYunSmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/hzszlyy/ShiyiSmsApi.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/hzszlyy/ShiyiSmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/ls/LishuiMessageConfig.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/ls/LishuiMessageConfigProvider.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/ls/LishuiSmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/ls/LishuiStaticConfig.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/MessagingInsertSmsApi.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/MessagingInsertSmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsert.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertLocator.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap12Stub.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap_BindingStub.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/messaginginsert/ws/MessagingInsertSoap_PortType.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/nhfy/MtSubmitSmResp.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/nhfy/MtSubmitSmUtil.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/nhfy/NhfySmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/shiyi/ShiyiHisSmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsRequest.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/shiyi/ShiyiSmsUtil.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/SltdResponseParser.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/SltdSmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/Hello.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/HelloResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GReply.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GReplyResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GReport.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GReportResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GSearchNumber.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GSearchNumberResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GSend.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/I5GSendResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/MessageSend.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/ObjectFactory.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetNumber.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetNumberResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetTemplateList.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetTemplateListResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetUserList.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSGetUserListResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSSaveContent.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSSaveContentResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSVerify.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyPrivate.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyPrivateResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SMSVerifyResponse.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SmsService.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/SmsServiceSoap.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/sltd/ws/package-info.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/sms/provider/xh/XinHuaSmsSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatMpConfig.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatMpConfigProvider.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatSendRequest.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/WechatSenderFactory.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/hzszlyy/ShiyiMpApi.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/hzszlyy/ShiyiMpSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/sdk/WxMpSdkSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/xh/XinHuaMpApi.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/xh/XinHuaMpSender.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/provider/xh/XinHuaWXReq.class
Binary files differ
ruoyi-message/target/classes/com/ruoyi/message/wechat/support/WxMpUtils.class
Binary files differ
ruoyi-message/target/classes/message-binding-check.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
# ä»…用于 MessagePropertiesBindingCheck:模拟各站点 application-<profile>.yml çš„「根级扁平 key」
# æ³¨æ„ï¼šè¿™äº› key åœ¨çœŸå®žé…ç½®æ–‡ä»¶é‡Œå°±æ˜¯æ ¹çº§çš„,不带任何前缀
xhsmsPath: http://192.16.4.220:8092/sendSms
xhsmsAccount: 911124
xhsmsPwd: zW5eXe
xhsmsjrm: 1069055
sms_accountName: S200232
sms_password: lD94Yo
sms_url: http://open.nhyfy.cn/kapi/gw-api/hntypt/ucpmsg/send
sms_sourceAddr: 1069100020341
sms_serviceCode: PUSH
sms_appkey: ak-BFq4NN0TMf92J7KAioNYGz74
sms_flag: ã€å—华大学附属第一医院】
accessKeyId: LTAI5tTestKeyId
accessKeySecret: testSecret
signName: æ­å·žåˆ©æ¹–科技
ruoyi-message/target/classes/wsdl/MessagingInsert.asmx
copy from ruoyi-system/src/main/java/WebServiceClient/MessagingInsert.asmx copy to ruoyi-message/target/classes/wsdl/MessagingInsert.asmx
ruoyi-message/target/maven-archiver/pom.properties
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
#Generated by Maven
#Sat Oct 10 14:15:13 CST 2026
version=3.8.5
groupId=com.ruoyi
artifactId=ruoyi-message
ruoyi-message/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
com\ruoyi\message\wechat\provider\xh\XinHuaMpApi.class
com\ruoyi\message\sms\provider\ls\LishuiStaticConfig.class
com\ruoyi\message\config\MessageConfigValidator.class
com\ruoyi\message\MessageChannel.class
com\ruoyi\message\sms\provider\sltd\ws\MessageSend.class
com\ruoyi\message\wechat\WechatMpConfig.class
com\ruoyi\message\sms\provider\ls\LishuiSmsSender.class
com\ruoyi\message\wechat\provider\xh\XinHuaWXReq.class
com\ruoyi\message\config\MessageProperties$Wechat.class
com\ruoyi\message\config\MessageProperties.class
com\ruoyi\message\sms\provider\shiyi\ShiyiSmsRequest.class
com\ruoyi\message\wechat\support\WxMpUtils.class
com\ruoyi\message\sms\AbstractSmsSender.class
com\ruoyi\message\wechat\WechatSenderFactory.class
com\ruoyi\message\sms\provider\sltd\ws\I5GReportResponse.class
com\ruoyi\message\sms\provider\aliyun\AliYunSmsSender$SendSmsRequestAdapter.class
com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsertSoap_PortType.class
com\ruoyi\message\sms\provider\sltd\ws\I5GSendResponse.class
com\ruoyi\message\sms\provider\aliyun\AliYunSmsApi.class
com\ruoyi\message\sms\provider\sltd\ws\SMSVerifyPrivateResponse.class
com\ruoyi\message\config\XhsmsConfig.class
com\ruoyi\message\sms\provider\hzszlyy\ShiyiSmsApi.class
com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsert.class
com\ruoyi\message\sms\provider\shiyi\ShiyiHisSmsSender.class
com\ruoyi\message\sms\provider\sltd\ws\SMSSaveContent.class
com\ruoyi\message\sms\provider\sltd\SltdResponseParser.class
com\ruoyi\message\sms\provider\sltd\ws\SmsServiceSoap.class
com\ruoyi\message\wechat\provider\hzszlyy\ShiyiMpApi.class
com\ruoyi\message\wechat\WechatSender.class
com\ruoyi\message\sms\provider\sltd\ws\SMSGetUserListResponse.class
com\ruoyi\message\sms\provider\sltd\ws\Hello.class
com\ruoyi\message\sms\provider\sltd\ws\I5GSearchNumber.class
com\ruoyi\message\sms\provider\sltd\ws\HelloResponse.class
com\ruoyi\message\SendException.class
com\ruoyi\message\wechat\provider\sdk\WxMpSdkSender.class
com\ruoyi\message\sms\provider\sltd\ws\SMSVerify.class
com\ruoyi\message\sms\provider\sltd\ws\SMSGetUserList.class
com\ruoyi\message\sms\provider\sltd\ws\SMSGetTemplateListResponse.class
com\ruoyi\message\sms\provider\ls\LishuiMessageConfigProvider.class
com\ruoyi\message\sms\provider\xh\XinHuaSmsSender.class
com\ruoyi\message\sms\SmsSendRequest.class
com\ruoyi\message\sms\provider\messaginginsert\MessagingInsertSmsApi.class
com\ruoyi\message\sms\provider\aliyun\AliYunSmsSender$1.class
com\ruoyi\message\sms\provider\sltd\ws\SMSVerifyPrivate.class
com\ruoyi\message\sms\provider\shiyi\ShiyiSmsUtil.class
com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsertSoap_BindingStub.class
com\ruoyi\message\sms\provider\shiyi\ShiyiSmsResponse.class
com\ruoyi\message\SendResult.class
com\ruoyi\message\sms\provider\sltd\ws\package-info.class
com\ruoyi\message\wechat\WechatSendRequest.class
com\ruoyi\message\sms\provider\sltd\ws\SMSGetNumberResponse.class
com\ruoyi\message\sms\provider\sltd\ws\SMSSaveContentResponse.class
com\ruoyi\message\sms\provider\sltd\ws\SMSVerifyResponse.class
com\ruoyi\message\config\MessageProperties$MessagingInsert.class
com\ruoyi\message\sms\provider\sltd\ws\I5GReply.class
com\ruoyi\message\sms\provider\sltd\ws\I5GSearchNumberResponse.class
com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsertSoap12Stub.class
com\ruoyi\message\sms\provider\sltd\ws\I5GSend.class
com\ruoyi\message\sms\provider\aliyun\AliYunSmsSender.class
com\ruoyi\message\config\AliyunSmsConfig.class
com\ruoyi\message\config\RootMessageProperties.class
com\ruoyi\message\wechat\WechatMpConfigProvider.class
com\ruoyi\message\sms\provider\nhfy\MtSubmitSmUtil.class
com\ruoyi\message\sms\provider\sltd\ws\SmsService.class
com\ruoyi\message\sms\SmsSender.class
com\ruoyi\message\config\MtSmsConfig.class
com\ruoyi\message\sms\provider\sltd\ws\I5GReport.class
com\ruoyi\message\sms\provider\sltd\ws\ObjectFactory.class
com\ruoyi\message\config\MessageProperties$Xinhua.class
com\ruoyi\message\sms\provider\sltd\ws\I5GReplyResponse.class
com\ruoyi\message\config\MessageProperties$ShiyiWechat.class
com\ruoyi\message\config\MessageProperties$ShiyiSms.class
com\ruoyi\message\sms\provider\nhfy\MtSubmitSmResp.class
com\ruoyi\message\sms\provider\messaginginsert\MessagingInsertSmsSender.class
com\ruoyi\message\config\MessageProperties$Sms.class
com\ruoyi\message\wechat\provider\hzszlyy\ShiyiMpSender.class
com\ruoyi\message\sms\provider\ls\LishuiMessageConfig.class
com\ruoyi\message\sms\provider\sltd\ws\SMSGetNumber.class
com\ruoyi\message\sms\provider\sltd\ws\SMSGetTemplateList.class
com\ruoyi\message\sms\provider\hzszlyy\ShiyiSmsSender.class
com\ruoyi\message\sms\provider\nhfy\NhfySmsSender.class
com\ruoyi\message\sms\provider\sltd\SltdSmsSender.class
com\ruoyi\message\sms\SmsSenderFactory.class
com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsertLocator.class
com\ruoyi\message\wechat\provider\xh\XinHuaMpSender.class
ruoyi-message/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\config\XhsmsConfig.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\shiyi\ShiyiSmsUtil.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\Hello.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\SltdResponseParser.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\ObjectFactory.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\WechatMpConfigProvider.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\provider\hzszlyy\ShiyiMpApi.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\config\AliyunSmsConfig.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\shiyi\ShiyiSmsRequest.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSGetUserList.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\WechatSenderFactory.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\AbstractSmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\config\MtSmsConfig.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSSaveContentResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSVerifyResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\config\RootMessageProperties.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\provider\xh\XinHuaWXReq.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\aliyun\AliYunSmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\ls\LishuiSmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsertSoap_BindingStub.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\SltdSmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\shiyi\ShiyiSmsResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSVerify.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\aliyun\AliYunSmsApi.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\messaginginsert\MessagingInsertSmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSVerifyPrivate.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\MessageChannel.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\provider\sdk\WxMpSdkSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\provider\xh\XinHuaMpSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\I5GReport.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\config\MessageConfigValidator.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsertLocator.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\hzszlyy\ShiyiSmsApi.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\I5GReplyResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\nhfy\MtSubmitSmUtil.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSGetTemplateListResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\SmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSVerifyPrivateResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\SmsSenderFactory.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsertSoap12Stub.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSSaveContent.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\WechatSendRequest.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\I5GSearchNumber.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSGetTemplateList.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\SmsSendRequest.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\I5GSendResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSGetUserListResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\support\WxMpUtils.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\shiyi\ShiyiHisSmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\xh\XinHuaSmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\messaginginsert\MessagingInsertSmsApi.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\nhfy\MtSubmitSmResp.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\I5GSend.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SmsService.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSGetNumberResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SmsServiceSoap.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\SendException.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\MessageSend.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\SMSGetNumber.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\package-info.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\HelloResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\WechatMpConfig.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsertSoap_PortType.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\messaginginsert\ws\MessagingInsert.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\hzszlyy\ShiyiSmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\provider\xh\XinHuaMpApi.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\I5GReply.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\ls\LishuiMessageConfigProvider.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\config\MessageProperties.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\I5GSearchNumberResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\ls\LishuiMessageConfig.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\nhfy\NhfySmsSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\sltd\ws\I5GReportResponse.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\WechatSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\sms\provider\ls\LishuiStaticConfig.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\wechat\provider\hzszlyy\ShiyiMpSender.java
D:\daima\smartor\smartor\ruoyi-message\src\main\java\com\ruoyi\message\SendResult.java
ruoyi-message/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
com\ruoyi\message\config\MessagePropertiesBindingCheck.class
com\ruoyi\message\config\PerSiteBindingCheck.class
ruoyi-message/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
D:\daima\smartor\smartor\ruoyi-message\src\test\java\com\ruoyi\message\config\MessagePropertiesBindingCheck.java
D:\daima\smartor\smartor\ruoyi-message\src\test\java\com\ruoyi\message\config\PerSiteBindingCheck.java
ruoyi-message/target/ruoyi-message-3.8.5.jar
Binary files differ
ruoyi-message/target/test-classes/com/ruoyi/message/config/MessagePropertiesBindingCheck.class
Binary files differ
ruoyi-message/target/test-classes/com/ruoyi/message/config/PerSiteBindingCheck.class
Binary files differ
ruoyi-message/target/test-classes/message-binding-check.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
# ä»…用于 MessagePropertiesBindingCheck:模拟各站点 application-<profile>.yml çš„「根级扁平 key」
# æ³¨æ„ï¼šè¿™äº› key åœ¨çœŸå®žé…ç½®æ–‡ä»¶é‡Œå°±æ˜¯æ ¹çº§çš„,不带任何前缀
xhsmsPath: http://192.16.4.220:8092/sendSms
xhsmsAccount: 911124
xhsmsPwd: zW5eXe
xhsmsjrm: 1069055
sms_accountName: S200232
sms_password: lD94Yo
sms_url: http://open.nhyfy.cn/kapi/gw-api/hntypt/ucpmsg/send
sms_sourceAddr: 1069100020341
sms_serviceCode: PUSH
sms_appkey: ak-BFq4NN0TMf92J7KAioNYGz74
sms_flag: ã€å—华大学附属第一医院】
accessKeyId: LTAI5tTestKeyId
accessKeySecret: testSecret
signName: æ­å·žåˆ©æ¹–科技
ruoyi-quartz/pom.xml
@@ -44,6 +44,12 @@
            <version>3.8.5</version>
        </dependency>
        <!-- æ¶ˆæ¯å‘送(短信/微信公众号)模块 -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-message</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -11,13 +11,15 @@
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.service.IDingTalkService;
import com.ruoyi.common.dx.MessageSend;
import com.ruoyi.common.enums.MsgLSEnum;
import com.ruoyi.common.enums.ServiceFromEnum;
import com.ruoyi.common.enums.WxGZHEnum;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.common.utils.sms.smsUtils;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.SmsSenderFactory;
import com.ruoyi.message.sms.provider.xh.XinHuaSmsSender;
import com.ruoyi.message.wechat.WechatSendRequest;
import com.ruoyi.message.wechat.WechatSenderFactory;
import com.ruoyi.quartz.constant.MzGatherConstant;
import com.ruoyi.quartz.enums.MzGatherParamEnum;
import com.ruoyi.quartz.enums.MzGatherSlotEnum;
@@ -29,8 +31,7 @@
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysUserService;
import com.smartor.common.LSHospTokenUtil;
import com.smartor.common.MtSubmitSmUtil;
import com.ruoyi.message.sms.provider.sltd.ws.MessageSend;
import com.smartor.common.QwenLLMUtil;
import com.smartor.domain.*;
import com.smartor.domain.VO.ServiceSubtaskDetailTraceVO;
@@ -49,7 +50,6 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@@ -59,8 +59,6 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static cn.hutool.core.convert.Convert.toHex;
/**
 * å®šæ—¶ä»»åŠ¡è°ƒåº¦æµ‹è¯•
@@ -122,18 +120,6 @@
    @Value("${localIP}")
    private String localIP;
    @Value("${xhsmsPath}")
    private String xhsmsPath;
    @Value("${xhsmsAccount}")
    private String xhsmsAccount;
    @Value("${xhsmsPwd}")
    private String xhsmsPwd;
    // è™šæ‹ŸæŽ¥å…¥ç 
    @Value("${xhsmsjrm}")
    private String xhsmsjrm;
    @Value("${pub_key}")
    private String pub_key;
@@ -192,8 +178,17 @@
    @Value("${server.port}")
    private String port;
    /**
     * æ¶ˆæ¯å‘送方工厂(短信/微信公众号)。
     * <p>
     * åŽŸæ¥çŸ­ä¿¡ä¸Žå…¬ä¼—å·çš„å‘é€é€»è¾‘åœ¨æœ¬ç±»é‡Œç”¨ {@code if (active.equals("xx"))} ç¡¬åˆ†æ”¯äº†çº¦ 300 è¡Œï¼Œ
     * çŽ°å·²æ”¶å£åˆ° {@code ruoyi-message} æ¨¡å—,这里只负责按 {@code active} è·¯ç”±ã€‚
     */
    @Autowired
    private MtSubmitSmUtil mtSubmitSmUtil;
    private SmsSenderFactory smsSenderFactory;
    @Autowired
    private WechatSenderFactory wechatSenderFactory;
    @Value("${spring.profiles.active}")
    private String active;
@@ -1483,124 +1478,11 @@
                    serviceOutPath.setUpdateTime(new Date());
                    serviceOutPath.setUrl(url);
                    iServiceOutPathService.updateServiceOutPath(serviceOutPath);
                    String isSuccess = null;
                    //短信发送:各站点对接方式差异较大(报文/签名/token/返回判定都不同),
                    //已收口到 ruoyi-message æ¨¡å—,这里只负责组装入参并路由
                    log.info("active的值为:{}", active);
                    if (active.equals("xh")) {
                        Map<String, String> req = new HashMap<>();
//                        req.put("phone", sendMagParam.getPhone());
//                        req.put("content", sendMagParam.getContent());
                        Map<String, String> map2 = new HashMap<String, String>();
                        map2.put("action", "send");
                        map2.put("account", xhsmsAccount);
                        map2.put("password", xhsmsPwd);
                        map2.put("mobile", sendMagParam.getPhone());
                        map2.put("content", sendMagParam.getContent());
                        map2.put("extno", xhsmsjrm);
                        map2.put("rt", "json");
                        StringBuilder encodedParams = new StringBuilder();
                        for (Map.Entry<String, String> entry : map2.entrySet()) {
                            if (encodedParams.length() > 0) {
                                encodedParams.append("&");
                            }
                            // å¯¹é”®å’Œå€¼è¿›è¡ŒURL编码
                            String encodedKey = URLEncoder.encode(entry.getKey(), "UTF-8");
                            String encodedValue = URLEncoder.encode(entry.getValue(), "UTF-8");
                            encodedParams.append(encodedKey).append("=").append(encodedValue);
                        }
                        Boolean result = HttpUtils.sendPost(xhsmsPath, new Gson().toJson(req));
                        if (result) isSuccess = "true";
                    } else if (active.equals("ls")) {
//                        String url = configService.selectConfigByKey("token.360", serviceSubtask.getOrgid());
//                        //丽水的短信发送方式
//                        Map<String, String> map = MsgLSEnum.getAddressByCode(serviceSubtask.getOrgid());
                        SysConfig config = new SysConfig();
                        config.setConfigKey("ls.msg.info");
                        config.setOrgid(serviceSubtask.getOrgid());
                        List<SysConfig> sysConfigs = configService.selectConfigList(config);
                        Map<String, String> msgInfo = new HashMap<>();
                        if (CollectionUtils.isNotEmpty(sysConfigs)) {
                            String configValue = sysConfigs.get(0).getConfigValue();
                            ObjectMapper objectMapper = new ObjectMapper();
                            if (StringUtils.isNotEmpty(configValue)) {
                                msgInfo = objectMapper.readValue(configValue, Map.class);
                            }
                        }
                        log.info("-----------map的值为:{}", msgInfo);
                        String token = LSHospTokenUtil.getToken(serviceSubtask.getOrgid(), msgInfo.get("tokenUrl"));
                        if (ObjectUtils.isNotEmpty(msgInfo) && StringUtils.isEmpty(msgInfo.get("address"))) {
                            setSuccessPreachForm(serviceSubtask, sendPreachform, "短信发送失败,该机构没有配置短信地址, æ—¶é—´:" + now, "5");
                            return;
                        }
                        log.info("【sfHandlle】丽水短信发送,地址:{},参数:{},机构ID:{}", msgInfo.get("address"), sendMagParam, serviceSubtask.getOrgid());
                        String dxCode = getDXCode(msgInfo.get("address"), sendMagParam.getPhone(), sendMagParam.getContent(), msgInfo.get("sendPersonId"), msgInfo.get("sendPersonName"), MsgLSEnum.getHeaderByCode(msgInfo), token);
                        log.info("【sfHandlle】丽水短信发送结果:{}", dxCode);
                        ObjectMapper objectMapper = new ObjectMapper();
                        Map<String, Object> textParam = objectMapper.readValue(dxCode, Map.class);
                        String code = textParam.get("Code").toString();
                        if (code.equals("0")) {
                            isSuccess = "true";
                        }
                    } else if (active.equals("sltd") || active.equals("nhfy")) {
                        log.info("sltd进来了吗?{}   ,---sendMagParam.getContent()的参数为:{}", active, sendMagParam.getContent());
                        if (active.equals("sltd")) {
                            //省立同德的短信发送方式
                            String data = MessageSend.sendMsg(sendMagParam.getContent(), "6", sendMagParam.getPhone(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
                            if (StringUtils.isNotEmpty(data)) {
                                // è§£æž result=0&description=成功&faillist= æ ¼å¼ï¼ˆresult=0成功,1失败)
                                log.info("【sfHandlle】省立同德短信发送返回:{}", data);
                                String[] pairs = data.split("&");
                                String resultVal = null;
                                for (String pair : pairs) {
                                    String[] kv = pair.split("=", 2);
                                    if ("result".equals(kv[0])) {
                                        resultVal = kv.length > 1 ? kv[1] : null;
                                        break;
                                    }
                                }
                                if ("0".equals(resultVal)) {
                                    isSuccess = "true";
                                }
                            }
                        } else if (active.equals("nhfy")) {
                            Map<String, Object> smsResult = mtSubmitSmUtil.submitSm(sendMagParam.getPhone(), sendMagParam.getContent());
                            if (smsResult != null && smsResult.get("result") != null && smsResult.get("result").toString().equals("0")) {
                                isSuccess = "true";
                            }
                        }
                    } else if (active.equals("hzszlyy")) {//市一医院
                        String content = sendMagParam.getContent().replace("【新华医院】", "");
                        SysDept sysDeptVo = new SysDept();
                        List<SysDept> sysDepts = new ArrayList<>();
                        if (ObjectUtils.isNotEmpty(serviceSubtask.getDeptcode())) {
                            sysDeptVo.setDeptType("1");
                            sysDeptVo.setOrgid(serviceSubtask.getOrgid());
                            sysDeptVo.setDeptCode(serviceSubtask.getDeptcode());
                            sysDeptService.selectDeptList(sysDeptVo);
                        } else if (ObjectUtils.isNotEmpty(serviceSubtask.getLeavehospitaldistrictcode())) {
                            sysDeptVo.setDeptType("2");
                            sysDeptVo.setOrgid(serviceSubtask.getOrgid());
                            sysDeptVo.setDeptCode(serviceSubtask.getLeavehospitaldistrictcode());
                            sysDeptService.selectDeptList(sysDeptVo);
                        }
                        SysDept sysDept = CollectionUtils.isNotEmpty(sysDepts) ? sysDepts.get(0) : new SysDept();
                        String campusid = ObjectUtils.isNotEmpty(sysDept) ? sysDept.getCampusid() : "2";
                        //湖滨单独发送方式
                        if ("1".equals(campusid)) {
                            String result = smsUtils.sendSms1(xhsmsPath, xhsmsAccount, xhsmsPwd, sendMagParam.getPhone(), content);
                            //这里需要根据result返回的值去判断,是否成功,然后将值赋值给isSuccess
                            if (StringUtils.isNotEmpty(result) && result.contains("处理成功")) {
                                isSuccess = "true";
                            }
                        } else {
                            String result = smsUtils.sendSms(xhsmsPath, xhsmsAccount, xhsmsPwd, sendMagParam.getPhone(), content);
                            //这里需要根据result返回的值去判断,是否成功,然后将值赋值给isSuccess
                            if (StringUtils.isNotEmpty(result) && result.contains("处理成功")) {
                                isSuccess = "true";
                            }
                        }
                    }
                    SendResult smsSendResult = sendSmsByActive(serviceSubtask, sendMagParam);
                    String isSuccess = smsSendResult.asLegacyFlag();
                    //任务发送记录
                    ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord();
                    serviceSubtaskRecord.setTaskid(serviceSubtask.getTaskid().toString());
@@ -1659,29 +1541,11 @@
                    serviceOutPath.setUrl(url);
                    iServiceOutPathService.updateServiceOutPath(serviceOutPath);
                    //获取微信公众号请求信息根据机构ID
                    List<String> wxqqxx = WxGZHEnum.getDescByCode(patArchive.getOrgid());
                    //市一医院 ä¸æ ¡éªŒ
                    //微信公众号发送:市一与新华是两套完全不同的网关与判定标准,
                    //机构配置校验、HIS患者ID校验都已收口到 ruoyi-message çš„发送方实现里
                    SendResult wechatSendResult = sendWechatByActive(serviceSubtask, patArchive, url);
                    Boolean wxCodeSuccess = wechatSendResult.isOk();
                    if (CollectionUtils.isEmpty(wxqqxx) || wxqqxx.size() < 4) {
                        setSuccessPreachForm(serviceSubtask, sendPreachform, "【公众号】该机构的公众号配置信息不全,无法通过公众号发送", "4");
                        return;
                    }
                    if (StringUtils.isEmpty(patArchive.getPatidHis())) {
                        setSuccessPreachForm(serviceSubtask, sendPreachform, "【公众号】公众号发送失败,his系统的患者id为空", "5");
                        return;
                    }
                    String wxCode = "";
                    //市一医院
                    if (active.equals("hzszlyy")) {
                        //处理中文乱码问题
                        wxCode = smsUtils.sendChat(url, patArchive.getTelcode(), serviceSubtask.getSfzh());
                    } else {
                        wxCode = getWXCode(serviceSubtask.getSfzh(), url, serviceSubtask.getTaskName(), serviceSubtask.getTaskDesc(), patArchive.getTelcode(), serviceSubtask.getSendname(), patArchive.getPatidHis(), wxqqxx);
                    }
                    Map map = JSONObject.parseObject(wxCode, Map.class);
                    //任务发送记录
                    ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord();
                    serviceSubtaskRecord.setTaskid(serviceSubtask.getTaskid().toString());
@@ -1690,22 +1554,7 @@
                    serviceSubtaskRecord.setPreachform("4");
                    serviceSubtaskRecord.setStartTime(System.currentTimeMillis());
                    serviceSubtaskRecord.setResult("success");
                    Boolean wxCodeSuccess = false;
                    if (active.equals("hzszlyy")) {
                        if (!map.isEmpty() && map.get("success") != null) {
                            Integer wxCodeSuccessShiyi = (Integer) map.get("success");
                            if (ObjectUtils.isNotEmpty(wxCodeSuccessShiyi) && wxCodeSuccessShiyi == 1) {
                                wxCodeSuccess = true;
                            } else {
                                wxCodeSuccess = false;
                            }
                        }
                    } else {
                        if (!map.isEmpty() && map.get("succ") != null) {
                            wxCodeSuccess = (boolean) map.get("succ");
                        }
                    }
                    if (!map.isEmpty() && !wxCodeSuccess) serviceSubtaskRecord.setResult("error");
                    if (!wxCodeSuccess) serviceSubtaskRecord.setResult("error");
                    serviceSubtaskRecordService.insertServiceSubtaskRecord(serviceSubtaskRecord);
                    //发送完成后,修改状态
@@ -1714,7 +1563,7 @@
                        serviceSubtask.setResult("success");
                        setSuccessPreachForm(serviceSubtask, sendPreachform, "【公众号】公众号发送成功", "2");
                    } else {
                        log.error("【sfHandlle】公众号发送失败,患者信息:{},错误信息:{}", serviceSubtask, map.get("msg"));
                        log.error("【sfHandlle】公众号发送失败,患者信息:{},错误信息:{}", serviceSubtask, wechatSendResult.getMessage());
                        setSuccessPreachForm(serviceSubtask, sendPreachform, "【公众号】公众号发送失败", "5");
                    }
                } catch (Exception e) {
@@ -1727,6 +1576,72 @@
            setSuccessPreachForm(serviceSubtask, sendPreachform, "人工发送成功", "2");
        }
    }
    /**
     * æŒ‰ {@code active}(站点)路由到对应的短信发送方。
     * <p>
     * å„站点差异见 {@code ruoyi-message} æ¨¡å—各实现类;本方法只做「入参组装 + è·¯ç”± + æ—¥å¿—」。
     * <p>
     * âš ï¸ æ–°åŽï¼ˆ{@code xh})站点仍走「旧实现(发送空报文)」,与原 {@code RyTask} è¡Œä¸ºä¸€å­—不差,
     * ç¡®è®¤ç½‘关报文格式后应改为 {@code XinHuaSmsSender#sendForm}。详见该类的 TODO è¯´æ˜Žã€‚
     */
    private SendResult sendSmsByActive(ServiceSubtask serviceSubtask, SendMagParam sendMagParam) {
        SmsSendRequest request = SmsSendRequest.of(sendMagParam.getPhone(), sendMagParam.getContent(), serviceSubtask.getOrgid());
        //市一区分湖滨/吴山两套短信签名
        if ("hzszlyy".equals(active)) {
            request.setCampus(resolveShiyiCampus(serviceSubtask));
        }
        if ("xh".equals(active)) {
            XinHuaSmsSender xinHuaSmsSender = (XinHuaSmsSender) smsSenderFactory.get(active);
            return xinHuaSmsSender.sendJsonLegacy(request);
        }
        return smsSenderFactory.send(active, request);
    }
    /**
     * æŒ‰ {@code active}(站点)路由到对应的微信公众号发送方。
     * <p>
     * âš ï¸ æ–°åŽå…¬ä¼—号必须传患者信息(姓名 + HIS æ‚£è€…ID),与原
     * {@code getWXCode(...)} çš„入参保持一一对应。
     */
    private SendResult sendWechatByActive(ServiceSubtask serviceSubtask, PatArchive patArchive, String url) {
        WechatSendRequest request = new WechatSendRequest();
        request.setOrgid(patArchive.getOrgid());
        request.setIdCard(serviceSubtask.getSfzh());
        request.setUrl(url);
        request.setTitle(serviceSubtask.getTaskName());
        request.setContent(serviceSubtask.getTaskDesc());
        request.setPhone(patArchive.getTelcode());
        request.setPatientName(serviceSubtask.getSendname());
        request.setPatidHis(patArchive.getPatidHis());
        return wechatSenderFactory.send(active, request);
    }
    /**
     * æŸ¥è¯¢æ‚£è€…所在院区(1=湖滨,其他=吴山)。
     * <p>
     * TODO å¾…确认:原实现并未把 {@code sysDeptService.selectDeptList(...)} çš„返回值赋给
     * {@code sysDepts},因此 {@code sysDepts} æ’为空列表、{@code campusid} æ’为 {@code "2"},
     * æ¹–滨院区实际上永远走不到 {@code sendSms1}。本次重构<b>保持该行为不变</b>,
     * è¯·åœ¨ä¸šåŠ¡ç¡®è®¤åŽå†å†³å®šæ˜¯å¦ä¿®æˆ {@code sysDepts = sysDeptService.selectDeptList(...)}。
     */
    private String resolveShiyiCampus(ServiceSubtask serviceSubtask) {
        SysDept sysDeptVo = new SysDept();
        List<SysDept> sysDepts = new ArrayList<>();
        if (ObjectUtils.isNotEmpty(serviceSubtask.getDeptcode())) {
            sysDeptVo.setDeptType("1");
            sysDeptVo.setOrgid(serviceSubtask.getOrgid());
            sysDeptVo.setDeptCode(serviceSubtask.getDeptcode());
            sysDeptService.selectDeptList(sysDeptVo);
        } else if (ObjectUtils.isNotEmpty(serviceSubtask.getLeavehospitaldistrictcode())) {
            sysDeptVo.setDeptType("2");
            sysDeptVo.setOrgid(serviceSubtask.getOrgid());
            sysDeptVo.setDeptCode(serviceSubtask.getLeavehospitaldistrictcode());
            sysDeptService.selectDeptList(sysDeptVo);
        }
        SysDept sysDept = CollectionUtils.isNotEmpty(sysDepts) ? sysDepts.get(0) : new SysDept();
        return ObjectUtils.isNotEmpty(sysDept) ? sysDept.getCampusid() : "2";
    }
@@ -1863,74 +1778,10 @@
        return 0;
    }
    private String getDXCode(String address, String ShouJiHM, String FaSongNR, String FaSongRID, String FaSongRXM, Map<String, Object> headerMap, String token) {
        Map<String, Object> map = new LinkedHashMap<>();
        Map<String, Object> YeWuXX = new LinkedHashMap<>();
        Map<String, Object> DuanXinXX = new LinkedHashMap<>();
        DuanXinXX.put("ShouJiHM", ShouJiHM);
        DuanXinXX.put("FaSongNR", FaSongNR);
        DuanXinXX.put("FaSongRID", FaSongRID);
        DuanXinXX.put("FaSongRXM", FaSongRXM);
        YeWuXX.put("DuanXinXX", DuanXinXX);
        map.put("XiaoXiTou", headerMap);
        map.put("YeWuXX", YeWuXX);
        String body = new Gson().toJson(map);
//        String result = HttpUtils.sendPost(address, body);
        String result = null;
        try {
            result = OkHttpExample.sendPostRequest(address, body, token);
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info("【getDXCode】短信发送结果:{}", result);
        JSONObject jsonObject = JSONObject.parseObject(result);
        String code = (String) jsonObject.toString();
        return code;
    }
    private String getWXCode(String idcard, String url, String taskName, String taskDesc, String phone, String setPatientname, String setPatientid, List<String> wxqqxx) {
        XinHuaWXReq xinHuaWXReq = new XinHuaWXReq();
        xinHuaWXReq.setIdcard(idcard);
        xinHuaWXReq.setUrl(url);
        xinHuaWXReq.setPatientid(setPatientid);
        xinHuaWXReq.setPatientname(setPatientname);
        xinHuaWXReq.setMobile(phone);
        xinHuaWXReq.setMedcardno(null);
        xinHuaWXReq.setTitlename(taskName);
        xinHuaWXReq.setContent(taskDesc);
        xinHuaWXReq.setAppid(wxqqxx.get(1));
        log.info("【getWXCode】微信公众号请求参数:{}", xinHuaWXReq);
        String body = new Gson().toJson(xinHuaWXReq);
        String encode = encode(wxqqxx.get(2).concat(body));
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("sign", encode);
        String result = HttpUtils.sendPostByHeader(wxqqxx.get(3), body, headerMap);
        log.info("【getWXCode】微信公众号返回参数:{}", result);
        JSONObject jsonObject = JSONObject.parseObject(result);
        String code = (String) jsonObject.toString();
        return code;
    }
    public String encode(String arg) {
        if (arg == null) {
            arg = "";
        }
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            md5.update(arg.getBytes("UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return toHex(md5.digest());
    }
    // ---------------------------------------------------------------------
    // è¯´æ˜Žï¼šåŽŸ getDXCode / getWXCode / encode ä¸‰ä¸ªç§æœ‰æ–¹æ³•已随发送方一起收口到
    // ruoyi-message æ¨¡å—(LishuiSmsSender / XinHuaMpApi),此处已删除,避免重复实现。
    // ---------------------------------------------------------------------
    public void collectHIS() {
        try {
ruoyi-system/pom.xml
@@ -23,6 +23,12 @@
            <artifactId>ruoyi-common</artifactId>
        </dependency>
        <!-- æ¶ˆæ¯å‘送(短信/微信公众号)模块 -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-message</artifactId>
        </dependency>
        <!-- WebService (短信发送) -->
        <dependency>
            <groupId>org.apache.axis</groupId>
ruoyi-system/src/main/java/WebServiceClient/MessagingInsertTestCase.java
ÎļþÒÑɾ³ý
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SmsParamServiceImpl.java
@@ -1,17 +1,13 @@
package com.ruoyi.system.service.impl;
import WebServiceClient.MessagingInsertLocator;
import WebServiceClient.MessagingInsertSoap12Stub;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.message.sms.provider.messaginginsert.MessagingInsertSmsApi;
import com.ruoyi.system.domain.SmsParam;
import com.ruoyi.system.mapper.SmsParamMapper;
import com.ruoyi.system.service.ISmsParamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.xml.rpc.holders.IntHolder;
import java.rmi.RemoteException;
import java.util.Date;
import java.util.List;
/**
@@ -24,6 +20,12 @@
public class SmsParamServiceImpl implements ISmsParamService {
    @Autowired
    private SmsParamMapper smsParamMapper;
    /**
     * é¢„约挂号短信平台(MessagingInsert)API,已收口到 ruoyi-message æ¨¡å—
     */
    @Autowired
    private MessagingInsertSmsApi messagingInsertSmsApi;
    /**
     * æŸ¥è¯¢çŸ­ä¿¡å‚æ•°
@@ -96,19 +98,8 @@
    @Override
    public int sendSmsInfo(String info, String phone) {
        MessagingInsertSoap12Stub binding = null;
        try {
            binding = (MessagingInsertSoap12Stub) new MessagingInsertLocator().getMessagingInsertSoap12();
        } catch (Exception e) {
            e.printStackTrace();
        }
        String aa = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<Request>\n" + "<operate>S001</operate>\n" + "<Sheets>\n" + "<Sheet>\n" + "<Content>" + info + "</Content>\n" + "<Mobile>" + phone + "</Mobile>\n" + "<DateTime>" + new Date() + "</DateTime>\n" + "<Type>预约挂号</Type>\n" + "</Sheet>\n" + "</Sheets>\n" + "</Request>";
        Integer integer = null;
        try {
            integer = binding.SMSMessageAccept(aa, new IntHolder(), new javax.xml.rpc.holders.StringHolder());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return integer;
        //预约挂号短信平台(MessagingInsert,Axis SOAP12)已收口到 ruoyi-message æ¨¡å—
        Integer result = messagingInsertSmsApi.sendSmsInfo(info, phone);
        return result == null ? 0 : result;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SmsRecordsServiceImpl.java
@@ -1,25 +1,18 @@
package com.ruoyi.system.service.impl;
import java.io.IOException;
import java.sql.Date;
import java.time.LocalDate;
import java.util.*;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.ruoyi.common.enums.MsgLSEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.OkHttpExample;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.SmsSenderFactory;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.common.core.service.ISubtaskSmsService;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.system.mapper.SmsRecordsMapper;
@@ -41,8 +34,11 @@
    @Autowired
    private ISysConfigService iSysConfigService;
    /**
     * æ¶ˆæ¯å‘送方工厂(短信),丽水短信已收口到 ruoyi-message
     */
    @Autowired
    private ISysConfigService configService;
    private SmsSenderFactory smsSenderFactory;
    /**
     * æŸ¥è¯¢çŸ­ä¿¡è®°å½•
     *
@@ -116,39 +112,18 @@
    public Boolean sendSMS(String orgid, String phone, String content) {
        //丽水的短信发送方式
        Boolean isSuccess = false;
        //丽水的短信发送方式:已收口到 ruoyi-message çš„ LishuiSmsSender
        //(机构配置从 sys_config çš„ ls.msg.info è¯»å–,token ç”± smartor æ³¨å†Œçš„
        //  LishuiMessageConfigProvider æä¾›ï¼‰ï¼Œè¿™é‡Œä¸å†è‡ªå·±æ‹¼æŠ¥æ–‡
        try {
            SysConfig config = new SysConfig();
            config.setConfigKey("ls.msg.info");
            config.setOrgid(orgid);
            List<SysConfig> sysConfigs = configService.selectConfigList(config);
            Map<String, String> msgInfo = new HashMap<>();
            if (CollectionUtils.isNotEmpty(sysConfigs)) {
                String configValue = sysConfigs.get(0).getConfigValue();
                ObjectMapper objectMapper = new ObjectMapper();
                if (StringUtils.isNotEmpty(configValue)) {
                    msgInfo = objectMapper.readValue(configValue, Map.class);
                }
            }
            log.info("-----------map的值为:{}", msgInfo);
            ISubtaskSmsService subtaskSmsService = SpringUtils.getBean(ISubtaskSmsService.class);
            String token = subtaskSmsService.getLSHospToken(orgid, msgInfo.get("tokenUrl"));
            log.info("【sfHandlle】丽水短信发送,地址:{},参数:{},机构ID:{}", msgInfo.get("address"), orgid);
            String dxCode = getDXCode(msgInfo.get("address"), phone, content, msgInfo.get("sendPersonId"), msgInfo.get("sendPersonName"), MsgLSEnum.getHeaderByCode(msgInfo), token);
            log.info("【sfHandlle】丽水短信发送结果:{}", dxCode);
            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, Object> textParam = null;
            textParam = objectMapper.readValue(dxCode, Map.class);
            String code = textParam.get("Code").toString();
            if (code.equals("0")) {
                isSuccess = true;
            }
            SendResult sendResult = smsSenderFactory.send("ls",
                    SmsSendRequest.of(phone, content, orgid));
            log.info("【SmsRecordsServiceImpl】丽水短信发送结果:{}", sendResult);
            return sendResult.isOk();
        } catch (Exception e) {
            e.printStackTrace();
            log.error("【SmsRecordsServiceImpl】丽水短信发送异常,orgid:{},phone:{}", orgid, phone, e);
            return false;
        }
        return isSuccess;
    }
    /**
@@ -162,7 +137,6 @@
        smsRecords.setUpdateTime(DateUtils.getNowDate());
        return smsRecordsMapper.updateSmsRecords(smsRecords);
    }
    /**
     * æ‰¹é‡åˆ é™¤çŸ­ä¿¡è®°å½•
     *
@@ -185,34 +159,8 @@
        return smsRecordsMapper.deleteSmsRecordsByRecordid(recordid);
    }
    private String getDXCode(String address, String ShouJiHM, String FaSongNR, String FaSongRID, String FaSongRXM, Map<String, Object> headerMap, String token) {
        Map<String, Object> map = new LinkedHashMap<>();
        Map<String, Object> YeWuXX = new LinkedHashMap<>();
        Map<String, Object> DuanXinXX = new LinkedHashMap<>();
        DuanXinXX.put("ShouJiHM", ShouJiHM);
        DuanXinXX.put("FaSongNR", FaSongNR);
        DuanXinXX.put("FaSongRID", FaSongRID);
        DuanXinXX.put("FaSongRXM", FaSongRXM);
        YeWuXX.put("DuanXinXX", DuanXinXX);
        map.put("XiaoXiTou", headerMap);
        map.put("YeWuXX", YeWuXX);
        String body = new Gson().toJson(map);
//        String result = HttpUtils.sendPost(address, body);
        String result = null;
        try {
            result = OkHttpExample.sendPostRequest(address, body, token);
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info("【getDXCode】短信发送结果:{}", result);
        JSONObject jsonObject = JSONObject.parseObject(result);
        String code = (String) jsonObject.toString();
        return code;
    }
    // ---------------------------------------------------------------------
    // è¯´æ˜Žï¼šåŽŸ getDXCode å·²éšå‘送方一起收口到 ruoyi-message çš„ LishuiSmsSender,
    // æ­¤å¤„已删除,避免第三套丽水报文实现继续存在。
    // ---------------------------------------------------------------------
}
smartor/pom.xml
@@ -155,6 +155,11 @@
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-system</artifactId>
        </dependency>
        <!-- æ¶ˆæ¯å‘送(短信/微信公众号)模块 -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-message</artifactId>
        </dependency>
    </dependencies>
smartor/src/main/java/WebServiceClient/MessagingInsert.asmx
ÎļþÒÑɾ³ý
smartor/src/main/java/WebServiceClient/MessagingInsert.java
ÎļþÒÑɾ³ý
smartor/src/main/java/WebServiceClient/MessagingInsertLocator.java
ÎļþÒÑɾ³ý
smartor/src/main/java/WebServiceClient/MessagingInsertSoap12Stub.java
ÎļþÒÑɾ³ý
smartor/src/main/java/WebServiceClient/MessagingInsertSoap_BindingStub.java
ÎļþÒÑɾ³ý
smartor/src/main/java/WebServiceClient/MessagingInsertSoap_PortType.java
ÎļþÒÑɾ³ý
smartor/src/main/java/WebServiceClient/MessagingInsertTestCase.java
ÎļþÒÑɾ³ý
smartor/src/main/java/com/smartor/common/MtSubmitSmUtil.java
ÎļþÒÑɾ³ý
smartor/src/main/java/com/smartor/domain/MtSubmitSmResp.java
ÎļþÒÑɾ³ý
smartor/src/main/java/com/smartor/domain/ShiyiSmsRequest.java
ÎļþÒÑɾ³ý
smartor/src/main/java/com/smartor/domain/ShiyiSmsResponse.java
ÎļþÒÑɾ³ý
smartor/src/main/java/com/smartor/domain/XinHuaWXReq.java
ÎļþÒÑɾ³ý
smartor/src/main/java/com/smartor/service/impl/ServicPhoneServiceImpl.java
@@ -9,7 +9,6 @@
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.service.IConfigService;
import com.ruoyi.common.dx.MessageSend;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.http.HttpUtils;
@@ -18,7 +17,6 @@
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.smartor.common.FtpService;
import com.smartor.common.MtSubmitSmUtil;
import com.smartor.common.ScheduleSubtaskBuilder;
import com.smartor.config.PhoneUtils;
import com.smartor.domain.DTO.ServiceSubtaskDetailDTO;
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -9,17 +9,20 @@
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.service.IConfigService;
import com.ruoyi.common.dx.MessageSend;
import com.ruoyi.common.enums.WxGZHEnum;
import com.ruoyi.message.sms.provider.sltd.ws.MessageSend;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.SmsSenderFactory;
import com.ruoyi.message.wechat.WechatSendRequest;
import com.ruoyi.message.wechat.WechatSenderFactory;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.smartor.common.FtpService;
import com.smartor.common.MtSubmitSmUtil;
import com.smartor.common.ScheduleSubtaskBuilder;
import com.smartor.config.PhoneUtils;
import com.smartor.domain.DTO.*;
@@ -165,10 +168,16 @@
    private ISysConfigService configService;
    @Autowired
    private MtSubmitSmUtil mtSubmitSmUtil;
    private SysConfigMapper sysConfigMapper;
    /**
     * æ¶ˆæ¯å‘送方工厂(短信/微信公众号),替代原来直接调用各站点发送代码的写法
     */
    @Autowired
    private SmsSenderFactory smsSenderFactory;
    @Autowired
    private SysConfigMapper sysConfigMapper;
    private WechatSenderFactory wechatSenderFactory;
    @Autowired
    private RedisTemplate redisTemplate;
@@ -190,9 +199,6 @@
    @Value("${req_path}")
    private String req_path;
    @Value("${xhsmsPath}")
    private String xhsmsPath;
    @Value("${phoneUpEveryHour}")
    private Integer phoneUpEveryHour;
@@ -4213,10 +4219,8 @@
        serviceOutPath.setUpdateTime(new Date());
        serviceOutPath.setUrl(url);
        iServiceOutPathService.updateServiceOutPath(serviceOutPath);
        Map<String, String> map = new HashMap<>();
        map.put("phone", serviceSubtask.getPhone());
        map.put("content", sendMagParam.getContent());
        HttpUtil.postJsonRequest(xhsmsPath, new Gson().toJson(map));
        //短信发送已收口到 ruoyi-message æ¨¡å—;与原实现一致:不判断发送结果
        smsSenderFactory.send(active, SmsSendRequest.of(serviceSubtask.getPhone(), sendMagParam.getContent(), serviceSubtask.getOrgid()));
        redisCache.setCacheObject(subTaskId + "recordAccept-hungup", "1", 10, TimeUnit.MINUTES);
        if (StringUtils.isNotEmpty(serviceSubtask.getRemark()))
            serviceSubtask.setRemark("电话发送拒接,短信补偿发送成功");
@@ -5172,9 +5176,11 @@
            sendMagParam.setContent(smsContemt.replace("${wjurl}", sendMagParam.getUrl()));
        }
        //短信发送:各站点对接方式差异较大,已收口到 ruoyi-message æ¨¡å—
        if (active.equals("nhfy")) {
            Map<String, Object> smsResult = mtSubmitSmUtil.submitSm(sendMagParam.getPhone(), sendMagParam.getContent());
            if (smsResult != null && smsResult.get("result") != null && smsResult.get("result").toString().equals("0")) {
            SendResult smsResult = smsSenderFactory.send(active,
                    SmsSendRequest.of(sendMagParam.getPhone(), sendMagParam.getContent(), serviceSubtask.getOrgid()));
            if (smsResult.isOk()) {
                resultMap.put("code", "200");
                resultMap.put("msg", "发送成功");
                serviceSubtask.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + ";短信发送成功(手动提交)" : "短信发送成功(手动提交)");
@@ -5220,35 +5226,33 @@
            iServiceOutPathService.updateServiceOutPath(serviceOutPath);
            //获取微信公众号请求信息根据机构ID
            List<String> wxqqxx = WxGZHEnum.getDescByCode(serviceSubtask.getOrgid());
            if (CollectionUtils.isEmpty(wxqqxx) || wxqqxx.size() < 4) {
                resultMap.put("code", -1);
                resultMap.put("message", "发送异常:" + "【公众号】该机构的公众号配置信息不全,无法通过公众号发送");
                return resultMap;
            }
            //his患者id校验(与原实现一致:先校验这一项)
            if (StringUtils.isEmpty(serviceSubtask.getPatidHis())) {
                resultMap.put("code", -1);
                resultMap.put("message", "发送异常:" + "【公众号】公众号发送失败,his系统的患者id为空");
                return resultMap;
            }
            String wxCode = "";
            wxCode = getWXCode(serviceSubtask.getSfzh(), url, serviceSubtask.getTaskName(), serviceSubtask.getTaskDesc(), patArchive.getTelcode(), serviceSubtask.getSendname(), patArchive.getPatidHis(), wxqqxx);
            Map map = JSONObject.parseObject(wxCode, Map.class);
            Boolean wxCodeSuccess = false;
            if (!map.isEmpty() && map.get("succ") != null) {
                wxCodeSuccess = (boolean) map.get("succ");
            }
            //机构配置校验、报文签名与返回判定均已收口到 ruoyi-message çš„发送方实现
            WechatSendRequest wechatRequest = new WechatSendRequest();
            wechatRequest.setOrgid(serviceSubtask.getOrgid());
            wechatRequest.setIdCard(serviceSubtask.getSfzh());
            wechatRequest.setUrl(url);
            wechatRequest.setTitle(serviceSubtask.getTaskName());
            wechatRequest.setContent(serviceSubtask.getTaskDesc());
            wechatRequest.setPhone(patArchive.getTelcode());
            wechatRequest.setPatientName(serviceSubtask.getSendname());
            wechatRequest.setPatidHis(patArchive.getPatidHis());
            SendResult wechatResult = wechatSenderFactory.send(active, wechatRequest);
            //发送完成后,修改状态
            serviceSubtask.setSendstate(3L);
            if (wxCodeSuccess) {
            if (wechatResult.isOk()) {
                serviceSubtask.setResult("success");
                resultMap.put("code", "200");
                resultMap.put("msg", "发送成功");
            } else {
                log.error("【sfHandlle】公众号发送失败,患者信息:{},错误信息:{}", serviceSubtask, map.get("msg"));
                log.error("【sfHandlle】公众号发送失败,患者信息:{},错误信息:{}", serviceSubtask, wechatResult.getMessage());
                resultMap.put("code", -1);
                resultMap.put("message", "【公众号】公众号发送失败");
                return resultMap;
@@ -6814,39 +6818,8 @@
        return list;
    }
    private String getWXCode(String idcard, String url, String taskName, String taskDesc, String phone, String setPatientname, String setPatientid, List<String> wxqqxx) {
        XinHuaWXReq xinHuaWXReq = new XinHuaWXReq();
        xinHuaWXReq.setIdcard(idcard);
        xinHuaWXReq.setUrl(url);
        xinHuaWXReq.setPatientid(setPatientid);
        xinHuaWXReq.setPatientname(setPatientname);
        xinHuaWXReq.setMobile(phone);
        xinHuaWXReq.setMedcardno(null);
        xinHuaWXReq.setTitlename(taskName);
        xinHuaWXReq.setContent(taskDesc);
        xinHuaWXReq.setAppid(wxqqxx.get(1));
        log.info("【getWXCode】微信公众号请求参数:{}", xinHuaWXReq);
        String body = new Gson().toJson(xinHuaWXReq);
        String encode = encode(wxqqxx.get(2).concat(body));
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("sign", encode);
        String result = HttpUtils.sendPostByHeader(wxqqxx.get(3), body, headerMap);
        log.info("【getWXCode】微信公众号返回参数:{}", result);
        JSONObject jsonObject = JSONObject.parseObject(result);
        String code = (String) jsonObject.toString();
        return code;
    }
    public String encode(String arg) {
        if (arg == null) {
            arg = "";
        }
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            md5.update(arg.getBytes("UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return toHex(md5.digest());
    }
    // ---------------------------------------------------------------------
    // è¯´æ˜Žï¼šåŽŸç§æœ‰ getWXCode / encode å·²éšå‘送方一起收口到 ruoyi-message æ¨¡å—
    // ï¼ˆXinHuaMpApi),此处已删除,避免三份拷贝各写一套签名逻辑。
    // ---------------------------------------------------------------------
}
smartor/src/main/java/com/smartor/service/impl/SubtaskSmsServiceImpl.java
@@ -1,7 +1,6 @@
package com.smartor.service.impl;
import com.ruoyi.common.core.service.ISubtaskSmsService;
import com.smartor.common.LSHospTokenUtil;
import com.smartor.domain.ServiceSubtask;
import com.smartor.mapper.ServiceSubtaskMapper;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,6 +25,8 @@
    @Override
    public String getLSHospToken(String orgid, String tokenUrl) {
        return LSHospTokenUtil.getToken(orgid, tokenUrl);
        //注:丽水短信已改为通过 LishuiMessageConfigProvider(见 com.smartor.message åŒ…)取 token,
        //本方法仅为兼容 ruoyi-common çš„历史接口保留
        return com.smartor.common.LSHospTokenUtil.getToken(orgid, tokenUrl);
    }
}
smartor/src/main/java/com/smartor/service/impl/XHGatherPatArchiveServiceImpl.java
@@ -12,6 +12,9 @@
import com.ruoyi.common.utils.HttpUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.SmsSenderFactory;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.smartor.domain.*;
@@ -57,17 +60,12 @@
    IServiceAppointRecordService iServiceAppointRecordService;
    @Autowired
    private SysConfigMapper sysConfigMapper;
    @Value("${xhsmsPath}")
    private String xhsmsPath;
    // å¸å·
    @Value("${xhsmsAccount}")
    private String xhsmsAccount;
    //  æŽ¥å£å¯†ç 
    @Value("${xhsmsPwd}")
    private String xhsmsPwd;
    // è™šæ‹ŸæŽ¥å…¥ç 
    @Value("${xhsmsjrm}")
    private String xhsmsjrm;
    /**
     * æ¶ˆæ¯å‘送方工厂(短信/微信公众号),替代原来直接注入 xhsms* é…ç½® + HttpUtils çš„写法
     */
    @Autowired
    private SmsSenderFactory smsSenderFactory;
    /**
@@ -135,28 +133,11 @@
                // ç¬¬äºŒæ­¥ï¼šå¤„理停诊记录信息
                for (ServiceAppointRecord serviceAppointRecord : appointRecordFromHIS) {
                    String replace = smsContemt.replace("${patFileName}", StringUtils.defaultString(serviceAppointRecord.getPatFileName())).replace("${registTimeRange}", StringUtils.defaultString(serviceAppointRecord.getRegistTimeRange())).replace("${registDeptName}", StringUtils.defaultString(serviceAppointRecord.getRegistDeptName())).replace("${treatDoctName}", StringUtils.defaultString(serviceAppointRecord.getTreatDoctName()));
                    Map<String, String> map2 = new HashMap<String, String>();
                    map2.put("action", "send");
                    map2.put("account", xhsmsAccount);
                    map2.put("password", xhsmsPwd);
                    map2.put("mobile", serviceAppointRecord.getPatTel());
                    map2.put("content", replace);
                    map2.put("extno", xhsmsjrm);
                    map2.put("rt", "json");
                    StringBuilder encodedParams = new StringBuilder();
                    for (Map.Entry<String, String> entry : map2.entrySet()) {
                        if (encodedParams.length() > 0) {
                            encodedParams.append("&");
                        }
                        // å¯¹é”®å’Œå€¼è¿›è¡ŒURL编码
                        String encodedKey = URLEncoder.encode(entry.getKey(), "UTF-8");
                        String encodedValue = URLEncoder.encode(entry.getValue(), "UTF-8");
                        encodedParams.append(encodedKey).append("=").append(encodedValue);
                    }
                    Boolean isSuccess = HttpUtils.sendPost(xhsmsPath, encodedParams.toString());
                    if (isSuccess) {
                        log.info("---处理停诊记录信息发送成功,入参为:{}", encodedParams.toString());
                    //短信发送已收口到 ruoyi-message æ¨¡å—(新华通道走表单模式,与原实现一致)
                    SendResult sendResult = smsSenderFactory.send("xh",
                            SmsSendRequest.of(serviceAppointRecord.getPatTel(), replace));
                    if (sendResult.isOk()) {
                        log.info("---处理停诊记录信息发送成功,入参为:{}", sendResult.getRawResponse());
                        serviceAppointRecord.setSendTime(new Date());
                        serviceAppointRecord.setSenderName("系统管理员");
                        iServiceAppointRecordService.insertServiceAppointRecord(serviceAppointRecord);