diff --git a/postdischarge-admin/src/main/resources/application.yml b/postdischarge-admin/src/main/resources/application.yml index 0b21fe40..89a7ce9f 100644 --- a/postdischarge-admin/src/main/resources/application.yml +++ b/postdischarge-admin/src/main/resources/application.yml @@ -217,4 +217,6 @@ wechat-official-account-config: # 微信公众号事件回调令牌 official-account-token: xinyilu # 微信公众号事件回调消息加密密钥 - official-account-encoding-aes-key: Awcn7nvDU4bcfBwAZmiRbB3lFgXAm2RIg45utdb5Zt3 \ No newline at end of file + official-account-encoding-aes-key: Awcn7nvDU4bcfBwAZmiRbB3lFgXAm2RIg45utdb5Zt3 + # 测试模板id + test-template-id: WUCYtSbH-QFRV_fMcfmn86QLsz1zo881QW7fQNTWOjc \ No newline at end of file diff --git a/postdischarge-common/pom.xml b/postdischarge-common/pom.xml index f64df95d..73f446fc 100644 --- a/postdischarge-common/pom.xml +++ b/postdischarge-common/pom.xml @@ -111,6 +111,12 @@ org.projectlombok lombok + + + org.apache.httpcomponents + httpclient + 4.5.13 + \ No newline at end of file diff --git a/postdischarge-common/src/main/java/com/xinelu/common/constant/Constants.java b/postdischarge-common/src/main/java/com/xinelu/common/constant/Constants.java index 0023f012..fef06939 100644 --- a/postdischarge-common/src/main/java/com/xinelu/common/constant/Constants.java +++ b/postdischarge-common/src/main/java/com/xinelu/common/constant/Constants.java @@ -223,4 +223,44 @@ public class Constants { * 获取微信小程序和微信公众号accessToken的url地址 */ public static final String WE_CHAT_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"; + + /** + * 微信公众号模板消息推送发送接口地址 + */ + public static final String OFFICIAL_ACCOUNT_TEMPLATE_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="; + + /** + * 获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口 + */ + public static final int INVALID_CREDENTIAL_ACCESS_TOKEN_ISINVALID_OR_NOT_LATEST = 40001; + + /** + * 不合法的 OpenID ,请开发者确认 OpenID (该用户)是否已关注公众号,或是否是其他公众号的 OpenID + */ + public static final int INVALID_OPENID = 40003; + + /** + * 不合法的 access_token ,请开发者认真比对 access_token 的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口 + */ + public static final int INVALID_ACCESS_TOKEN = 40014; + + /** + * 不合法的 template_id + */ + public static final int INVALID_TEMPLATE_ID = 40037; + + /** + * 用户拒接订阅 + */ + public static final int DENY_SUBSCRIPTION = 43101; + + /** + * 参数无效 + */ + public static final int ARGUMENT_INVALID = 47003; + + /** + * 成功 + */ + public static final int SUCCESS_ERROR_CODE = 0; } diff --git a/postdischarge-common/src/main/java/com/xinelu/common/enums/TemplateEventSendStatusEnum.java b/postdischarge-common/src/main/java/com/xinelu/common/enums/TemplateEventSendStatusEnum.java new file mode 100644 index 00000000..8d45922a --- /dev/null +++ b/postdischarge-common/src/main/java/com/xinelu/common/enums/TemplateEventSendStatusEnum.java @@ -0,0 +1,34 @@ +package com.xinelu.common.enums; + +import lombok.Getter; + +/** + * @Description 微信公众号模板事件消息发送状态枚举 + * @Author 纪寒 + * @Date 2024-03-25 14:38:27 + * @Version 1.0 + */ +@Getter +public enum TemplateEventSendStatusEnum { + /** + * 成功 + */ + SUCCESS("success"), + + /** + * 用户拒绝接收 + */ + REJECT("failed:user block"), + + /** + * 发送失败 + */ + FAILED("failed:system failed"), + ; + + final private String info; + + TemplateEventSendStatusEnum(String info) { + this.info = info; + } +} diff --git a/postdischarge-common/src/main/java/com/xinelu/common/utils/http/HttpUtils.java b/postdischarge-common/src/main/java/com/xinelu/common/utils/http/HttpUtils.java index 9159bb3b..1d5c3109 100644 --- a/postdischarge-common/src/main/java/com/xinelu/common/utils/http/HttpUtils.java +++ b/postdischarge-common/src/main/java/com/xinelu/common/utils/http/HttpUtils.java @@ -2,6 +2,16 @@ package com.xinelu.common.utils.http; import com.xinelu.common.constant.Constants; import com.xinelu.common.utils.StringUtils; +import org.apache.http.Consts; +import org.apache.http.HttpEntity; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.HttpClientUtils; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -207,4 +217,40 @@ public class HttpUtils { return true; } } + + /** + * 发送json格式的POST类型的http请求 + * + * @param url 请求地址 + * @param param json格式的请求参数 + * @return String 返回值信息 + */ + public static String sendPostJson(String url, String param) { + String result = null; + CloseableHttpClient httpClient = null; + CloseableHttpResponse response = null; + try { + httpClient = HttpClients.createDefault(); + // 字符串编码 + StringEntity entity = new StringEntity(param, Consts.UTF_8); + // 设置content-type + entity.setContentType("application/json"); + HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build()); + // 防止被当成攻击添加的 + httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/54.0.2840.87 Safari/537.36"); + // 接收参数设置 + httpPost.setHeader("Accept", "application/json"); + httpPost.setEntity(entity); + response = httpClient.execute(httpPost); + HttpEntity httpEntity = response.getEntity(); + result = EntityUtils.toString(httpEntity); + } catch (Exception e) { + log.warn("post请求发送失败,请求路径:[{}],请求参数:[{}],异常信息:[{}]", url, param, e); + } finally { + HttpClientUtils.closeQuietly(response); + HttpClientUtils.closeQuietly(httpClient); + } + return result; + } } \ No newline at end of file diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/domain/officialaccounttemplateevent/OfficialAccountTemplateEvent.java b/postdischarge-manage/src/main/java/com/xinelu/manage/domain/officialaccounttemplateevent/OfficialAccountTemplateEvent.java new file mode 100644 index 00000000..0b1d7bd6 --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/domain/officialaccounttemplateevent/OfficialAccountTemplateEvent.java @@ -0,0 +1,119 @@ +package com.xinelu.manage.domain.officialaccounttemplateevent; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.xinelu.common.annotation.Excel; +import com.xinelu.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.time.LocalDateTime; + +/** + * 微信公众号模板信息事件推送对象 official_account_template_event + * + * @author xinelu + * @date 2024-03-25 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "微信公众号模板信息事件推送对象", description = "official_account_template_event") +public class OfficialAccountTemplateEvent extends BaseEntity { + private static final long serialVersionUID = -8437491233815143821L; + /** + * 主键id + */ + private Long id; + + /** + * 患者信息表id + */ + @ApiModelProperty(value = "患者信息表id") + @Excel(name = "患者信息表id") + private Long patientId; + + /** + * 患者姓名 + */ + @ApiModelProperty(value = "患者姓名") + @Excel(name = "患者姓名") + private String patientName; + + /** + * 微信用户openid + */ + @ApiModelProperty(value = "微信用户openid") + @Excel(name = "微信用户openid") + private String openid; + + /** + * 微信公众号id + */ + @ApiModelProperty(value = "微信公众号id") + @Excel(name = "微信公众号id") + private String officialAccountAppId; + + /** + * 发送时间,即:事件创建时间,yyyy-MM-dd HH:mm:ss + */ + @ApiModelProperty(value = "发送时间,即:事件创建时间,yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "发送时间,即:事件创建时间,yyyy-MM-dd HH:mm:ss", width = 30, dateFormat = "yyyy-MM-dd") + private LocalDateTime sendTime; + + /** + * 消息类型是事件,固定取值为:event + */ + @ApiModelProperty(value = "消息类型是事件,固定取值为:event") + @Excel(name = "消息类型是事件,固定取值为:event") + private String msgType; + + /** + * 事件为模板消息发送结束,固定取值为:TEMPLATESENDJOBFINISH + */ + @ApiModelProperty(value = "事件为模板消息发送结束,固定取值为:TEMPLATESENDJOBFINISH") + @Excel(name = "事件为模板消息发送结束,固定取值为:TEMPLATESENDJOBFINISH") + private String eventType; + + /** + * 消息id + */ + @ApiModelProperty(value = "消息id") + @Excel(name = "消息id") + private String msgId; + + /** + * 发送状态,成功:success,用户拒绝接收:failed:user block,发送失败(非用户拒绝):failed:system failed + */ + @ApiModelProperty(value = "发送状态,成功:success,用户拒绝接收:failed:user block,发送失败") + @Excel(name = "发送状态,成功:success,用户拒绝接收:failed:user block,发送失败", readConverterExp = "非=用户拒绝") + private String status; + + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("patientId", getPatientId()) + .append("patientName", getPatientName()) + .append("openid", getOpenid()) + .append("officialAccountAppId", getOfficialAccountAppId()) + .append("sendTime", getSendTime()) + .append("msgType", getMsgType()) + .append("eventType", getEventType()) + .append("msgId", getMsgId()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/mapper/officialaccounttemplateevent/OfficialAccountTemplateEventMapper.java b/postdischarge-manage/src/main/java/com/xinelu/manage/mapper/officialaccounttemplateevent/OfficialAccountTemplateEventMapper.java new file mode 100644 index 00000000..ef4d97f4 --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/mapper/officialaccounttemplateevent/OfficialAccountTemplateEventMapper.java @@ -0,0 +1,61 @@ +package com.xinelu.manage.mapper.officialaccounttemplateevent; + +import com.xinelu.manage.domain.officialaccounttemplateevent.OfficialAccountTemplateEvent; + +import java.util.List; + +/** + * 微信公众号模板信息事件推送Mapper接口 + * + * @author xinelu + * @date 2024-03-25 + */ +public interface OfficialAccountTemplateEventMapper { + /** + * 查询微信公众号模板信息事件推送 + * + * @param id 微信公众号模板信息事件推送主键 + * @return 微信公众号模板信息事件推送 + */ + OfficialAccountTemplateEvent selectOfficialAccountTemplateEventById(Long id); + + /** + * 查询微信公众号模板信息事件推送列表 + * + * @param officialAccountTemplateEvent 微信公众号模板信息事件推送 + * @return 微信公众号模板信息事件推送集合 + */ + List selectOfficialAccountTemplateEventList(OfficialAccountTemplateEvent officialAccountTemplateEvent); + + /** + * 新增微信公众号模板信息事件推送 + * + * @param officialAccountTemplateEvent 微信公众号模板信息事件推送 + * @return 结果 + */ + int insertOfficialAccountTemplateEvent(OfficialAccountTemplateEvent officialAccountTemplateEvent); + + /** + * 修改微信公众号模板信息事件推送 + * + * @param officialAccountTemplateEvent 微信公众号模板信息事件推送 + * @return 结果 + */ + int updateOfficialAccountTemplateEvent(OfficialAccountTemplateEvent officialAccountTemplateEvent); + + /** + * 删除微信公众号模板信息事件推送 + * + * @param id 微信公众号模板信息事件推送主键 + * @return 结果 + */ + int deleteOfficialAccountTemplateEventById(Long id); + + /** + * 批量删除微信公众号模板信息事件推送 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + int deleteOfficialAccountTemplateEventByIds(Long[] ids); +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/service/officialaccounttemplateevent/IOfficialAccountTemplateEventService.java b/postdischarge-manage/src/main/java/com/xinelu/manage/service/officialaccounttemplateevent/IOfficialAccountTemplateEventService.java new file mode 100644 index 00000000..27d77114 --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/service/officialaccounttemplateevent/IOfficialAccountTemplateEventService.java @@ -0,0 +1,61 @@ +package com.xinelu.manage.service.officialaccounttemplateevent; + +import com.xinelu.manage.domain.officialaccounttemplateevent.OfficialAccountTemplateEvent; + +import java.util.List; + +/** + * 微信公众号模板信息事件推送Service接口 + * + * @author xinelu + * @date 2024-03-25 + */ +public interface IOfficialAccountTemplateEventService { + /** + * 查询微信公众号模板信息事件推送 + * + * @param id 微信公众号模板信息事件推送主键 + * @return 微信公众号模板信息事件推送 + */ + OfficialAccountTemplateEvent selectOfficialAccountTemplateEventById(Long id); + + /** + * 查询微信公众号模板信息事件推送列表 + * + * @param officialAccountTemplateEvent 微信公众号模板信息事件推送 + * @return 微信公众号模板信息事件推送集合 + */ + List selectOfficialAccountTemplateEventList(OfficialAccountTemplateEvent officialAccountTemplateEvent); + + /** + * 新增微信公众号模板信息事件推送 + * + * @param officialAccountTemplateEvent 微信公众号模板信息事件推送 + * @return 结果 + */ + int insertOfficialAccountTemplateEvent(OfficialAccountTemplateEvent officialAccountTemplateEvent); + + /** + * 修改微信公众号模板信息事件推送 + * + * @param officialAccountTemplateEvent 微信公众号模板信息事件推送 + * @return 结果 + */ + int updateOfficialAccountTemplateEvent(OfficialAccountTemplateEvent officialAccountTemplateEvent); + + /** + * 批量删除微信公众号模板信息事件推送 + * + * @param ids 需要删除的微信公众号模板信息事件推送主键集合 + * @return 结果 + */ + int deleteOfficialAccountTemplateEventByIds(Long[] ids); + + /** + * 删除微信公众号模板信息事件推送信息 + * + * @param id 微信公众号模板信息事件推送主键 + * @return 结果 + */ + int deleteOfficialAccountTemplateEventById(Long id); +} diff --git a/postdischarge-manage/src/main/java/com/xinelu/manage/service/officialaccounttemplateevent/impl/OfficialAccountTemplateEventServiceImpl.java b/postdischarge-manage/src/main/java/com/xinelu/manage/service/officialaccounttemplateevent/impl/OfficialAccountTemplateEventServiceImpl.java new file mode 100644 index 00000000..f9546e1b --- /dev/null +++ b/postdischarge-manage/src/main/java/com/xinelu/manage/service/officialaccounttemplateevent/impl/OfficialAccountTemplateEventServiceImpl.java @@ -0,0 +1,90 @@ +package com.xinelu.manage.service.officialaccounttemplateevent.impl; + +import com.xinelu.manage.domain.officialaccounttemplateevent.OfficialAccountTemplateEvent; +import com.xinelu.manage.mapper.officialaccounttemplateevent.OfficialAccountTemplateEventMapper; +import com.xinelu.manage.service.officialaccounttemplateevent.IOfficialAccountTemplateEventService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 微信公众号模板信息事件推送Service业务层处理 + * + * @author xinelu + * @date 2024-03-25 + */ +@Service +public class OfficialAccountTemplateEventServiceImpl implements IOfficialAccountTemplateEventService { + @Resource + private OfficialAccountTemplateEventMapper officialAccountTemplateEventMapper; + + /** + * 查询微信公众号模板信息事件推送 + * + * @param id 微信公众号模板信息事件推送主键 + * @return 微信公众号模板信息事件推送 + */ + @Override + public OfficialAccountTemplateEvent selectOfficialAccountTemplateEventById(Long id) { + return officialAccountTemplateEventMapper.selectOfficialAccountTemplateEventById(id); + } + + /** + * 查询微信公众号模板信息事件推送列表 + * + * @param officialAccountTemplateEvent 微信公众号模板信息事件推送 + * @return 微信公众号模板信息事件推送 + */ + @Override + public List selectOfficialAccountTemplateEventList(OfficialAccountTemplateEvent officialAccountTemplateEvent) { + return officialAccountTemplateEventMapper.selectOfficialAccountTemplateEventList(officialAccountTemplateEvent); + } + + /** + * 新增微信公众号模板信息事件推送 + * + * @param officialAccountTemplateEvent 微信公众号模板信息事件推送 + * @return 结果 + */ + @Override + public int insertOfficialAccountTemplateEvent(OfficialAccountTemplateEvent officialAccountTemplateEvent) { + officialAccountTemplateEvent.setCreateTime(LocalDateTime.now()); + return officialAccountTemplateEventMapper.insertOfficialAccountTemplateEvent(officialAccountTemplateEvent); + } + + /** + * 修改微信公众号模板信息事件推送 + * + * @param officialAccountTemplateEvent 微信公众号模板信息事件推送 + * @return 结果 + */ + @Override + public int updateOfficialAccountTemplateEvent(OfficialAccountTemplateEvent officialAccountTemplateEvent) { + officialAccountTemplateEvent.setUpdateTime(LocalDateTime.now()); + return officialAccountTemplateEventMapper.updateOfficialAccountTemplateEvent(officialAccountTemplateEvent); + } + + /** + * 批量删除微信公众号模板信息事件推送 + * + * @param ids 需要删除的微信公众号模板信息事件推送主键 + * @return 结果 + */ + @Override + public int deleteOfficialAccountTemplateEventByIds(Long[] ids) { + return officialAccountTemplateEventMapper.deleteOfficialAccountTemplateEventByIds(ids); + } + + /** + * 删除微信公众号模板信息事件推送信息 + * + * @param id 微信公众号模板信息事件推送主键 + * @return 结果 + */ + @Override + public int deleteOfficialAccountTemplateEventById(Long id) { + return officialAccountTemplateEventMapper.deleteOfficialAccountTemplateEventById(id); + } +} diff --git a/postdischarge-manage/src/main/resources/mapper/manage/officialaccounttemplateevent/OfficialAccountTemplateEventMapper.xml b/postdischarge-manage/src/main/resources/mapper/manage/officialaccounttemplateevent/OfficialAccountTemplateEventMapper.xml new file mode 100644 index 00000000..eeade5db --- /dev/null +++ b/postdischarge-manage/src/main/resources/mapper/manage/officialaccounttemplateevent/OfficialAccountTemplateEventMapper.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + select id, patient_id, patient_name, openid, official_account_app_id, send_time, msg_type, event_type, msg_id, status, create_by, create_time, update_by, update_time from official_account_template_event + + + + + + + + insert into official_account_template_event + + patient_id, + + patient_name, + + openid, + + official_account_app_id, + + send_time, + + msg_type, + + event_type, + + msg_id, + + status, + + create_by, + + create_time, + + update_by, + + update_time, + + + + #{patientId}, + + #{patientName}, + + #{openid}, + + #{officialAccountAppId}, + + #{sendTime}, + + #{msgType}, + + #{eventType}, + + #{msgId}, + + #{status}, + + #{createBy}, + + #{createTime}, + + #{updateBy}, + + #{updateTime}, + + + + + + update official_account_template_event + + patient_id = + #{patientId}, + + patient_name = + #{patientName}, + + openid = + #{openid}, + + official_account_app_id = + #{officialAccountAppId}, + + send_time = + #{sendTime}, + + msg_type = + #{msgType}, + + event_type = + #{eventType}, + + msg_id = + #{msgId}, + + status = + #{status}, + + create_by = + #{createBy}, + + create_time = + #{createTime}, + + update_by = + #{updateBy}, + + update_time = + #{updateTime}, + + + where id = #{id} + + + + delete from official_account_template_event where id = #{id} + + + + delete from official_account_template_event where id in + + #{id} + + + \ No newline at end of file diff --git a/postdischarge-mobile/src/main/java/com/xinelu/mobile/controller/MobileTestController.java b/postdischarge-mobile/src/main/java/com/xinelu/mobile/controller/MobileTestController.java index aae18f01..ad840f07 100644 --- a/postdischarge-mobile/src/main/java/com/xinelu/mobile/controller/MobileTestController.java +++ b/postdischarge-mobile/src/main/java/com/xinelu/mobile/controller/MobileTestController.java @@ -37,4 +37,12 @@ public class MobileTestController { public String getOfficialAccountAccessToken() { return weChatOfficialAccountUtils.getWeChatOfficialAccountAccessToken(); } + + /** + * 测试微信公众号模板消息发送 + */ + @GetMapping("/sendOfficialAccountTemplate") + public void sendOfficialAccountTemplateMessage() { + weChatOfficialAccountUtils.sendOfficialAccountTemplateMessage(); + } } diff --git a/postdischarge-mobile/src/main/java/com/xinelu/mobile/controller/wechatofficialaccountcallback/WeChatOfficialAccountCallbackController.java b/postdischarge-mobile/src/main/java/com/xinelu/mobile/controller/wechatofficialaccountcallback/WeChatOfficialAccountCallbackController.java index c48451dc..225dfdf6 100644 --- a/postdischarge-mobile/src/main/java/com/xinelu/mobile/controller/wechatofficialaccountcallback/WeChatOfficialAccountCallbackController.java +++ b/postdischarge-mobile/src/main/java/com/xinelu/mobile/controller/wechatofficialaccountcallback/WeChatOfficialAccountCallbackController.java @@ -4,12 +4,15 @@ import com.xinelu.common.config.WeChatOfficialAccountConfig; import com.xinelu.common.utils.aes.AesException; import com.xinelu.common.utils.aes.WXBizMsgCrypt; import com.xinelu.mobile.dto.wechatappletcallback.MessageSignDTO; +import com.xinelu.mobile.service.wechatofficialaccountcallback.WeChatOfficialAccountCallbackService; +import com.xinelu.mobile.utils.XmlUtil; +import com.xinelu.mobile.vo.wechatofficialaccountcallback.WeChatOfficialAccountEventPushVO; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import java.util.Objects; /** * @Description 院后微信公众号事件回调控制器 @@ -23,6 +26,8 @@ import javax.annotation.Resource; public class WeChatOfficialAccountCallbackController { @Resource private WeChatOfficialAccountConfig weChatOfficialAccountConfig; + @Resource + private WeChatOfficialAccountCallbackService weChatOfficialAccountCallbackService; /** * 院后微信公众号回调验证方法 @@ -33,10 +38,40 @@ public class WeChatOfficialAccountCallbackController { */ @GetMapping public String getWeChatOfficialAccountCallBack(MessageSignDTO messageSignDTO) throws AesException { + //解密微信报文信息并验证微信公众号与服务器之间是否正常通信 WXBizMsgCrypt wxCpt = new WXBizMsgCrypt(weChatOfficialAccountConfig.getOfficialAccountToken(), weChatOfficialAccountConfig.getOfficialAccountEncodingAesKey(), weChatOfficialAccountConfig.getOfficialAccountAppId()); String verifyMessage = wxCpt.verifyUrl(messageSignDTO.getSignature(), messageSignDTO.getTimestamp(), messageSignDTO.getNonce(), messageSignDTO.getEchostr()); log.info("院后微信公众号回调设置验证URL成功,验证信息:verifyMessage = [{}]", verifyMessage); return verifyMessage; } + + /** + * 院后微信公众号模板消息事件回调处理 + * + * @param signature 签名 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @param postData 事件报文内容 + * @throws AesException 异常信息 + */ + @PostMapping + public void handleWeChatOfficialAccountCallBack(@RequestParam("signature") String signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestBody String postData) throws AesException { + //解密微信报文信息并验证微信公众号与服务器之间是否正常通信 + WXBizMsgCrypt wxCpt = new WXBizMsgCrypt(weChatOfficialAccountConfig.getOfficialAccountToken(), weChatOfficialAccountConfig.getOfficialAccountEncodingAesKey(), weChatOfficialAccountConfig.getOfficialAccountAppId()); + String verifyMessage = wxCpt.verifyUrl(signature, timestamp, nonce, postData); + WeChatOfficialAccountEventPushVO eventPush = (WeChatOfficialAccountEventPushVO) XmlUtil.fromXml(verifyMessage, WeChatOfficialAccountEventPushVO.class); + if (Objects.isNull(eventPush)) { + log.info("院后微信公众号模板消息事件xml数据转换失败,请求信息为: [{}]", verifyMessage); + return; + } + if (StringUtils.isBlank(eventPush.getToUserName()) || StringUtils.isBlank(eventPush.getEvent()) || StringUtils.isBlank(eventPush.getMsgType())) { + log.info("院后微信公众号模板消息事件解析事件数据为空!"); + return; + } + weChatOfficialAccountCallbackService.handleOfficialAccountTemplateEvent(eventPush); + } } diff --git a/postdischarge-mobile/src/main/java/com/xinelu/mobile/service/wechatofficialaccountcallback/WeChatOfficialAccountCallbackService.java b/postdischarge-mobile/src/main/java/com/xinelu/mobile/service/wechatofficialaccountcallback/WeChatOfficialAccountCallbackService.java new file mode 100644 index 00000000..27e4c650 --- /dev/null +++ b/postdischarge-mobile/src/main/java/com/xinelu/mobile/service/wechatofficialaccountcallback/WeChatOfficialAccountCallbackService.java @@ -0,0 +1,18 @@ +package com.xinelu.mobile.service.wechatofficialaccountcallback; + +import com.xinelu.mobile.vo.wechatofficialaccountcallback.WeChatOfficialAccountEventPushVO; + +/** + * @Description 院后微信公众号事件回调service + * @Author 纪寒 + * @Date 2024-03-25 14:52:03 + * @Version 1.0 + */ +public interface WeChatOfficialAccountCallbackService { + /** + * 院后微信公众号模板消息事件回调处理 + * + * @param eventPushVO 模板消息事件回调数据 + */ + void handleOfficialAccountTemplateEvent(WeChatOfficialAccountEventPushVO eventPushVO); +} diff --git a/postdischarge-mobile/src/main/java/com/xinelu/mobile/service/wechatofficialaccountcallback/impl/WeChatOfficialAccountCallbackServiceImpl.java b/postdischarge-mobile/src/main/java/com/xinelu/mobile/service/wechatofficialaccountcallback/impl/WeChatOfficialAccountCallbackServiceImpl.java new file mode 100644 index 00000000..0f68d8af --- /dev/null +++ b/postdischarge-mobile/src/main/java/com/xinelu/mobile/service/wechatofficialaccountcallback/impl/WeChatOfficialAccountCallbackServiceImpl.java @@ -0,0 +1,65 @@ +package com.xinelu.mobile.service.wechatofficialaccountcallback.impl; + +import com.xinelu.common.exception.ServiceException; +import com.xinelu.common.utils.DateUtils; +import com.xinelu.manage.domain.officialaccounttemplateevent.OfficialAccountTemplateEvent; +import com.xinelu.manage.domain.patientinfo.PatientInfo; +import com.xinelu.manage.mapper.officialaccounttemplateevent.OfficialAccountTemplateEventMapper; +import com.xinelu.manage.mapper.patientinfo.PatientInfoMapper; +import com.xinelu.mobile.service.wechatofficialaccountcallback.WeChatOfficialAccountCallbackService; +import com.xinelu.mobile.vo.wechatofficialaccountcallback.WeChatOfficialAccountEventPushVO; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Objects; + +/** + * @Description 院后微信公众号事件回调service实现 + * @Author 纪寒 + * @Date 2024-03-25 14:55:53 + * @Version 1.0 + */ +@Service +@Slf4j +public class WeChatOfficialAccountCallbackServiceImpl implements WeChatOfficialAccountCallbackService { + + @Resource + private PatientInfoMapper patientInfoMapper; + @Resource + private OfficialAccountTemplateEventMapper officialAccountTemplateEventMapper; + + /** + * 院后微信公众号模板消息事件回调处理 + * + * @param eventPushVO 模板消息事件回调数据 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void handleOfficialAccountTemplateEvent(WeChatOfficialAccountEventPushVO eventPushVO) { + //查询患者信息 + PatientInfo patientInfo = patientInfoMapper.getPatientInfoByOpenId(eventPushVO.getFromUserName()); + if (Objects.isNull(patientInfo)) { + log.info("当前患者信息不存在,微信用户openid为:{}", eventPushVO.getFromUserName()); + return; + } + OfficialAccountTemplateEvent templateEvent = new OfficialAccountTemplateEvent(); + templateEvent.setPatientId(Objects.isNull(patientInfo.getId()) ? null : patientInfo.getId()); + templateEvent.setPatientName(StringUtils.isBlank(patientInfo.getPatientName()) ? "" : patientInfo.getPatientName()); + templateEvent.setOpenid(eventPushVO.getFromUserName()); + templateEvent.setOfficialAccountAppId(StringUtils.isBlank(eventPushVO.getToUserName()) ? "" : eventPushVO.getToUserName()); + templateEvent.setSendTime(StringUtils.isBlank(eventPushVO.getCreateTime()) ? null : DateUtils.timestampToLocalDateTime(Long.parseLong(eventPushVO.getCreateTime()) * 1000L)); + templateEvent.setMsgType(StringUtils.isBlank(eventPushVO.getMsgType()) ? "" : eventPushVO.getMsgType()); + templateEvent.setEventType(StringUtils.isBlank(eventPushVO.getEvent()) ? "" : eventPushVO.getEvent()); + templateEvent.setMsgId(StringUtils.isBlank(eventPushVO.getMsgId()) ? "" : eventPushVO.getMsgId()); + templateEvent.setStatus(StringUtils.isBlank(eventPushVO.getStatus()) ? "" : eventPushVO.getStatus()); + int insertTemplateEventCount = officialAccountTemplateEventMapper.insertOfficialAccountTemplateEvent(templateEvent); + if (insertTemplateEventCount < 0) { + log.error("院后微信公众号模板事件推送,新增模板事件消息信息失败,信息为:[{}]", templateEvent); + throw new ServiceException("院后微信公众号模板事件推送记录信息失败!"); + } + log.info("院后微信公众号模板事件推送处理成功!"); + } +} diff --git a/postdischarge-mobile/src/main/java/com/xinelu/mobile/utils/WeChatOfficialAccountUtils.java b/postdischarge-mobile/src/main/java/com/xinelu/mobile/utils/WeChatOfficialAccountUtils.java index ccfc4b0a..6c3a3f93 100644 --- a/postdischarge-mobile/src/main/java/com/xinelu/mobile/utils/WeChatOfficialAccountUtils.java +++ b/postdischarge-mobile/src/main/java/com/xinelu/mobile/utils/WeChatOfficialAccountUtils.java @@ -6,11 +6,14 @@ import com.xinelu.common.constant.Constants; import com.xinelu.common.entity.AccessToken; import com.xinelu.common.exception.ServiceException; import com.xinelu.common.utils.http.HttpUtils; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -20,6 +23,7 @@ import java.util.concurrent.TimeUnit; * @Date 2024-03-21 13:42:31 * @Version 1.0 */ +@Slf4j @Component public class WeChatOfficialAccountUtils { @@ -68,4 +72,62 @@ public class WeChatOfficialAccountUtils { } return accessToken; } + + /** + * 微信公众号模板消息发送 + */ + public void sendOfficialAccountTemplateMessage() { + //获取微信公众号的accessToken + String accessToken = this.getWeChatOfficialAccountAccessToken(); + //定义模板内容,公众模板内容 + Map paramsMap = new LinkedHashMap<>(); + paramsMap.put("touser", "oHBuH5T90TghJdYKfBlxE4fhayBc"); + paramsMap.put("template_id", "WUCYtSbH-QFRV_fMcfmn86QLsz1zo881QW7fQNTWOjc"); + //微信小程序跳转内容 + Map miniprogramMap = new LinkedHashMap<>(); + miniprogramMap.put("appid", "wxdc32268eca6b78f9"); + miniprogramMap.put("pagepath", "pages/startup/startup"); + //微信小程序模板data内容 + Map dataMap = new LinkedHashMap<>(); + dataMap.put("phrase7", "泉医陪护服务"); + dataMap.put("character_string3", "000026315412331612100"); + dataMap.put("time6", "2024-03-25 16:21:32"); + dataMap.put("time10", "2024-03-27 10:00:00"); + dataMap.put("thing11", "济南市槐荫区首诺城市之光东座22楼E10"); + paramsMap.put("miniprogram", miniprogramMap); + paramsMap.put("data", dataMap); + //拼接请求地址并发送 + String messageUrl = Constants.OFFICIAL_ACCOUNT_TEMPLATE_SEND_URL + accessToken; + String param = JSON.toJSONString(paramsMap); + String result = HttpUtils.sendPostJson(messageUrl, param); + //返回参数映射 + AccessToken errCode = JSON.parseObject(result, AccessToken.class); + if (Objects.nonNull(errCode) && Objects.nonNull(errCode.getErrcode())) { + switch (errCode.getErrcode()) { + case Constants.SUCCESS_ERROR_CODE: + log.info("发送消息成功!"); + break; + case Constants.INVALID_CREDENTIAL_ACCESS_TOKEN_ISINVALID_OR_NOT_LATEST: + log.error("取 access_token 时 AppSecret 错误,或者 access_token 无效!"); + break; + case Constants.INVALID_OPENID: + log.error("不合法的 OpenId!"); + break; + case Constants.INVALID_ACCESS_TOKEN: + log.error("合法的 access_token!"); + break; + case Constants.INVALID_TEMPLATE_ID: + log.error("不合法的 template_id!"); + break; + case Constants.ARGUMENT_INVALID: + log.error("参数无效!"); + break; + case Constants.DENY_SUBSCRIPTION: + log.error("用户拒接订阅!"); + break; + default: + break; + } + } + } } diff --git a/postdischarge-mobile/src/main/java/com/xinelu/mobile/vo/wechatofficialaccountcallback/WeChatOfficialAccountEventPushVO.java b/postdischarge-mobile/src/main/java/com/xinelu/mobile/vo/wechatofficialaccountcallback/WeChatOfficialAccountEventPushVO.java new file mode 100644 index 00000000..b5930da6 --- /dev/null +++ b/postdischarge-mobile/src/main/java/com/xinelu/mobile/vo/wechatofficialaccountcallback/WeChatOfficialAccountEventPushVO.java @@ -0,0 +1,60 @@ +package com.xinelu.mobile.vo.wechatofficialaccountcallback; + +import lombok.Data; +import org.simpleframework.xml.Element; +import org.simpleframework.xml.Root; + +import java.io.Serializable; + +/** + * @Description 微信公众号模板消息事件推送实体类 + * @Author 纪寒 + * @Date 2024-03-25 13:36:17 + * @Version 1.0 + */ +@Root(name = "xml", strict = false) +@Data +public class WeChatOfficialAccountEventPushVO implements Serializable { + private static final long serialVersionUID = 4329284826265109369L; + /** + * 公众号微信号 + */ + @Element(name = "ToUserName", required = false) + private String toUserName; + + /** + * 接收模板消息的用户的openid + */ + @Element(name = "FromUserName", required = false) + private String fromUserName; + + /** + * 创建时间 + */ + @Element(name = "CreateTime", required = false) + private String createTime; + + /** + * 消息类型是事件 + */ + @Element(name = "MsgType", required = false) + private String msgType; + + /** + * 事件为模板消息发送结束 + */ + @Element(name = "Event", required = false) + private String event; + + /** + * 消息id + */ + @Element(name = "MsgID", required = false) + private String msgId; + + /** + * 发送状态为,成功:success,用户拒绝接收:failed:user block,发送失败(非用户拒绝):failed:system failed + */ + @Element(name = "Status", required = false) + private String status; +}