Browse Source

订阅授权

15376779826 2 days ago
parent
commit
860fce9a02

+ 8 - 17
fs-company/src/main/java/com/fs/company/controller/wechat/WechatCallbackController.java

@@ -4,6 +4,7 @@ import com.baidu.dev2.thirdparty.commons.codec.digest.DigestUtils;
 import com.fs.company.param.WechatEvent;
 import com.fs.company.service.WechatService;
 import com.fs.company.util.XmlUtil;
+import com.fs.wx.utils.WechatTemplateAuthUtil;
 import org.apache.commons.io.IOUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -21,6 +22,9 @@ public class WechatCallbackController {
     @Autowired
     private WechatService wechatService;
 
+    @Autowired
+    private WechatTemplateAuthUtil wechatTemplateAuthUtil;
+
     private static final String TOKEN = "PPKOdAlCoMO";
 
     /** 微信服务器验证 (GET) */
@@ -58,28 +62,15 @@ public class WechatCallbackController {
                 wechatService.bindSellerByScene(sceneKey, openid);
             }
 
-            // 自动回复带订阅授权链接
-            String authUrl = generateAuthUrl();
-            String msgContent = "欢迎关注!点击下方链接开启消息提醒:" + authUrl;
-            return buildTextMessage(openid, fromUser, msgContent);
+//            // 自动回复带订阅授权链接
+//            String authUrl = generateAuthUrl();
+//            String msgContent = "欢迎关注!点击下方链接开启消息提醒:" + authUrl;
+//            return buildTextMessage(openid, fromUser, msgContent);
         }
 
         return "success";
     }
 
