|
|
@@ -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
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|