From a62cffd2d300bff5885e983c4fc68d555c1e4157 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期四, 09 七月 2026 15:16:26 +0800
Subject: [PATCH] 微信订阅消息推送

---
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java     |    4 
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WxSubscribeMessageServiceImpl.java |  188 +++++++++++++++++++++++++++++++++++++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderEvaluateServiceImpl.java     |   26 +++++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/WxSubscribeMessageService.java          |   19 +++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WxSubscribeTemplate.java           |   25 +++++
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderEvaluateController.java        |   37 +++++++
 6 files changed, 299 insertions(+), 0 deletions(-)

diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderEvaluateController.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderEvaluateController.java
index e33f0e5..27c1c87 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderEvaluateController.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderEvaluateController.java
@@ -2,6 +2,8 @@
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ruoyi.chargingPile.api.feignClient.SiteClient;
+import com.ruoyi.chargingPile.api.model.Site;
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import com.ruoyi.common.core.web.page.PageInfo;
 import com.ruoyi.common.log.annotation.Log;
@@ -10,6 +12,7 @@
 import com.ruoyi.common.security.annotation.Logical;
 import com.ruoyi.common.security.annotation.RequiresPermissions;
 import com.ruoyi.common.security.service.TokenService;
+import com.ruoyi.order.api.model.TChargingOrder;
 import com.ruoyi.order.api.model.TOrderEvaluate;
 import com.ruoyi.order.api.model.TOrderEvaluateTag;
 import com.ruoyi.order.api.query.TOrderEvaluateQuery;
@@ -18,16 +21,21 @@
 import com.ruoyi.order.dto.GetOrderEvaluatePageList;
 import com.ruoyi.order.dto.GetOrderEvaluatePageListDTO;
 import com.ruoyi.order.dto.OrderEvaluateVo;
+import com.ruoyi.order.service.TChargingOrderService;
 import com.ruoyi.order.service.TOrderEvaluateService;
 import com.ruoyi.order.service.TOrderEvaluateTagService;
+import com.ruoyi.order.service.WxSubscribeMessageService;
 import com.ruoyi.order.vo.ReplyEvaluationVO;
 import com.ruoyi.other.api.vo.TEvaluationTagVO;
 import io.swagger.annotations.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -43,6 +51,8 @@
 @RequestMapping("/t-order-evaluate")
 public class TOrderEvaluateController {
 
+    private static final Logger log = LoggerFactory.getLogger(TOrderEvaluateController.class);
+
     @Autowired
     private TOrderEvaluateService orderEvaluateService;
     
@@ -51,6 +61,15 @@
     
     @Resource
     private TokenService tokenService;
+    
+    @Resource
+    private TChargingOrderService chargingOrderService;
+    
+    @Resource
+    private SiteClient siteClient;
+    
+    @Resource
+    private WxSubscribeMessageService wxSubscribeMessageService;
     
     
     
@@ -109,8 +128,26 @@
         orderEvaluate.setEvaluationResponse(reply);
         orderEvaluate.setResponseTime(LocalDateTime.now());
         orderEvaluateService.updateById(orderEvaluate);
+        sendEvaluationReplyMessage(orderEvaluate);
         return AjaxResult.success();
     }
+
+    private void sendEvaluationReplyMessage(TOrderEvaluate orderEvaluate) {
+        try {
+            if (orderEvaluate == null || !Integer.valueOf(1).equals(orderEvaluate.getOrderType())) {
+                return;
+            }
+            TChargingOrder order = chargingOrderService.getById(orderEvaluate.getOrderId());
+            Site site = null;
+            if (order != null && order.getSiteId() != null) {
+                List<Site> sites = siteClient.getSiteByIds(Collections.singletonList(order.getSiteId())).getData();
+                site = sites == null || sites.isEmpty() ? null : sites.get(0);
+            }
+            wxSubscribeMessageService.sendEvaluationReply(orderEvaluate, order, site);
+        } catch (Exception e) {
+            log.warn("评价回复订阅消息发送异常,evaluateId={}", orderEvaluate == null ? null : orderEvaluate.getId(), e);
+        }
+    }
     
     
     
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/WxSubscribeMessageService.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/WxSubscribeMessageService.java
new file mode 100644
index 0000000..b85bba1
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/WxSubscribeMessageService.java
@@ -0,0 +1,19 @@
+package com.ruoyi.order.service;
+
+import com.ruoyi.chargingPile.api.model.Site;
+import com.ruoyi.chargingPile.api.model.TChargingGun;
+import com.ruoyi.order.api.model.TChargingOrder;
+import com.ruoyi.order.api.model.TOrderEvaluate;
+
+import java.math.BigDecimal;
+
+public interface WxSubscribeMessageService {
+
+    void sendChargeStart(TChargingOrder order, Site site, TChargingGun chargingGun);
+
+    void sendChargeEnd(TChargingOrder order, Site site, TChargingGun chargingGun);
+
+    void sendEvaluationReply(TOrderEvaluate orderEvaluate, TChargingOrder order, Site site);
+
+    void sendPriceDrop(Long appUserId, Site site, BigDecimal price);
+}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
index ee68dae..6500d39 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -169,6 +169,8 @@
 	
 	@Resource
 	private SendMessageClient sendMessageClient;
