فهرست منبع

fix(app):
1.支付相关配置
2.bug修复
feat(pay): 实现微信支付回调解密功能

- 新增 AesUtil 工具类,用于微信支付回调数据解密- 修改 wechatPayNotify 方法,增加数据解密逻辑
- 更新 AppOrderInfoDTO,添加 gameAddress 字段
- 调整 AppSiteServiceImpl 中的 editSite 方法
- 修改 OrderController 中的 wechatPayNotify 接口

wzq 1 هفته پیش
والد
کامیت
7b09570978

+ 1 - 1
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/impl/OrderServiceImpl.java

@@ -1175,7 +1175,7 @@ public class OrderServiceImpl implements IOrderService {
             //支付者信息
             JSONObject payer = new JSONObject();
             //用户在小程序侧的openid
-            payer.put("sub_openid", "oObyf7Z4-Y9P5w6_8YIKEiKSAD6I");
+            payer.put("sub_openid", sysUser.getOpenid());
             params.put("payer", payer);
 
             //拉起支付-返回JSAPI参数

+ 25 - 1
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/config/WechatPayV3Utils.java

@@ -3,6 +3,7 @@ package org.jeecg.modules.pay.config;
 import com.alibaba.fastjson2.JSONObject;
 import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
 import com.wechat.pay.contrib.apache.httpclient.auth.*;
+import com.wechat.pay.contrib.apache.httpclient.auth.Signer;
 import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
 import com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders;
 import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
@@ -24,7 +25,8 @@ import javax.servlet.http.HttpServletRequest;
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.nio.charset.StandardCharsets;
-import java.security.PrivateKey;
+import java.security.*;
+import java.util.Base64;
 
 /**
  * @author wangzhiqiang
@@ -221,5 +223,27 @@ public class WechatPayV3Utils {
             return "";
         }
     }
+
+
+    /**
+     * 计算签名
+     *
+     * @param message
+     * @return
+     */
+    public String sign(byte[] message) {
+        try {
+            if (merchantPrivateKey == null) {
+                setMerchantPrivateKey();
+            }
+            Signature sign = Signature.getInstance("SHA256withRSA");
+            sign.initSign(merchantPrivateKey);
+            sign.update(message);
+            return Base64.getEncoder().encodeToString(sign.sign());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
 }
 

+ 6 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/config/WechatUrlConstants.java

@@ -22,4 +22,10 @@ public class WechatUrlConstants {
     //微信支付v3 退款通知接口地址
     public final static String PAY_V3_REFUND_NOTIFY = "https://1caa0b6f.r28.cpolar.top/jeecg-boot/app/order/refundOrderNotify";
 
+    //微信支付v3 请求分账
+    public final static String PAY_V3_PROFIT_SHARING = "https://api.mch.weixin.qq.com/v3/profitsharing/orders";
+
+    //微信支付v3 查询分账结果
+    public final static String PAY_V3_QUERY_PROFIT_SHARING = "https://api.mch.weixin.qq.com/v3/profitsharing/orders/";
+
 }

+ 32 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/routing/ReceiversTypeEnum.java

@@ -0,0 +1,32 @@
+package org.jeecg.modules.pay.routing;
+
+import lombok.Getter;
+
+/**
+ * 【分账接收方类型】
+ */
+public enum ReceiversTypeEnum {
+    MERCHANT_ID("MERCHANT_ID",0, "商户ID"),
+    PERSONAL_OPENID("PERSONAL_OPENID",0, "个人openid(由父商户APPID转换得到)"),
+    PERSONAL_SUB_OPENID("PERSONAL_SUB_OPENID",0, "个人sub_openid(由子商户APPID转换得到)"),
+    ;
+
+    // 成员变量
+    @Getter
+    private final String type;
+    private final Integer value;
+    private final String desc;
+
+    // 构造方法 ,赋值给成员变量
+    ReceiversTypeEnum(String type, Integer value, String desc) {
+        this.type = type;
+        this.value = value;
+        this.desc = desc;
+    }
+
+    //覆盖方法  :只能使用toString方法来输出枚举变量值
+    @Override
+    public String toString() {
+        return this.type + "_" + this.value+ "_" + this.desc;
+    }
+}

+ 630 - 382
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/serverPay/WXPayUtility.java

@@ -1,8 +1,14 @@
 package org.jeecg.modules.pay.serverPay;
 
-import com.google.gson.*;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
 import com.google.gson.annotations.Expose;
-import com.wechat.pay.java.core.util.GsonUtil;
+import com.google.gson.annotations.SerializedName;
 import okhttp3.Headers;
 import okhttp3.Response;
 import okio.BufferedSource;
@@ -11,6 +17,8 @@ import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.io.UnsupportedEncodingException;
@@ -18,7 +26,15 @@ import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.security.*;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
@@ -28,414 +44,646 @@ import java.time.Instant;
 import java.util.Base64;
 import java.util.Map;
 import java.util.Objects;
- 
+
 public class WXPayUtility {
-  private static final Gson gson = new GsonBuilder()
-      .disableHtmlEscaping()
-      .addSerializationExclusionStrategy(new ExclusionStrategy() {
-        @Override
-        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
-          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
-          return expose != null && !expose.serialize();
-        }
- 
-        @Override
-        public boolean shouldSkipClass(Class<?> aClass) {
-          return false;
-        }
-      })
-      .addDeserializationExclusionStrategy(new ExclusionStrategy() {
-        @Override
-        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
-          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
-          return expose != null && !expose.deserialize();
-        }
- 
-        @Override
-        public boolean shouldSkipClass(Class<?> aClass) {
-          return false;
-        }
-      })
-      .create();
-  private static final char[] SYMBOLS =
-      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
-  private static final SecureRandom random = new SecureRandom();
- 
-  /**
-   * 将 Object 转换为 JSON 字符串
-   */
-  public static String toJson(Object object) {
-    return gson.toJson(object);
-  }
- 
-  /**
-   * 将 JSON 字符串解析为特定类型的实例
-   */
-  public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
-    return gson.fromJson(json, classOfT);
-  }
- 
-  /**
-   * 从公私钥文件路径中读取文件内容
-   *
-   * @param keyPath 文件路径
-   * @return 文件内容
-   */
-  private static String readKeyStringFromPath(String keyPath) {
-    try {
-      return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    }
-  }
- 
-  /**
-   * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
-   *
-   * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
-   * @return PrivateKey 对象
-   */
-  public static PrivateKey loadPrivateKeyFromString(String keyString) {
-    try {
-      keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
-          .replace("-----END PRIVATE KEY-----", "")
-          .replaceAll("\\s+", "");
-      return KeyFactory.getInstance("RSA").generatePrivate(
-          new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
-    } catch (NoSuchAlgorithmException e) {
-      throw new UnsupportedOperationException(e);
-    } catch (InvalidKeySpecException e) {
-      throw new IllegalArgumentException(e);
-    }
-  }
- 
-  /**
-   * 从 PKCS#8 格式的私钥文件中加载私钥
-   *
-   * @param keyPath 私钥文件路径
-   * @return PrivateKey 对象
-   */
-  public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
-    return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
-  }
- 
-  /**
-   * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
-   *
-   * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
-   * @return PublicKey 对象
-   */
-  public static PublicKey loadPublicKeyFromString(String keyString) {
-    try {
-      keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
-          .replace("-----END PUBLIC KEY-----", "")
-          .replaceAll("\\s+", "");
-      return KeyFactory.getInstance("RSA").generatePublic(
-          new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
-    } catch (NoSuchAlgorithmException e) {
-      throw new UnsupportedOperationException(e);
-    } catch (InvalidKeySpecException e) {
-      throw new IllegalArgumentException(e);
+    private static final Gson gson = new GsonBuilder()
+            .disableHtmlEscaping()
+            .addSerializationExclusionStrategy(new ExclusionStrategy() {
+                @Override
+                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
+                    return expose != null && !expose.serialize();
+                }
+
+                @Override
+                public boolean shouldSkipClass(Class<?> aClass) {
+                    return false;
+                }
+            })
+            .addDeserializationExclusionStrategy(new ExclusionStrategy() {
+                @Override
+                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
+                    return expose != null && !expose.deserialize();
+                }
+
+                @Override
+                public boolean shouldSkipClass(Class<?> aClass) {
+                    return false;
+                }
+            })
+            .create();
+    private static final char[] SYMBOLS =
+            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+    private static final SecureRandom random = new SecureRandom();
+
+    /**
+     * 将 Object 转换为 JSON 字符串
+     */
+    public static String toJson(Object object) {
+        return gson.toJson(object);
     }
-  }
- 
-  /**
-   * 从 PKCS#8 格式的公钥文件中加载公钥
-   *
-   * @param keyPath 公钥文件路径
-   * @return PublicKey 对象
-   */
-  public static PublicKey loadPublicKeyFromPath(String keyPath) {
-    return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
-  }
- 
-  /**
-   * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
-   */
-  public static String createNonce(int length) {
-    char[] buf = new char[length];
-    for (int i = 0; i < length; ++i) {
-      buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
+
+    /**
+     * 将 JSON 字符串解析为特定类型的实例
+     */
+    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
+        return gson.fromJson(json, classOfT);
     }
-    return new String(buf);
-  }
- 
-  /**
-   * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
-   *
-   * @param publicKey 加密用公钥对象
-   * @param plaintext 待加密明文
-   * @return 加密后密文
-   */
-  public static String encrypt(PublicKey publicKey, String plaintext) {
-    final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
- 
-    try {
-      Cipher cipher = Cipher.getInstance(transformation);
-      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
-      return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
-    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
-      throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
-    } catch (InvalidKeyException e) {
-      throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
-    } catch (BadPaddingException | IllegalBlockSizeException e) {
-      throw new IllegalArgumentException("Plaintext is too long", e);
+
+    /**
+     * 从公私钥文件路径中读取文件内容
+     *
+     * @param keyPath 文件路径
+     * @return 文件内容
+     */
+    private static String readKeyStringFromPath(String keyPath) {
+        try {
+            return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
-  }
- 
-  /**
-   * 使用私钥按照指定算法进行签名
-   *
-   * @param message 待签名串
-   * @param algorithm 签名算法,如 SHA256withRSA
-   * @param privateKey 签名用私钥对象
-   * @return 签名结果
-   */
-  public static String sign(String message, String algorithm, PrivateKey privateKey) {
-    byte[] sign;
-    try {
-      Signature signature = Signature.getInstance(algorithm);
-      signature.initSign(privateKey);
-      signature.update(message.getBytes(StandardCharsets.UTF_8));
-      sign = signature.sign();
-    } catch (NoSuchAlgorithmException e) {
-      throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
-    } catch (InvalidKeyException e) {
-      throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
-    } catch (SignatureException e) {
-      throw new RuntimeException("An error occurred during the sign process.", e);
+
+    /**
+     * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
+     *
+     * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
+     * @return PrivateKey 对象
+     */
+    public static PrivateKey loadPrivateKeyFromString(String keyString) {
+        try {
+            keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
+                    .replace("-----END PRIVATE KEY-----", "")
+                    .replaceAll("\\s+", "");
+            return KeyFactory.getInstance("RSA").generatePrivate(
+                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(e);
+        } catch (InvalidKeySpecException e) {
+            throw new IllegalArgumentException(e);
+        }
     }
-    return Base64.getEncoder().encodeToString(sign);
-  }
- 
-  /**
-   * 使用公钥按照特定算法验证签名
-   *
-   * @param message 待签名串
-   * @param signature 待验证的签名内容
-   * @param algorithm 签名算法,如:SHA256withRSA
-   * @param publicKey 验签用公钥对象
-   * @return 签名验证是否通过
-   */
-  public static boolean verify(String message, String signature, String algorithm,
-                               PublicKey publicKey) {
-    try {
-      Signature sign = Signature.getInstance(algorithm);
-      sign.initVerify(publicKey);
-      sign.update(message.getBytes(StandardCharsets.UTF_8));
-      return sign.verify(Base64.getDecoder().decode(signature));
-    } catch (SignatureException e) {
-      return false;
-    } catch (InvalidKeyException e) {
-      throw new IllegalArgumentException("verify uses an illegal publickey.", e);
-    } catch (NoSuchAlgorithmException e) {
-      throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
+
+    /**
+     * 从 PKCS#8 格式的私钥文件中加载私钥
+     *
+     * @param keyPath 私钥文件路径
+     * @return PrivateKey 对象
+     */
+    public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
+        return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
     }
-  }
- 
-  /**
-   * 根据微信支付APIv3请求签名规则构造 Authorization 签名
-   *
-   * @param mchid 商户号
-   * @param certificateSerialNo 商户API证书序列号
-   * @param privateKey 商户API证书私钥
-   * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
-   * @param uri 请求接口的URL
-   * @param body 请求接口的Body
-   * @return 构造好的微信支付APIv3 Authorization 头
-   */
-  public static String buildAuthorization(String mchid, String certificateSerialNo,
-                                          PrivateKey privateKey,
-                                          String method, String uri, String body) {
-    String nonce = createNonce(32);
-    long timestamp = Instant.now().getEpochSecond();
- 
-    String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
-        body == null ? "" : body);
- 
-    String signature = sign(message, "SHA256withRSA", privateKey);
- 
-    return String.format(
-        "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
-            "timestamp=\"%d\",serial_no=\"%s\"",
-        mchid, nonce, signature, timestamp, certificateSerialNo);
-  }
- 
-  /**
-   * 对参数进行 URL 编码
-   *
-   * @param content 参数内容
-   * @return 编码后的内容
-   */
-  public static String urlEncode(String content) {
-    try {
-      return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
-    } catch (UnsupportedEncodingException e) {
-      throw new RuntimeException(e);
+
+    /**
+     * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
+     *
+     * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
+     * @return PublicKey 对象
+     */
+    public static PublicKey loadPublicKeyFromString(String keyString) {
+        try {
+            keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
+                    .replace("-----END PUBLIC KEY-----", "")
+                    .replaceAll("\\s+", "");
+            return KeyFactory.getInstance("RSA").generatePublic(
+                    new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException(e);
+        } catch (InvalidKeySpecException e) {
+            throw new IllegalArgumentException(e);
+        }
     }
-  }
- 
-  /**
-   * 对参数Map进行 URL 编码,生成 QueryString
-   *
-   * @param params Query参数Map
-   * @return QueryString
-   */
-  public static String urlEncode(Map<String, Object> params) {
-    if (params == null || params.isEmpty()) {
-      return "";
+
+    /**
+     * 从 PKCS#8 格式的公钥文件中加载公钥
+     *
+     * @param keyPath 公钥文件路径
+     * @return PublicKey 对象
+     */
+    public static PublicKey loadPublicKeyFromPath(String keyPath) {
+        return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
     }
- 
-    int index = 0;
-    StringBuilder result = new StringBuilder();
-    for (Map.Entry<String, Object> entry : params.entrySet()) {
-      result.append(entry.getKey())
-          .append("=")
-          .append(urlEncode(entry.getValue().toString()));
-      index++;
-      if (index < params.size()) {
-        result.append("&");
-      }
+
+    /**
+     * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
+     */
+    public static String createNonce(int length) {
+        char[] buf = new char[length];
+        for (int i = 0; i < length; ++i) {
+            buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
+        }
+        return new String(buf);
     }
-    return result.toString();
-  }
- 
-  /**
-   * 从应答中提取 Body
-   *
-   * @param response HTTP 请求应答对象
-   * @return 应答中的Body内容,Body为空时返回空字符串
-   */
-  public static String extractBody(Response response) {
-    if (response.body() == null) {
-      return "";
+
+    /**
+     * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
+     *
+     * @param publicKey 加密用公钥对象
+     * @param plaintext 待加密明文
+     * @return 加密后密文
+     */
+    public static String encrypt(PublicKey publicKey, String plaintext) {
+        final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
+
+        try {
+            Cipher cipher = Cipher.getInstance(transformation);
+            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+            return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
+        } catch (BadPaddingException | IllegalBlockSizeException e) {
+            throw new IllegalArgumentException("Plaintext is too long", e);
+        }
     }
- 
-    try {
-      BufferedSource source = response.body().source();
-      return source.readUtf8();
-    } catch (IOException e) {
-      throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e);
+
+    public static String aesAeadDecrypt(byte[] key, byte[] associatedData, byte[] nonce,
+                                        byte[] ciphertext) {
+        final String transformation = "AES/GCM/NoPadding";
+        final String algorithm = "AES";
+        final int tagLengthBit = 128;
+
+        try {
+            Cipher cipher = Cipher.getInstance(transformation);
+            cipher.init(
+                    Cipher.DECRYPT_MODE,
+                    new SecretKeySpec(key, algorithm),
+                    new GCMParameterSpec(tagLengthBit, nonce));
+            if (associatedData != null) {
+                cipher.updateAAD(associatedData);
+            }
+            return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
+        } catch (InvalidKeyException
+                 | InvalidAlgorithmParameterException
+                 | BadPaddingException
+                 | IllegalBlockSizeException
+                 | NoSuchAlgorithmException
+                 | NoSuchPaddingException e) {
+            throw new IllegalArgumentException(String.format("AesAeadDecrypt with %s Failed",
+                    transformation), e);
+        }
     }
-  }
- 
-  /**
-   * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
-   *
-   * @param wechatpayPublicKeyId 微信支付公钥ID
-   * @param wechatpayPublicKey 微信支付公钥对象
-   * @param headers 微信支付应答 Header 列表
-   * @param body 微信支付应答 Body
-   */
-  public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
-                                      Headers headers,
-                                      String body) {
-    String timestamp = headers.get("Wechatpay-Timestamp");
-    try {
-      Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
-      // 拒绝过期请求
-      if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
-        throw new IllegalArgumentException(
-            String.format("Validate http response,timestamp[%s] of httpResponse is expires, "
-                    + "request-id[%s]",
-                timestamp, headers.get("Request-ID")));
-      }
-    } catch (DateTimeException | NumberFormatException e) {
-      throw new IllegalArgumentException(
-          String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " +
-                  "request-id[%s]", timestamp,
-              headers.get("Request-ID")));
+
+    /**
+     * 使用私钥按照指定算法进行签名
+     *
+     * @param message    待签名串
+     * @param algorithm  签名算法,如 SHA256withRSA
+     * @param privateKey 签名用私钥对象
+     * @return 签名结果
+     */
+    public static String sign(String message, String algorithm, PrivateKey privateKey) {
+        byte[] sign;
+        try {
+            Signature signature = Signature.getInstance(algorithm);
+            signature.initSign(privateKey);
+            signature.update(message.getBytes(StandardCharsets.UTF_8));
+            sign = signature.sign();
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
+        } catch (SignatureException e) {
+            throw new RuntimeException("An error occurred during the sign process.", e);
+        }
+        return Base64.getEncoder().encodeToString(sign);
     }
-    String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
-        body == null ? "" : body);
-    String serialNumber = headers.get("Wechatpay-Serial");
-    if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
-      throw new IllegalArgumentException(
-          String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId,
-              serialNumber));
+
+    /**
+     * 使用公钥按照特定算法验证签名
+     *
+     * @param message   待签名串
+     * @param signature 待验证的签名内容
+     * @param algorithm 签名算法,如:SHA256withRSA
+     * @param publicKey 验签用公钥对象
+     * @return 签名验证是否通过
+     */
+    public static boolean verify(String message, String signature, String algorithm,
+                                 PublicKey publicKey) {
+        try {
+            Signature sign = Signature.getInstance(algorithm);
+            sign.initVerify(publicKey);
+            sign.update(message.getBytes(StandardCharsets.UTF_8));
+            return sign.verify(Base64.getDecoder().decode(signature));
+        } catch (SignatureException e) {
+            return false;
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException("verify uses an illegal publickey.", e);
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
+        }
     }
-    String signature = headers.get("Wechatpay-Signature");
- 
-    boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
-    if (!success) {
-      throw new IllegalArgumentException(
-          String.format("Validate response failed,the WechatPay signature is incorrect.%n"
-                  + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
-              headers.get("Request-ID"), headers, body));
+
+    /**
+     * 根据微信支付APIv3请求签名规则构造 Authorization 签名
+     *
+     * @param mchid               商户号
+     * @param certificateSerialNo 商户API证书序列号
+     * @param privateKey          商户API证书私钥
+     * @param method              请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
+     * @param uri                 请求接口的URL
+     * @param body                请求接口的Body
+     * @return 构造好的微信支付APIv3 Authorization 头
+     */
+    public static String buildAuthorization(String mchid, String certificateSerialNo,
+                                            PrivateKey privateKey,
+                                            String method, String uri, String body) {
+        String nonce = createNonce(32);
+        long timestamp = Instant.now().getEpochSecond();
+
+        String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
+                body == null ? "" : body);
+
+        String signature = sign(message, "SHA256withRSA", privateKey);
+
+        return String.format(
+                "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
+                        "timestamp=\"%d\",serial_no=\"%s\"",
+                mchid, nonce, signature, timestamp, certificateSerialNo);
     }
-  }
- 
-  /**
-   * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
-   */
-  public static class ApiException extends RuntimeException {
-    private static final long serialVersionUID = 2261086748874802175L;
- 
-    private final int statusCode;
-    private final String body;
-    private final Headers headers;
-    private final String errorCode;
-    private final String errorMessage;
- 
-    public ApiException(int statusCode, String body, Headers headers) {
-      super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers));
-      this.statusCode = statusCode;
-      this.body = body;
-      this.headers = headers;
- 
-      if (body != null && !body.isEmpty()) {
-        JsonElement code;
-        JsonElement message;
- 
+
+    /**
+     * 对参数进行 URL 编码
+     *
+     * @param content 参数内容
+     * @return 编码后的内容
+     */
+    public static String urlEncode(String content) {
         try {
-          JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class);
-          code = jsonObject.get("code");
-          message = jsonObject.get("message");
-        } catch (JsonSyntaxException ignored) {
-          code = null;
-          message = null;
-        }
-        this.errorCode = code == null ? null : code.getAsString();
-        this.errorMessage = message == null ? null : message.getAsString();
-      } else {
-        this.errorCode = null;
-        this.errorMessage = null;
-      }
+            return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 对参数Map进行 URL 编码,生成 QueryString
+     *
+     * @param params Query参数Map
+     * @return QueryString
+     */
+    public static String urlEncode(Map<String, Object> params) {
+        if (params == null || params.isEmpty()) {
+            return "";
+        }
+
+        int index = 0;
+        StringBuilder result = new StringBuilder();
+        for (Map.Entry<String, Object> entry : params.entrySet()) {
+            if (entry.getValue() == null) {
+                continue;
+            }
+            result.append(entry.getKey())
+                    .append("=")
+                    .append(urlEncode(entry.getValue().toString()));
+            index++;
+            if (index < params.size()) {
+                result.append("&");
+            }
+        }
+        return result.toString();
     }
- 
+
     /**
-     * 获取 HTTP 应答状态码
+     * 从应答中提取 Body
+     *
+     * @param response HTTP 请求应答对象
+     * @return 应答中的Body内容,Body为空时返回空字符串
      */
-    public int getStatusCode() {
-      return statusCode;
+    public static String extractBody(Response response) {
+        if (response.body() == null) {
+            return "";
+        }
+
+        try {
+            BufferedSource source = response.body().source();
+            return source.readUtf8();
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("An error occurred during reading response body. " +
+                    "Status: %d", response.code()), e);
+        }
     }
- 
+
     /**
-     * 获取 HTTP 应答包体内容
+     * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
+     *
+     * @param wechatpayPublicKeyId 微信支付公钥ID
+     * @param wechatpayPublicKey   微信支付公钥对象
+     * @param headers              微信支付应答 Header 列表
+     * @param body                 微信支付应答 Body
      */
-    public String getBody() {
-      return body;
+    public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
+                                        Headers headers,
+                                        String body) {
+        String timestamp = headers.get("Wechatpay-Timestamp");
+        String requestId = headers.get("Request-ID");
+        try {
+            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
+            // 拒绝过期请求
+            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
+                throw new IllegalArgumentException(
+                        String.format("Validate response failed, timestamp[%s] is expired, request-id[%s]",
+                                timestamp, requestId));
+            }
+        } catch (DateTimeException | NumberFormatException e) {
+            throw new IllegalArgumentException(
+                    String.format("Validate response failed, timestamp[%s] is invalid, request-id[%s]",
+                            timestamp, requestId));
+        }
+        String serialNumber = headers.get("Wechatpay-Serial");
+        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
+            throw new IllegalArgumentException(
+                    String.format("Validate response failed, Invalid Wechatpay-Serial, Local: %s, Remote: " +
+                            "%s", wechatpayPublicKeyId, serialNumber));
+        }
+
+        String signature = headers.get("Wechatpay-Signature");
+        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
+                body == null ? "" : body);
+
+        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
+        if (!success) {
+            throw new IllegalArgumentException(
+                    String.format("Validate response failed,the WechatPay signature is incorrect.%n"
+                                    + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
+                            headers.get("Request-ID"), headers, body));
+        }
     }
- 
+
     /**
-     * 获取 HTTP 应答 Header
+     * 根据微信支付APIv3通知验签规则对通知签名进行验证,验证不通过时抛出异常
+     * @param wechatpayPublicKeyId 微信支付公钥ID
+     * @param wechatpayPublicKey 微信支付公钥对象
+     * @param headers 微信支付通知 Header 列表
+     * @param body 微信支付通知 Body
      */
-    public Headers getHeaders() {
-      return headers;
+    public static void validateNotification(String wechatpayPublicKeyId,
+                                            PublicKey wechatpayPublicKey, Headers headers,
+                                            String body) {
+        String timestamp = headers.get("Wechatpay-Timestamp");
+        try {
+            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
+            // 拒绝过期请求
+            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
+                throw new IllegalArgumentException(
+                        String.format("Validate notification failed, timestamp[%s] is expired", timestamp));
+            }
+        } catch (DateTimeException | NumberFormatException e) {
+            throw new IllegalArgumentException(
+                    String.format("Validate notification failed, timestamp[%s] is invalid", timestamp));
+        }
+        String serialNumber = headers.get("Wechatpay-Serial");
+        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
+            throw new IllegalArgumentException(
+                    String.format("Validate notification failed, Invalid Wechatpay-Serial, Local: %s, " +
+                                    "Remote: %s",
+                            wechatpayPublicKeyId,
+                            serialNumber));
+        }
+
+        String signature = headers.get("Wechatpay-Signature");
+        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
+                body == null ? "" : body);
+
+        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
+        if (!success) {
+            throw new IllegalArgumentException(
+                    String.format("Validate notification failed, WechatPay signature is incorrect.\n"
+                                    + "responseHeader[%s]\tresponseBody[%.1024s]",
+                            headers, body));
+        }
     }
