|
|
@@ -0,0 +1,146 @@
|
|
|
+package work.baiyun.chronicdiseaseapp.service.impl;
|
|
|
+
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import work.baiyun.chronicdiseaseapp.config.WeChatProperties;
|
|
|
+import work.baiyun.chronicdiseaseapp.mapper.UserInfoMapper;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.po.MessagePO;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.po.UserInfo;
|
|
|
+import work.baiyun.chronicdiseaseapp.service.PushService;
|
|
|
+import work.baiyun.chronicdiseaseapp.util.WeChatAccessTokenManager;
|
|
|
+import work.baiyun.chronicdiseaseapp.util.WeChatSubscribeMessageSender;
|
|
|
+
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 推送服务实现类
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class PushServiceImpl implements PushService {
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(PushServiceImpl.class);
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private WeChatProperties weChatProperties;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private UserInfoMapper userInfoMapper;
|
|
|
+
|
|
|
+ private WeChatAccessTokenManager accessTokenManager;
|
|
|
+ private WeChatSubscribeMessageSender subscribeMessageSender;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 懒加载初始化微信工具类
|
|
|
+ */
|
|
|
+ private void initWeChatTools() {
|
|
|
+ if (accessTokenManager == null) {
|
|
|
+ accessTokenManager = new WeChatAccessTokenManager(
|
|
|
+ weChatProperties.getAppid(),
|
|
|
+ weChatProperties.getSecret()
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (subscribeMessageSender == null) {
|
|
|
+ subscribeMessageSender = new WeChatSubscribeMessageSender(accessTokenManager);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> sendSubscribeMessage(Long userId, MessagePO message) {
|
|
|
+ try {
|
|
|
+ initWeChatTools();
|
|
|
+
|
|
|
+ // 获取用户信息
|
|
|
+ UserInfo user = userInfoMapper.selectById(userId);
|
|
|
+ if (user == null || user.getWxOpenid() == null || user.getWxOpenid().trim().isEmpty()) {
|
|
|
+ logger.warn("用户 {} 没有有效的 openid,跳过订阅消息发送", userId);
|
|
|
+ return Map.of("errcode", -1, "errmsg", "用户没有有效的openid");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构造订阅消息负载
|
|
|
+ Map<String, Object> payload = buildSubscribeMessagePayload(user.getWxOpenid(), message);
|
|
|
+
|
|
|
+ // 发送消息
|
|
|
+ Map<String, Object> result = subscribeMessageSender.send(payload);
|
|
|
+
|
|
|
+ if (result.containsKey("errcode") && result.get("errcode").equals(0)) {
|
|
|
+ logger.info("成功发送订阅消息给用户 {},消息ID: {}", userId, message.getId());
|
|
|
+ } else {
|
|
|
+ logger.error("发送订阅消息失败,用户: {},消息ID: {},错误: {}", userId, message.getId(), result);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("发送订阅消息异常,用户: {},消息ID: {}", userId, message.getId(), e);
|
|
|
+ return Map.of("errcode", -1, "errmsg", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean sendPopupNotification(Long userId, MessagePO message) {
|
|
|
+ // 弹窗通知暂时通过前端实现,后续可集成其他推送服务
|
|
|
+ logger.info("弹窗通知暂不支持服务端推送,用户: {},消息ID: {}", userId, message.getId());
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建订阅消息负载
|
|
|
+ */
|
|
|
+ private Map<String, Object> buildSubscribeMessagePayload(String openid, MessagePO message) {
|
|
|
+ Map<String, Object> payload = new HashMap<>();
|
|
|
+
|
|
|
+ // 基础信息
|
|
|
+ payload.put("touser", openid);
|
|
|
+ payload.put("template_id", "ACS7cwcbx0F0Y_YaB4GZr7rWP7BO2-7wQOtYsnUjmFI"); // 模板ID
|
|
|
+ payload.put("page", "pages/public/message-detail"); // 点击跳转页面
|
|
|
+ payload.put("miniprogram_state", "developer"); // 开发环境
|
|
|
+ payload.put("lang", "zh_CN");
|
|
|
+
|
|
|
+ // 消息数据
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+
|
|
|
+ // 根据消息类型设置不同的内容
|
|
|
+ String messageType = getMessageTypeText(message.getType().name());
|
|
|
+ String date = message.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
|
|
+ String content = message.getContent();
|
|
|
+
|
|
|
+ // 模板字段:phrase2(类型)、date3(日期)、thing4(内容)
|
|
|
+ data.put("phrase2", Map.of("value", messageType));
|
|
|
+ data.put("date3", Map.of("value", date));
|
|
|
+ data.put("thing4", Map.of("value", truncateContent(content, 20))); // thing4最多20个字符
|
|
|
+
|
|
|
+ payload.put("data", data);
|
|
|
+
|
|
|
+ return payload;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取消息类型文本
|
|
|
+ */
|
|
|
+ private String getMessageTypeText(String type) {
|
|
|
+ switch (type) {
|
|
|
+ case "DOCTOR":
|
|
|
+ return "医生消息";
|
|
|
+ case "SYSTEM_DAILY":
|
|
|
+ return "日常通知";
|
|
|
+ case "SYSTEM_ANOMALY":
|
|
|
+ return "异常提醒";
|
|
|
+ default:
|
|
|
+ return "消息通知";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 截断内容到指定长度
|
|
|
+ */
|
|
|
+ private String truncateContent(String content, int maxLength) {
|
|
|
+ if (content == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return content.length() > maxLength ? content.substring(0, maxLength) : content;
|
|
|
+ }
|
|
|
+}
|