+	@Resource
+	private WxSubscribeMessageService wxSubscribeMessageService;
 	
 	@Resource
 	private TOrderEvaluateService orderEvaluateService;
@@ -1256,6 +1258,7 @@
 				//推送状态给三方平台
 				tcecClient.pushChargingGunStatus(chargingGun.getFullNumber(), 4);
 			}
+			wxSubscribeMessageService.sendChargeStart(order, site, chargingGun);
 		}
 		redisService.setCacheObject("AQJC_" + order.getChargingGunId(), preChargeCheck1, 24L, TimeUnit.HOURS);
 		
@@ -3481,6 +3484,7 @@
                 if (2 == chargingOrder.getOrderSource() && 1 == site.getIsOpenSynchronization()) {
                     tcecClient.pushChargingGunStatus(chargingGun.getFullNumber(), 2);
                 }
+                wxSubscribeMessageService.sendChargeEnd(chargingOrder, site, chargingGun);
 
                 //添加积分
                 if (1 == chargingOrder.getOrderSource()) {
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderEvaluateServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderEvaluateServiceImpl.java
index 3419cb9..cf9f85b 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderEvaluateServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderEvaluateServiceImpl.java
@@ -36,6 +36,7 @@
 import com.ruoyi.order.service.TChargingOrderService;
 import com.ruoyi.order.service.TEvaluationAutoReplyService;
 import com.ruoyi.order.service.TOrderEvaluateService;
+import com.ruoyi.order.service.WxSubscribeMessageService;
 import com.ruoyi.other.api.domain.TEvaluationTag;
 import com.ruoyi.other.api.domain.TIntegralRule;
 import com.ruoyi.other.api.domain.TVip;
@@ -45,6 +46,8 @@
 import com.ruoyi.system.api.feignClient.SysUserClient;
 import com.ruoyi.system.api.feignClient.SysUserRoleClient;
 import com.ruoyi.system.api.model.SysUserRoleVo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
@@ -65,6 +68,8 @@
  */
 @Service
 public class TOrderEvaluateServiceImpl extends ServiceImpl<TOrderEvaluateMapper, TOrderEvaluate> implements TOrderEvaluateService {
+
+    private static final Logger log = LoggerFactory.getLogger(TOrderEvaluateServiceImpl.class);
 
     @Resource
     private TEvaluationTagClient evaluationTagClient;
@@ -112,6 +117,9 @@
     
     @Resource
     private TEvaluationAutoReplyService evaluationAutoReplyService;
+    
+    @Resource
+    private WxSubscribeMessageService wxSubscribeMessageService;
     
     
     
@@ -409,6 +417,24 @@
         orderEvaluate.setEvaluationResponse(rule.getReplyContent());
         orderEvaluate.setResponseTime(LocalDateTime.now());
         this.updateById(orderEvaluate);
+        sendAutoEvaluationReplyMessage(orderEvaluate);
+    }
+
+    private void sendAutoEvaluationReplyMessage(TOrderEvaluate orderEvaluate) {
+        try {
+            if (orderEvaluate == null || !Integer.valueOf(1).equals(orderEvaluate.getOrderType())) {
+                return;
+            }
+            TChargingOrder order = chargingOrderService.getById(orderEvaluate.getOrderId());
+            Site site = null;
+            if (order != null && order.getSiteId() != null) {
+                List<Site> sites = siteClient.getSiteByIds(Collections.singletonList(order.getSiteId())).getData();
+                site = sites == null || sites.isEmpty() ? null : sites.get(0);
+            }
+            wxSubscribeMessageService.sendEvaluationReply(orderEvaluate, order, site);
+        } catch (Exception e) {
+            log.warn("评价自动回复订阅消息发送异常,evaluateId={}", orderEvaluate == null ? null : orderEvaluate.getId(), e);
+        }
     }
     
     
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WxSubscribeMessageServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WxSubscribeMessageServiceImpl.java
new file mode 100644
index 0000000..f59df6a
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WxSubscribeMessageServiceImpl.java
@@ -0,0 +1,188 @@
+package com.ruoyi.order.service.impl;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.account.api.feignClient.AppUserClient;
+import com.ruoyi.account.api.model.TAppUser;
+import com.ruoyi.chargingPile.api.model.Site;
+import com.ruoyi.chargingPile.api.model.TChargingGun;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.order.api.model.TChargingOrder;
+import com.ruoyi.order.api.model.TOrderEvaluate;
+import com.ruoyi.order.service.WxSubscribeMessageService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Service
+public class WxSubscribeMessageServiceImpl implements WxSubscribeMessageService {
+
+    private static final Logger log = LoggerFactory.getLogger(WxSubscribeMessageServiceImpl.class);
+    private static final String SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={accessToken}";
+
+    @Resource
+    private AppUserClient appUserClient;
+    @Resource
+    private RestTemplate restTemplate;
+
+    @Override
+    public void sendChargeStart(TChargingOrder order, Site site, TChargingGun chargingGun) {
+        if (order == null) {
+            return;
+        }
+        Map<String, String> data = new LinkedHashMap<>();
+        data.put("thing16", limit(siteName(site), 20));
+        data.put("thing17", limit(gunCode(chargingGun), 20));
+        data.put("amount10", amount(order.getRechargeAmount()));
+        data.put("thing21", limit(defaultText(order.getPlateNum(), "未填写"), 20));
+        send(WxSubscribeTemplate.CHARGE_START, order.getAppUserId(), page(WxSubscribeTemplate.CHARGE_START, order), data);
+    }
+
+    @Override
+    public void sendChargeEnd(TChargingOrder order, Site site, TChargingGun chargingGun) {
+        if (order == null) {
+            return;
+        }
+        Map<String, String> data = new LinkedHashMap<>();
+        data.put("thing6", limit(siteName(site), 20));
+        data.put("character_string15", limit(gunCode(chargingGun), 32));
+        data.put("amount33", amount(order.getPaymentAmount() == null ? order.getOrderAmount() : order.getPaymentAmount()));
+        data.put("amount18", amount(order.getRefundAmount()));
+        data.put("thing3", limit(endReason(order.getEndMode()), 20));
+        send(WxSubscribeTemplate.CHARGE_END, order.getAppUserId(), page(WxSubscribeTemplate.CHARGE_END, order), data);
+    }
+
+    @Override
+    public void sendEvaluationReply(TOrderEvaluate orderEvaluate, TChargingOrder order, Site site) {
+        if (orderEvaluate == null) {
+            return;
+        }
+        Map<String, String> data = new LinkedHashMap<>();
+        data.put("thing1", limit(siteName(site), 20));
+        data.put("thing3", limit(defaultText(orderEvaluate.getEvaluationResponse(), "平台已回复您的评价"), 20));
+        send(WxSubscribeTemplate.SERVICE_EVALUATION, orderEvaluate.getAppUserId(), WxSubscribeTemplate.SERVICE_EVALUATION.getDefaultPage(), data);
+    }
+
+    @Override
+    public void sendPriceDrop(Long appUserId, Site site, BigDecimal price) {
+        Map<String, String> data = new LinkedHashMap<>();
+        data.put("thing1", limit(siteName(site), 20));
+        data.put("amount4", amount(price));
+        send(WxSubscribeTemplate.PRICE_DROP, appUserId, priceDropPage(site), data);
+    }
+
+    private void send(WxSubscribeTemplate template, Long appUserId, String page, Map<String, String> data) {
+        try {
+            if (appUserId == null) {
+                return;
+            }
+            R<TAppUser> userResult = appUserClient.getUserById(appUserId);
+            TAppUser appUser = userResult == null ? null : userResult.getData();
+            if (appUser == null || !StringUtils.hasText(appUser.getWxOpenid())) {
+                log.info("微信订阅消息未发送,用户openid为空,template={}, appUserId={}", template.name(), appUserId);
+                return;
+            }
+            R<String> tokenResult = appUserClient.getWXToken();
+            String accessToken = tokenResult == null ? null : tokenResult.getData();
+            if (!StringUtils.hasText(accessToken)) {
+                log.warn("微信订阅消息未发送,access_token为空,template={}, appUserId={}", template.name(), appUserId);
+                return;
+            }
+            JSONObject payload = buildPayload(appUser.getWxOpenid(), template, page, data);
+            JSONObject response = restTemplate.postForObject(SEND_URL, payload, JSONObject.class, accessToken);
+            if (response != null && response.getIntValue("errcode") != 0) {
+                log.warn("微信订阅消息发送失败,template={}, appUserId={}, response={}", template.name(), appUserId, response);
+            }
+        } catch (Exception e) {
+            log.warn("微信订阅消息发送异常,template={}, appUserId={}", template.name(), appUserId, e);
+        }
+    }
+
+    static JSONObject buildPayload(String openid, WxSubscribeTemplate template, String page, Map<String, String> data) {
+        JSONObject payload = new JSONObject();
+        payload.put("touser", openid);
+        payload.put("template_id", template.getTemplateId());
+        if (StringUtils.hasText(page)) {
+            payload.put("page", page);
+        }
+        JSONObject dataJson = new JSONObject();
+        for (Map.Entry<String, String> entry : data.entrySet()) {
+            JSONObject value = new JSONObject();
+            value.put("value", entry.getValue());
+            dataJson.put(entry.getKey(), value);
+        }
+        payload.put("data", dataJson);
+        return payload;
+    }
+
+    private static String page(WxSubscribeTemplate template, TChargingOrder order) {
+        if (order == null || order.getChargingGunId() == null) {
+            return template.getDefaultPage();
+        }
+        return template.getDefaultPage() + "?gunId=" + order.getChargingGunId();
+    }
+
+    private static String priceDropPage(Site site) {
+        if (site == null || site.getId() == null) {
+            return WxSubscribeTemplate.PRICE_DROP.getDefaultPage();
+        }
+        return WxSubscribeTemplate.PRICE_DROP.getDefaultPage() + "?id=" + site.getId();
+    }
+
+    private static String siteName(Site site) {
+        return site == null ? "充电站" : defaultText(site.getName(), "充电站");
+    }
+
+    private static String gunCode(TChargingGun chargingGun) {
+        if (chargingGun == null) {
+            return "充电桩";
+        }
+        if (StringUtils.hasText(chargingGun.getFullNumber())) {
+            return chargingGun.getFullNumber();
+        }
+        if (StringUtils.hasText(chargingGun.getCode())) {
+            return chargingGun.getCode();
+        }
+        return defaultText(chargingGun.getName(), "充电桩");
+    }
+
+    private static String amount(BigDecimal value) {
+        return (value == null ? BigDecimal.ZERO : value).setScale(2, RoundingMode.HALF_UP).toPlainString() + "元";
+    }
+
+    private static String endReason(Integer endMode) {
+        if (endMode == null) {
+            return "充电结束";
+        }
+        switch (endMode) {
+            case 0:
+                return "异常终止";
+            case 1:
+                return "主动终止";
+            case 2:
+                return "满电终止";
+            case 3:
+                return "费用不足终止";
+            default:
+                return "充电结束";
+        }
+    }
+
+    private static String defaultText(String value, String defaultValue) {
+        return StringUtils.hasText(value) ? value : defaultValue;
+    }
+
+    private static String limit(String value, int maxLength) {
+        if (value == null || value.length() <= maxLength) {
+            return value;
+        }
+        return value.substring(0, maxLength);
+    }
+}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WxSubscribeTemplate.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WxSubscribeTemplate.java
new file mode 100644
index 0000000..e18f7a7
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/WxSubscribeTemplate.java
@@ -0,0 +1,25 @@
+package com.ruoyi.order.service.impl;
+
+public enum WxSubscribeTemplate {
+
+    SERVICE_EVALUATION("LwdykWyQ2P74AR0qk-pLZR6f0oFn0TC03H1eh6Mg2tY", "pagesMine/evaluateRecord/evaluateRecord"),
+    PRICE_DROP("7nCycRUZxQ2jxAb2UaYazjp2sPZy3qYyeLqVytyhHhY", "pagesHome/pileDetail/index"),
+    CHARGE_END("aetqMnzytun-66wQ40bT-7r97hMKu9pgcnvMkRyx5yA", "pagesMap/startCharge/chargeEnd"),
+    CHARGE_START("Zz42EOeBt_hlCt50T7bRzlLtpGrZvt1idULi7m9PW_8", "pagesMap/startCharge/startCharge");
+
+    private final String templateId;
+    private final String defaultPage;
+
+    WxSubscribeTemplate(String templateId, String defaultPage) {
+        this.templateId = templateId;
+        this.defaultPage = defaultPage;
+    }
+
+    public String getTemplateId() {
+        return templateId;
+    }
+
+    public String getDefaultPage() {
+        return defaultPage;
+    }
+}

--
Gitblit v1.7.1