- 
+
     /**
-     * 获取 错误码 (错误应答中的 code 字段)
+     * 对微信支付通知进行签名验证、解析,同时将业务数据解密。验签名失败、解析失败、解密失败时抛出异常
+     * @param apiv3Key 商户的 APIv3 Key
+     * @param wechatpayPublicKeyId 微信支付公钥ID
+     * @param wechatpayPublicKey   微信支付公钥对象
+     * @param headers              微信支付应答 Header 列表
+     * @param body                 微信支付应答 Body
+     * @return 解析后的通知内容,解密后的业务数据可以使用 Notification.getPlaintext() 访问
      */
-    public String getErrorCode() {
-      return errorCode;
+    public static Notification parseNotification(String apiv3Key, String wechatpayPublicKeyId,
+                                                 PublicKey wechatpayPublicKey, Headers headers,
+                                                 String body) {
+        validateNotification(wechatpayPublicKeyId, wechatpayPublicKey, headers, body);
+        Notification notification = gson.fromJson(body, Notification.class);
+        notification.decrypt(apiv3Key);
+        return notification;
     }
- 
+
     /**
-     * 获取 错误消息 (错误应答中的 message 字段)
+     * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
      */
-    public String getErrorMessage() {
-      return errorMessage;
+    public static class ApiException extends RuntimeException {
+        private static final long serialVersionUID = 2261086748874802175L;
+
+        private final int statusCode;
+        private final String body;
+        private final Headers headers;
+        private final String errorCode;
+        private final String errorMessage;
+
+        public ApiException(int statusCode, String body, Headers headers) {
+            super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode,
+                    body, headers));
+            this.statusCode = statusCode;
+            this.body = body;
+            this.headers = headers;
+
+            if (body != null && !body.isEmpty()) {
+                JsonElement code;
+                JsonElement message;
+
+                try {
+                    JsonObject jsonObject = gson.fromJson(body, JsonObject.class);
+                    code = jsonObject.get("code");
+                    message = jsonObject.get("message");
+                } catch (JsonSyntaxException ignored) {
+                    code = null;
+                    message = null;
+                }
+                this.errorCode = code == null ? null : code.getAsString();
+                this.errorMessage = message == null ? null : message.getAsString();
+            } else {
+                this.errorCode = null;
+                this.errorMessage = null;
+            }
+        }
+
+        /**
+         * 获取 HTTP 应答状态码
+         */
+        public int getStatusCode() {
+            return statusCode;
+        }
+
+        /**
+         * 获取 HTTP 应答包体内容
+         */
+        public String getBody() {
+            return body;
+        }
+
+        /**
+         * 获取 HTTP 应答 Header
+         */
+        public Headers getHeaders() {
+            return headers;
+        }
+
+        /**
+         * 获取 错误码 (错误应答中的 code 字段)
+         */
+        public String getErrorCode() {
+            return errorCode;
+        }
+
+        /**
+         * 获取 错误消息 (错误应答中的 message 字段)
+         */
+        public String getErrorMessage() {
+            return errorMessage;
+        }
+    }
+
+    public static class Notification {
+        @SerializedName("id")
+        private String id;
+        @SerializedName("create_time")
+        private String createTime;
+        @SerializedName("event_type")
+        private String eventType;
+        @SerializedName("resource_type")
+        private String resourceType;
+        @SerializedName("summary")
+        private String summary;
+        @SerializedName("resource")
+        private Resource resource;
+        private String plaintext;
+
+        public String getId() {
+            return id;
+        }
+
+        public String getCreateTime() {
+            return createTime;
+        }
+
+        public String getEventType() {
+            return eventType;
+        }
+
+        public String getResourceType() {
+            return resourceType;
+        }
+
+        public String getSummary() {
+            return summary;
+        }
+
+        public Resource getResource() {
+            return resource;
+        }
+
+        /**
+         * 获取解密后的业务数据(JSON字符串,需要自行解析)
+         */
+        public String getPlaintext() {
+            return plaintext;
+        }
+
+        private void validate() {
+            if (resource == null) {
+                throw new IllegalArgumentException("Missing required field `resource` in notification");
+            }
+            resource.validate();
+        }
+
+        /**
+         * 使用 APIv3Key 对通知中的业务数据解密,解密结果可以通过 getPlainText 访问。
+         * 外部拿到的 Notification 一定是解密过的,因此本方法没有设置为 public
+         * @param apiv3Key 商户APIv3 Key
+         */
+        private void decrypt(String apiv3Key) {
+            validate();
+
+            plaintext = aesAeadDecrypt(
+                    apiv3Key.getBytes(StandardCharsets.UTF_8),
+                    resource.associatedData.getBytes(StandardCharsets.UTF_8),
+                    resource.nonce.getBytes(StandardCharsets.UTF_8),
+                    Base64.getDecoder().decode(resource.ciphertext)
+            );
+        }
+
+        public static class Resource {
+            @SerializedName("algorithm")
+            private String algorithm;
+
+            @SerializedName("ciphertext")
+            private String ciphertext;
+
+            @SerializedName("associated_data")
+            private String associatedData;
+
+            @SerializedName("nonce")
+            private String nonce;
+
+            @SerializedName("original_type")
+            private String originalType;
+
+            public String getAlgorithm() {
+                return algorithm;
+            }
+
+            public String getCiphertext() {
+                return ciphertext;
+            }
+
+            public String getAssociatedData() {
+                return associatedData;
+            }
+
+            public String getNonce() {
+                return nonce;
+            }
+
+            public String getOriginalType() {
+                return originalType;
+            }
+
+            private void validate() {
+                if (algorithm == null || algorithm.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `algorithm` in Notification" +
+                            ".Resource");
+                }
+                if (!Objects.equals(algorithm, "AEAD_AES_256_GCM")) {
+                    throw new IllegalArgumentException(String.format("Unsupported `algorithm`[%s] in " +
+                            "Notification.Resource", algorithm));
+                }
+
+                if (ciphertext == null || ciphertext.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `ciphertext` in Notification" +
+                            ".Resource");
+                }
+
+                if (associatedData == null || associatedData.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `associatedData` in " +
+                            "Notification.Resource");
+                }
+
+                if (nonce == null || nonce.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `nonce` in Notification" +
+                            ".Resource");
+                }
+
+                if (originalType == null || originalType.isEmpty()) {
+                    throw new IllegalArgumentException("Missing required field `originalType` in " +
+                            "Notification.Resource");
+                }
+            }
+        }
     }