-    /** 生成订阅消息授权链接 */
-    private String generateAuthUrl() throws UnsupportedEncodingException {
-        String appid = "wx7670b3b1b1cfcd47"; // 服务号appid
-        String templateId = "CHyzPCokptj4Z9qTpo-UJQzd_OAN4cNa6zpZo9LZ_YY"; // 长期订阅模板ID
-        String redirectUrl = URLEncoder.encode("https://userapp.hbhdt.top//app/common/submsgCallback", StandardCharsets.UTF_8.name());
-
-        return "https://mp.weixin.qq.com/mp/subscribemsg?action=get_confirm"
-                + "&appid=" + appid
-                + "&scene=1000"
-                + "&template_id=" + templateId
-                + "&redirect_url=" + redirectUrl
-                + "#wechat_redirect";
-    }
 
     /** 构建文本消息XML */
     private String buildTextMessage(String toUser, String fromUser, String content) {

+ 79 - 3
fs-service/src/main/java/com/fs/company/util/WechatApi.java

@@ -1,8 +1,12 @@
 package com.fs.company.util;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fs.common.core.redis.RedisCache;
 import org.springframework.beans.factory.annotation.Autowired;
 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.stereotype.Component;
 import org.springframework.web.client.RestTemplate;
 
@@ -58,13 +62,13 @@ public class WechatApi {
     }
 
     public static void sendSubscribeMessage(String accessToken, String openId) {
-        accessToken = "97_Pw_YjpbbakVd9Oy9opTVwiKz7vz-ZtglyFdipnion71g-PgRW9PKEN1IxxS-E3m23dU7GrmlktRN6ht3SUOtcKtU7eMeRv3fSMWmX5PBqMF9X6vhsMcUqjcPrsUCOAhACAXJV";
+        accessToken = "97_AWdZem-aCEBY8bt8vlQV0Hz1OmJnXGCRwWZiiUAuVxZmwYtIJbxP13ADoqZ5p8cFBef2JrF8lo3wMUjvGCUTsXuhA0hrlG-RYhPzuoRFHh8o4Iwxj0Ai4TDWvGcWEQhAFAEMY";
         String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/bizsend?access_token=" + accessToken;
 
         // 消息体
         Map<String, Object> data = new HashMap<>();
         data.put("touser", openId);
-        data.put("template_id", "CHyzPCokptj4Z9qTpo-UJQzd_OAN4cNa6zpZo9LZ_YY");
+        data.put("template_id", "CHyzPCokptj4Z9qTpo-UJSDR4NmFTFTnEs4IOl9Jjjw");
 
         // 模板参数
         Map<String, Object> payloadData = new HashMap<>();
@@ -83,13 +87,85 @@ public class WechatApi {
         System.out.println("微信返回结果:" + result);
     }
 
+    public static void sendTemplateMessage(String accessToken, String openId) {
+        accessToken = "97_AWdZem-aCEBY8bt8vlQV0Hz1OmJnXGCRwWZiiUAuVxZmwYtIJbxP13ADoqZ5p8cFBef2JrF8lo3wMUjvGCUTsXuhA0hrlG-RYhPzuoRFHh8o4Iwxj0Ai4TDWvGcWEQhAFAEMY";
+        String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken;
+
+        // 消息体
+        Map<String, Object> data = new HashMap<>();
+        data.put("touser", openId);
+        data.put("template_id", "phVk0BLSeFZOw9f3vNlWGGvqvzICkOvjZJiI7wgpNyg");
+
+        // 模板参数
+        Map<String, Object> payloadData = new HashMap<>();
+        payloadData.put("time3", createValue("2025年10月30日 14:00"));
+        payloadData.put("thing4", createValue("张三"));
+        payloadData.put("amount8", createValue("10.00"));
+        payloadData.put("character_string9", createValue("123456789"));
+
+        data.put("data", payloadData);
+
+        RestTemplate restTemplate = new RestTemplate();
+
+        Map<String, Object> result = restTemplate.postForObject(url, data, Map.class);
+
+        System.out.println("微信返回结果:" + result);
+    }
+
     private static Map<String, String> createValue(String value) {
         Map<String, String> map = new HashMap<>();
         map.put("value", value);
         return map;
     }
 
+    public Map<String, Object> sendTemplateMessage(String openId, String templateId,
+                                                   Map<String, Object> data, String url,
+                                                   Map<String, Object> miniProgram) {
+        String apiUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + getAccessToken();
+
+        Map<String, Object> message = new HashMap<>();
+        message.put("touser", openId);
+        message.put("template_id", templateId);
+
+        if (url != null && !url.isEmpty()) {
+            message.put("url", url);
+        }
+
+        if (miniProgram != null && !miniProgram.isEmpty()) {
+            message.put("miniprogram", miniProgram);
+        }
+
+        message.put("data", data);
+
+        RestTemplate restTemplate = new RestTemplate();
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            String jsonBody = mapper.writeValueAsString(message);
+            HttpEntity<String> request = new HttpEntity<>(jsonBody, headers);
+
+            Map<String, Object> result = restTemplate.postForObject(apiUrl, request, Map.class);
+            System.out.println("发送模板消息结果: " + result);
+            return result;
+        } catch (Exception e) {
+            throw new RuntimeException("发送模板消息失败", e);
+        }
+    }
+
+
+    /**
+     * 创建模板数据值(带颜色)
+     */
+    public static Map<String, String> createValue(String value, String color) {
+        Map<String, String> map = new HashMap<>();
+        map.put("value", value);
+        map.put("color", color);
+        return map;
+    }
+
     public static void main(String[] args) {
-        sendSubscribeMessage("","ooWg-6u1yGLDT9xlmV4lvwjLZV5o");
+        sendTemplateMessage("","ooWg-6u1yGLDT9xlmV4lvwjLZV5o");
     }
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java

@@ -28,6 +28,7 @@ import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyMoneyLogsMapper;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
+import com.fs.company.util.WechatApi;
 import com.fs.core.config.WxMaConfiguration;
 import com.fs.core.config.WxPayProperties;
 import com.fs.core.utils.OrderCodeUtils;
@@ -189,6 +190,8 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
     private ConfigUtil configUtil;
     @Autowired
     private OpenIMService openIMService;
+    @Autowired
+    private WechatApi wechatApi;
     /**
      * 查询问诊订单
      *

+ 21 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -24,6 +24,7 @@ import com.fs.company.param.FsStoreStatisticsParam;
 import com.fs.company.service.ICompanyDeptService;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
+import com.fs.company.util.WechatApi;
 import com.fs.company.vo.FsStoreOrderStatisticsVO;
 import com.fs.company.vo.FsStoreProductStatisticsVO;
 import com.fs.config.cloud.CloudHostProper;
@@ -290,6 +291,9 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
     @Autowired
     private com.fs.gtPush.service.uniPush2Service uniPush2Service;
 
+    @Autowired
+    private WechatApi wechatApi;
+
     //ERP 类型到服务的映射
     private Map<Integer, IErpOrderService> erpServiceMap;
     @PostConstruct
@@ -1651,6 +1655,23 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
             }
             storeOrder.setPayTime(new Date());
             fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
+
+            try {
+                Long companyUserId = order.getCompanyUserId();
+                if (companyUserId != null) {
+                    // 从company_user表查出openid
+                    CompanyUser companyUser = companyUserService.selectCompanyUserById(companyUserId);
+                    if (StringUtils.isNotEmpty(companyUser.getMpOpenId())) {
+                        WechatApi.sendTemplateMessage(wechatApi.getAccessToken(), companyUser.getMpOpenId());
+                        log.info("支付成功模板消息已发送给用户: {}", companyUserId);
+                    } else {
+                        log.warn("未找到 companyUserId={} 的 openId,跳过模板消息发送", companyUserId);
+                    }
+                }
+            } catch (Exception msgEx) {
+                log.error("支付成功后发送模板消息失败,订单号:{},原因:{}", order.getOrderCode(), msgEx.getMessage());
+            }
+
             try {
                 //更新用户下单次数(获取阈值,当订单总价大于等于阈值,则下单次数+1)
                 BigDecimal minThreshold = toBigDecimal(config.get("minimumThreshold"), BigDecimal.ZERO);

+ 171 - 0
fs-service/src/main/java/com/fs/wx/utils/WechatTemplateAuthUtil.java

@@ -0,0 +1,171 @@
+package com.fs.wx.utils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信模板消息授权工具类
+ */
+@Component
+public class WechatTemplateAuthUtil {
+
+    private String appId = "wx7670b3b1b1cfcd47";
+
+    /**
+     * 生成一次性订阅消息授权链接
+     */
+    public String generateOnceSubscribeAuthUrl(String templateId, String redirectUrl,
+                                              String scene, String reserved) throws UnsupportedEncodingException {
+        String encodedRedirectUrl = URLEncoder.encode(redirectUrl, StandardCharsets.UTF_8.name());
+        String encodedReserved = reserved != null ? URLEncoder.encode(reserved, StandardCharsets.UTF_8.name()) : "";
+
+        return "https://mp.weixin.qq.com/mp/subscribemsg?action=get_confirm" +
+                "&appid=" + appId +
+                "&scene=1000" +
+                "&template_id=" + templateId +
+                "&redirect_url=" + encodedRedirectUrl +
+                (reserved != null ? "&reserved=" + encodedReserved : "") +
+                "#wechat_redirect";
+    }
+
+    /**
+     * 生成长期订阅消息授权链接
+     */
+    public String generateLongTermSubscribeAuthUrl(String templateId, String redirectUrl, String scene)
+            throws UnsupportedEncodingException {
+        String encodedRedirectUrl = URLEncoder.encode(redirectUrl, StandardCharsets.UTF_8.name());
+
+        return "https://mp.weixin.qq.com/mp/subscribemsg?action=get_confirm" +
+                "&appid=" + appId +
+                "&scene=" + (scene != null ? scene : "1000") +
+                "&template_id=" + templateId +
+                "&redirect_url=" + encodedRedirectUrl +
+                "#wechat_redirect";
+    }
+
+    /**
+     * 生成带参数的授权链接(用于特定业务场景)
+     */
+    public String generateBusinessAuthUrl(String templateId, String redirectUrl,
+                                         String scene, Map<String, String> businessParams)
+            throws UnsupportedEncodingException {
+
+        // 将业务参数编码到reserved中
+        String reserved = encodeBusinessParams(businessParams);
+        return generateOnceSubscribeAuthUrl(templateId, redirectUrl, scene, reserved);
+    }
+
+    /**
+     * 编码业务参数到reserved字段
+     */
+    private String encodeBusinessParams(Map<String, String> params) {
+        if (params == null || params.isEmpty()) {
+            return null;
+        }
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            String jsonParams = mapper.writeValueAsString(params);
+            return URLEncoder.encode(jsonParams, StandardCharsets.UTF_8.name());
+        } catch (Exception e) {
+            throw new RuntimeException("编码业务参数失败", e);
+        }
+    }
+
+    /**
+     * 解码reserved字段中的业务参数
+     */
+    public Map<String, String> decodeBusinessParams(String reserved) {
+        if (reserved == null || reserved.isEmpty()) {
+            return new HashMap<>();
+        }
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            String decoded = java.net.URLDecoder.decode(reserved, StandardCharsets.UTF_8.name());
+            return mapper.readValue(decoded, Map.class);
+        } catch (Exception e) {
+            // 如果解析失败,返回原始值
+            Map<String, String> result = new HashMap<>();
+            result.put("reserved", reserved);
+            return result;
+        }
+    }
+
+    /**
+     * 验证授权回调参数
+     */
+    public boolean validateAuthCallback(String action, String openid, String templateId) {
+        return "confirm".equals(action) &&
+               openid != null && !openid.isEmpty() &&
+               templateId != null && !templateId.isEmpty();
+    }
+
+    /**
+     * 获取预定义的模板配置
+     */
+    public Map<String, String> getTemplateConfig(String templateType) {
+        Map<String, String> configs = new HashMap<>();
+
+        switch (templateType) {
+            case "consultation":
+                // 咨询通知模板
+                configs.put("templateId", "I8RwP2oSI_UuY3BQRaXGb_x9efowNukNU0viLx25DQk");
+                configs.put("scene", "1001");
+                configs.put("redirectUrl", "https://userapp.hbhdt.top/app/common/submsgCallback");
+                break;
+
+            case "order":
+                // 订单通知模板
+                configs.put("templateId", "CHyzPCokptj4Z9qTpo-UJSDR4NmFTFTnEs4IOl9Jjjw");
+                configs.put("scene", "1002");
+                configs.put("redirectUrl", "https://userapp.hbhdt.top/app/common/submsgCallback");
+                break;
+
+            case "system":
+                // 系统通知模板
+                configs.put("templateId", "ANOTHER_TEMPLATE_ID");
+                configs.put("scene", "1003");
+                configs.put("redirectUrl", "https://userapp.hbhdt.top/app/common/submsgCallback");
+                break;
+
+            default:
+                throw new IllegalArgumentException("未知的模板类型: " + templateType);
+        }
+
+        return configs;
+    }
+
+    /**
+     * 生成特定业务场景的授权链接
+     */
+    public String generateBusinessAuthUrl(String templateType, String businessId, String userId)
+            throws UnsupportedEncodingException {
+
+        Map<String, String> templateConfig = getTemplateConfig(templateType);
+
+        // 构建业务参数
+        Map<String, String> businessParams = new HashMap<>();
+        businessParams.put("businessId", businessId);
+        businessParams.put("userId", userId);
+        businessParams.put("templateType", templateType);
+        businessParams.put("timestamp", String.valueOf(System.currentTimeMillis()));
+
+        return generateBusinessAuthUrl(
+            templateConfig.get("templateId"),
+            templateConfig.get("redirectUrl"),
+            templateConfig.get("scene"),
+            businessParams
+        );
+    }
+}