-  }
 }

+ 24 - 8
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/quartz/job/OrTeachingJobService.java

@@ -18,10 +18,7 @@ import org.jeecg.modules.system.app.dto.TeachingDayDTO;
 import org.jeecg.modules.system.app.entity.*;
 import org.jeecg.modules.system.app.mapper.AppDeviceMapper;
 import org.jeecg.modules.system.app.mapper.AppGmtInfoMapper;
-import org.jeecg.modules.system.app.service.IAppSitePlaceService;
-import org.jeecg.modules.system.app.service.IAppSitePriceRulesService;
-import org.jeecg.modules.system.app.service.IAppSiteService;
-import org.jeecg.modules.system.app.service.IAppTeachingTimeService;
+import org.jeecg.modules.system.app.service.*;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
@@ -56,6 +53,10 @@ public class OrTeachingJobService {
 
     private final IAppSiteService appSiteService;
 
+    private final IAppOrderProInfoService appOrderProInfoService;
+
+    private final IAppOrderService appOrderService;
+
     @Resource
     private AppDeviceMapper appDeviceMapper;
 
@@ -243,10 +244,11 @@ public class OrTeachingJobService {
             allRecords.forEach(data -> {
                 log.info("提取数据: {}", data);
                 if(null == appGmtInfoMapper.selectOne(Wrappers.<AppGmtInfo>lambdaQuery().eq(AppGmtInfo::getOriginId, data.getOriginId()).last("LIMIT 1"))){
+                    String siteId = appDeviceMapper.selectOne(Wrappers.<AppDevice>lambdaQuery()
+                            .eq(AppDevice::getDeviceSerial, data.getDeviceSerial().split("-")[0])).getSiteId();
                     appGmtInfoMapper.insert(new AppGmtInfo()
                             .setFamilyId(data.getEmployeeNo())
-                            .setSiteId(appDeviceMapper.selectOne(Wrappers.<AppDevice>lambdaQuery()
-                                    .eq(AppDevice::getDeviceSerial, data.getDeviceSerial().split("-")[0])).getSiteId())
+                            .setSiteId(siteId)
                             .setAuthResultMsg(data.getAuthResultMsg())
                             .setGmtCreate(DateUtils.str2Date(data.getGmtCreate(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")))
                             .setPicUrl(data.getPicUrl())
@@ -256,10 +258,24 @@ public class OrTeachingJobService {
                             .setCreateTime(new Date())
                             .setUpdateTime(new Date())
                             .setDeviceSerialNo(data.getDeviceSerial()));
-
+                    //查询相关日期学校子订单
+                    Date date = DateUtils.str2Date(data.getGmtCreate(), new SimpleDateFormat("yyyy-MM-dd"));
+                    List<AppOrderProInfo> orderProInfoList = appOrderProInfoService.list(Wrappers.lambdaQuery(AppOrderProInfo.class)
+                            .eq(AppOrderProInfo::getUseDateStr, date)
+                            .eq(AppOrderProInfo::getFamilyUserId, data.getEmployeeNo())
+                            .eq(AppOrderProInfo::getSiteId, siteId)
+                            .eq(AppOrderProInfo::getType, CommonConstant.ORDER_PRO_INFO_TYPE_0)
+                    );
+                    //修改订单状态
+                    for (AppOrderProInfo appOrderProInfo : orderProInfoList) {
+                        if (appOrderProInfo.getOrderStatus() == 1){
+                            appOrderProInfo.setOrderStatus(CommonConstant.ORDER_STATUS_2);
+                            appOrderProInfoService.updateById(appOrderProInfo);
+                            appOrderService.update(Wrappers.lambdaUpdate(AppOrder.class).set(AppOrder::getOrderStatus, CommonConstant.ORDER_STATUS_2).eq(AppOrder::getId, appOrderProInfo.getOrderId()));
+                        }
+                    }
                 }
             });
-
         } catch (Exception e) {
             log.error("处理门禁记录失败", e);
         }

+ 3 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/entity/AppOrderProInfo.java

@@ -50,6 +50,9 @@ public class AppOrderProInfo implements Serializable{
     @Excel(name = "商品名称", width = 15)
     @Schema(description = "商品名称")
     private String productName;
+    @Excel(name = "门店ID", width = 15)
+    @Schema(description = "门店ID")
+    private String siteId;
     @Excel(name = "地点", width = 15)
     @Schema(description = "地点")
     private String address;

+ 15 - 15
national-motion-module-system/national-motion-system-start/src/main/resources/application-dev.yml

@@ -333,24 +333,24 @@ justauth:
     prefix: 'demo::'
     timeout: 1h
 # 微信相关
-#wx:
-#  # 微信小程序
-#  miniapp:
-#    configs:
-#      - appid: wx6260718c6fd46efb
-#        secret: 353c9d6d84347de7af9a7788a00a725e
-#        token: #微信小程序消息服务器配置的token
-#        aesKey: #微信小程序消息服务器配置的EncodingAESKey
-#        msgDataFormat: JSON
 wx:
   # 微信小程序
   miniapp:
     configs:
-      - appid: wxc032a09413289004
-        secret: 453c5047ae43288f1d015d48df32a5c0
+      - appid: wx6260718c6fd46efb
+        secret: 353c9d6d84347de7af9a7788a00a725e
         token: #微信小程序消息服务器配置的token
         aesKey: #微信小程序消息服务器配置的EncodingAESKey
         msgDataFormat: JSON
+#wx:
+#  # 微信小程序
+#  miniapp:
+#    configs:
+#      - appid: wxc032a09413289004
+#        secret: 453c5047ae43288f1d015d48df32a5c0
+#        token: #微信小程序消息服务器配置的token
+#        aesKey: #微信小程序消息服务器配置的EncodingAESKey
+#        msgDataFormat: JSON
 # 微信支付配置
   pay:
     #服务商微信支付商户号
@@ -382,10 +382,10 @@ baidu:
 # 微信小程序配置
 wechat:
   miniprogram:
-    appid: wxc032a09413289004
-#    appid: wx6260718c6fd46efb
-    appsecret: 453c5047ae43288f1d015d48df32a5c0
-#    appsecret: 353c9d6d84347de7af9a7788a00a725e
+#    appid: wxc032a09413289004
+    appid: wx6260718c6fd46efb
+#    appsecret: 453c5047ae43288f1d015d48df32a5c0
+    appsecret: 353c9d6d84347de7af9a7788a00a725e
     token:
       cache:
         enabled: true