Explorar el Código

优化代码,添加接口

liuchengsen hace 1 mes
padre
commit
0b7893b902

+ 2 - 2
deploy/docker-compose.yml

@@ -112,12 +112,12 @@ services:
       ALIPAY_APP_ID: ${ALIPAY_APP_ID:-}
       ALIPAY_APP_ID: ${ALIPAY_APP_ID:-}
       ALIPAY_NOTIFY_URL: ${ALIPAY_NOTIFY_URL:-https://priceapi.kailin.com.cn/api/payment/callback/alipay}
       ALIPAY_NOTIFY_URL: ${ALIPAY_NOTIFY_URL:-https://priceapi.kailin.com.cn/api/payment/callback/alipay}
       ALIPAY_RETURN_URL: ${ALIPAY_RETURN_URL:-https://priceapi.kailin.com.cn/payment/success}
       ALIPAY_RETURN_URL: ${ALIPAY_RETURN_URL:-https://priceapi.kailin.com.cn/payment/success}
-      ALIPAY_PRIVATE_KEY_PATH: ${ALIPAY_PRIVATE_KEY_PATH:-}
+      ALIPAY_APP_PRIVATE_KEY_PATH: ${ALIPAY_APP_PRIVATE_KEY_PATH:-}
       ALIPAY_APP_CERT_PATH: ${ALIPAY_APP_CERT_PATH:-}
       ALIPAY_APP_CERT_PATH: ${ALIPAY_APP_CERT_PATH:-}
       ALIPAY_CERT_PATH: ${ALIPAY_CERT_PATH:-}
       ALIPAY_CERT_PATH: ${ALIPAY_CERT_PATH:-}
       ALIPAY_ROOT_CERT_PATH: ${ALIPAY_ROOT_CERT_PATH:-}
       ALIPAY_ROOT_CERT_PATH: ${ALIPAY_ROOT_CERT_PATH:-}
       # 支付宝公钥模式(备选)
       # 支付宝公钥模式(备选)
-      ALIPAY_PRIVATE_KEY: ${ALIPAY_PRIVATE_KEY:-}
+      ALIPAY_APP_PRIVATE_KEY: ${ALIPAY_APP_PRIVATE_KEY:-}
       ALIPAY_PUBLIC_KEY: ${ALIPAY_PUBLIC_KEY:-}
       ALIPAY_PUBLIC_KEY: ${ALIPAY_PUBLIC_KEY:-}
       # 微信支付
       # 微信支付
       WECHAT_PAY_APP_ID: ${WECHAT_PAY_APP_ID:-}
       WECHAT_PAY_APP_ID: ${WECHAT_PAY_APP_ID:-}

+ 140 - 0
zhijiayun-common/src/main/java/com/xuekairui/common/ParseUtil.java

@@ -0,0 +1,140 @@
+package com.xuekairui.common;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 统一解析/格式化工具类
+ * 消除各 Service 中重复散落的字符串转 Long、LocalDateTime 等逻辑
+ */
+public final class ParseUtil {
+
+    private ParseUtil() {}
+
+    /** 标准日期时间格式:yyyy-MM-dd HH:mm:ss */
+    public static final String PATTERN_DATETIME = "yyyy-MM-dd HH:mm:ss";
+
+    /** ISO-8601 UTC 格式(固定毫秒,带 Z):yyyy-MM-dd'T'HH:mm:ss.SSS'Z' */
+    public static final String PATTERN_ISO_UTC_MILLIS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+
+    /** 标准日期时间格式化器 */
+    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(PATTERN_DATETIME);
+
+    /** ISO-8601 UTC 格式化器 */
+    private static final DateTimeFormatter ISO_UTC_MILLIS_FORMATTER =
+            DateTimeFormatter.ofPattern(PATTERN_ISO_UTC_MILLIS).withZone(ZoneOffset.UTC);
+
+    /**
+     * 将字符串解析为 Long,非数值或为空时返回默认值
+     *
+     * @param value        原始字符串
+     * @param defaultValue 解析失败时的默认值
+     * @return 解析后的 Long 或默认值
+     */
+    public static Long parseLong(String value, Long defaultValue) {
+        if (value == null || value.isBlank()) {
+            return defaultValue;
+        }
+        try {
+            return Long.parseLong(value.trim());
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 按指定模式将字符串解析为 LocalDateTime,为空或解析失败时返回默认值
+     *
+     * @param value        原始字符串
+     * @param pattern      日期时间模式
+     * @param defaultValue 解析失败时的默认值
+     * @return 解析后的 LocalDateTime 或默认值
+     */
+    public static LocalDateTime parseLocalDateTime(String value, String pattern, LocalDateTime defaultValue) {
+        if (value == null || value.isBlank()) {
+            return defaultValue;
+        }
+        try {
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+            return LocalDateTime.parse(value.trim(), formatter);
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 将 yyyy-MM-dd HH:mm:ss 字符串解析为 LocalDateTime
+     *
+     * @param value        原始字符串
+     * @param defaultValue 解析失败时的默认值
+     * @return 解析后的 LocalDateTime 或默认值
+     */
+    public static LocalDateTime parseDateTime(String value, LocalDateTime defaultValue) {
+        return parseLocalDateTime(value, PATTERN_DATETIME, defaultValue);
+    }
+
+    /**
+     * 将 ISO-8601 UTC 字符串(如 2026-06-26T08:30:00.000Z)解析为 LocalDateTime(按 UTC 解释)
+     *
+     * @param value        原始字符串
+     * @param defaultValue 解析失败时的默认值
+     * @return 解析后的 LocalDateTime 或默认值
+     */
+    public static LocalDateTime parseIsoUtc(String value, LocalDateTime defaultValue) {
+        if (value == null || value.isBlank()) {
+            return defaultValue;
+        }
+        try {
+            return Instant.parse(value.trim()).atOffset(ZoneOffset.UTC).toLocalDateTime();
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 将 LocalDateTime 按指定模式格式化为字符串
+     *
+     * @param value   日期时间
+     * @param pattern 日期时间模式
+     * @return 格式化后的字符串;value 为空时返回 null
+     */
+    public static String formatLocalDateTime(LocalDateTime value, String pattern) {
+        if (value == null) {
+            return null;
+        }
+        try {
+            return value.format(DateTimeFormatter.ofPattern(pattern));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 将 LocalDateTime 格式化为 yyyy-MM-dd HH:mm:ss 字符串
+     *
+     * @param value 日期时间
+     * @return 格式化后的字符串;value 为空时返回 null
+     */
+    public static String formatDateTime(LocalDateTime value) {
+        if (value == null) {
+            return null;
+        }
+        return value.format(DATETIME_FORMATTER);
+    }
+
+    /**
+     * 将 LocalDateTime 格式化为 ISO-8601 UTC 字符串(yyyy-MM-dd'T'HH:mm:ss.SSS'Z')
+     * <p>按 UTC 时区输出,输入的 LocalDateTime 应视为 UTC 时间</p>
+     *
+     * @param value 日期时间
+     * @return 格式化后的字符串;value 为空时返回 null
+     */
+    public static String formatIsoUtc(LocalDateTime value) {
+        if (value == null) {
+            return null;
+        }
+        return value.atOffset(ZoneOffset.UTC).format(ISO_UTC_MILLIS_FORMATTER);
+    }
+}

+ 14 - 4
zhijiayun-gateway/src/main/resources/application-dev.yml

@@ -58,18 +58,28 @@ payment:
     height: 200
     height: 200
   # 支付宝(开发/测试环境):电脑网站支付,将沙箱证书文件复制到 certs/dev/alipay/ 目录下
   # 支付宝(开发/测试环境):电脑网站支付,将沙箱证书文件复制到 certs/dev/alipay/ 目录下
   alipay:
   alipay:
-    app-id: ${ALIPAY_APP_ID:2021006169624093}
+    app-id: ${ALIPAY_APP_ID:2021006169624278}
     notify-url: ${ALIPAY_NOTIFY_URL:https://priceapi.kailin.com.cn/api/payment/callback/alipay}
     notify-url: ${ALIPAY_NOTIFY_URL:https://priceapi.kailin.com.cn/api/payment/callback/alipay}
     return-url: ${ALIPAY_RETURN_URL:http://localhost:8001/payment/success}
     return-url: ${ALIPAY_RETURN_URL:http://localhost:8001/payment/success}
     server-url: ${ALIPAY_SERVER_URL:https://openapi.alipay.com/gateway.do}
     server-url: ${ALIPAY_SERVER_URL:https://openapi.alipay.com/gateway.do}
     # 公钥模式(优先):配置了 alipay-public-key 即走公钥模式
     # 公钥模式(优先):配置了 alipay-public-key 即走公钥模式
-    private-key: ${ALIPAY_PRIVATE_KEY:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCUw9VUpXoHxL6ycUd/3kaVBtZtkHFDyoCJVbS6QCaPA6Z+kTRGX7z81TWZbSUDZelI5TK6bIA+Duh4smr7vscdNadp65cQP+vIVRYyAbQKPWtwK4rfF07gMcW+CXI3KmJsKt8zZtqyD5cXs0IcGLEhF8EmUTVDSiWGSqHiXl7ZU94dFxDuY0h0iafxPghpDUCrCJTtkfQUsC82aWknRVvPqsyw/0G+V3xupkHbJlAlNwj0ITSt+SvgARZlHtcjVKgLDaO2aE/ydqGyp64nLb253mCc0Rt9tI75rgZiVK7JeMcVKUy8B1gpOZxkvfwxwVzpKi5a7Y3tlth+37vqhFXXAgMBAAECggEAKk11wFg91y+vOnbT6hx+3anQF9/XFXvkCSmB9uR/EnPrSFpqDwjyaMMINtS+jbTRHZVvU2u/G+aJXKj/mw6o+Dllnc0Ma+aBWbo4UYgCdlb40jDPkJjEKSR+Iof1HpLC4hde2PMKhU/Bm0+yIOM0m0ByAD9fcf8HpKRcXE6RkiLELaxMjEL14JHaKSa0l5F4sGbscChFVHgr0KVI0HYtWeV5bm3877QOD86DnlAkeKomXHeNhw+jVbHVzHkV9S0bhfizHloXIbFYaNAFpyBAIypyqhyPlfpvENRjn7y05X1QgobtOX7CBOMWiGbAd9F4RhtDpCvF3546iKMz6N3NQQKBgQDDPKpVii/OU6PD1miT4a3imWCRBY4N7sqQVZDxWge8TlJab7oPALVpK16+D/sH2o33cagsW60yFLwLwXUxbVBlsGznpZEBPGFmQ44jGFyiDwkHmiIh4Wck0PbeJcbQAg9AwSHKjJspZ8kUdOcBHMIainnf0qyOJu8mO7ls4fJiyQKBgQDDEI1XGsxPty7S3XIWGRCRv64aVmUUn5W5Icc/111pp+E4O1gNyvjCDWxIVEQipD5fuAUtC55PG+qa5kY2y8nlOxXB9Uu4fO5a/8U4UZtTHgXJQqcmMWblXHvAtsCwGTLTF4o7WiwCv2QtAYPja1kKxNegBuptygFlWfvhvfujnwKBgE3zDLb2YzvnnPz1v+87LafJQQrlwt/BNVMHNsWGAoymYK8G6KxQxh1lWYBc9QcftYrT8Cc1uyLBsIqpBNdlI7JWDEU4j3qE1wZzdm6ArYtuoVZNrc8FJZ9HZykuwAAQsnJSKeGq3dBcRaZLPmftSXo7S4mXnZlr5hPHyLK2S4YxAoGBALQ2X45AuM9Q2pZ/df5Ka89reirk9H/IK+gc99rYWnnZWq67zM75XTbSdd6g00351ZJTDZ0fJcPKc8VlxyqqvbzlCGXn/GvWJ9O0hv1QD6MLkqbsyEX10C1IcyCI9p36ucTh85h/5zcmoSMfSjzaGSpkzx0OJpkFzqJXLujQv2HbAoGAN6JTrmbZzdviJRBGysEzImUKfZh5MFmlc9de1dBAsGXu5n4DIvsESK4QivooSL7sAhoXj19VWxVAl5JLh2uIYVZyJXbWq6CgimYVtMNbUcIdyuhLM+LlUuAHI7z5erehBs7bxVh+4yKO53axXUSfU/hcxvz9g9wmz26HirzmVGo=}
-    alipay-public-key: ${ALIPAY_PUBLIC_KEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnVxW1gw1r7o5MhKl0OrKSLvoOeunFcdbwyObFi3D+WlD2y4Mr1uBMtEB9fo1Qqe5aNiIqGPRI+4D3ewmWlDEU3agzMbONDK17t+XRmkEuMoQBVkBoJ5ZbXqv5zX1w3Er0FU/ifbo7gepQDAWPqjZB7iZpkm9gVUVFhDLIudegljlu8qS1SIMx/bLcgMmKe8/Fl9G7ptpLRKqu8uiPXapwNNds5DC5wadGDThrszrv+VMSq9GKf9hIUgSDYKaJx44Cdgn0OsEHW4Sy2y0q3oInTU+lxteYaMmGZ3SDrA6GX3Y4YCI+j6RuZ2HL1LgQoA93381PKry6yPG7Pe264HbMwIDAQAB}
+    app-private-key: ${ALIPAY_APP_PRIVATE_KEY:MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPZKA2tBUzhVUOlQkoSaovA6Mg9hTyHuI/lK0eqx5C478MKi4Ve1LMDdatY7jKhY0HVT5vu8VQ+NYyzP6CXTlRhWRCzzWa6BYPzWvR3JAIj+mJN/7jRsWvMx8DFjzR+1VIaO2eA3FHU673pCJwuM8PTgM0t/dW0NuzoYus6/cEUXAKYoM7Bm8qewHjSVIB7bC9Dy1IUwMwbXLtBjb6IPquYfCkXqcgfLSXspxqQ5PTYbWkUxeI24kP+4IcoDo3C8LwOseXD8EHPo1J9N2IbiPD8mfEmTJPo4F+vvlI8oudFcLLVd46YP5gRcmkZkM3F6863vHumXwciFZgVsqqdOSVAgMBAAECggEAbcoxXoQ67Qlam9ud+2vz4eulAiADuqFK2KnWc/PWk0aJruDtgIXGsStqSGQQQXYt0IH6CaB3toPksQQAf/bmDxf0mcGj+RuAAmGXAIQo4797vB6emqaupC8F7dZp4CeTYD/b+tiLsVzFtWd4q//J7ao43EG/eIAcrp2ImcRZAjk+arRv+ZftP98bNwD5H3zPL+M66Y2mVqYSWq5N6ZNTMdrL8rU3s46piKhLQvcjwtiX3EoCju2ubQswDt0Z4K0gQ+TqL6HctIc4mltJlSzxqBSUb3WyifPzhbZWqFPKbuDU+aU5HSJFA6j0YJgFSV8Lg1NYh3BxCu/l1v2UNE0+CQKBgQDta+QAzNdh0P0je/lMPTcqW5X6s0uCA5hCcOGMhVbDxP9fI6sBOnUeNVp0H3wgG1vFRbeZeIqO6qSrdDOG+TfKuG+8lumQCLg0UxpkBvAS0zyTVsHg5N9+qtJQ0lf2jE2BWiP5x/7vqqlLb0x4tD8QIe2itCdRlZTZthhmbDQRXwKBgQDfnzI8uuT+CF3WNlhyvC8APmvKsplxeIlE2Ejn930hQOmSgomwbgSoSkanzZ3yxF9othOc7gDNyz833aWOQrhZ59t3s3QfrkI/ZHlAhfmQ+/+CuxEMvN+RPbkA9a2dLWNMzU9yPDKcVq0JNmlGvrrkpbG4V0Koaf1iMrYkARZKiwKBgAG6YE11MDghCkag116rr2QaiIvrlCgdGJWbNrkMG2aPX4B0xQDcSQ/wOXlQ5VlieZeE+2Idk3yYzpwE238m/zX1g7KusoJ+3cBbvnxt5H9ulClKX1zgrZPBcIhwD33W5ZWEb3CY+Q0Lg/HtnHPgc/kNFiqx/vtECHJRNfpA3VwnAoGBAKTFxtyc0dKlE4+d57pTE0bYt9BZnVrQ+ER0IfRalicEj5bW/OG1j1T5teo7ReD19V9Qmk8NyCXbVy+w8CZM303P3BTtc+lMG6BeRkHW9jdwl6sab8u09/8rPcCJRm4EXaDfv2RrGte7vjCFmTj5nPiRZsxv77vsrTnIKBGLg4NVAoGBAKei4bfOwPsh33KT+e0jMJFlDO3lJ3KvQ47j6kela2LyFvG8rAkwDfqQjdUp2HfrumSQ8+WeJ1fYKpfKEn5XMRAtsCBhCYr0wQyhevG3m1JcuJqioAqo1WljYfUL9PJUFskssAmF9FxrG14mthdbiIlo5H0hYLWTn8aKYv3uvS+e}
+    alipay-public-key: ${ALIPAY_PUBLIC_KEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ZB3OubP0KcNtCVLFsXdkvwMGXnzlLse6lFifFTjo5z3UJQQCRjRgFVgPO5idGiUT4TQaV5IzqDGoP/S4w9l2+oFYw/6Lc5QBAjO441NlD8hf05K8+HEc6qtRAhzimzVyvGPsPFjHxmLaQbjiF0Oqvhez5UKUoFiZcBrqnpy+bB9ym5rMoUUPaTKV/fgdAnS3h4NxIzb2kP/nWMIEWt320yQkkeyIbiWcKmdHwwzgPOIjJm6gDfoN4KQvjuc8CwNXb21YTn4FFqfXMhCSIW5Weh4uS+MN5YL8JeqBaMtit9x1crqnBKKYLxhbrV381O/O9P6qbsI3tPk6ePLFVGxWQIDAQAB}
     # 证书模式(备选):未配置 alipay-public-key 时回退到证书模式,从 certs/dev/alipay/ 读取
     # 证书模式(备选):未配置 alipay-public-key 时回退到证书模式,从 certs/dev/alipay/ 读取
-    private-key-path: ${ALIPAY_PRIVATE_KEY_PATH:certs/dev/alipay/appPrivateKey.txt}
+    app-private-key-path: ${ALIPAY_APP_PRIVATE_KEY_PATH:certs/dev/alipay/appPrivateKey.txt}
     app-cert-path: ${ALIPAY_APP_CERT_PATH:certs/dev/alipay/appCertPublicKey_2021005188648051.crt}
     app-cert-path: ${ALIPAY_APP_CERT_PATH:certs/dev/alipay/appCertPublicKey_2021005188648051.crt}
     alipay-cert-path: ${ALIPAY_CERT_PATH:certs/dev/alipay/alipayCertPublicKey_RSA2.crt}
     alipay-cert-path: ${ALIPAY_CERT_PATH:certs/dev/alipay/alipayCertPublicKey_RSA2.crt}
     alipay-root-cert-path: ${ALIPAY_ROOT_CERT_PATH:certs/dev/alipay/alipayRootCert.crt}
     alipay-root-cert-path: ${ALIPAY_ROOT_CERT_PATH:certs/dev/alipay/alipayRootCert.crt}
+
+
+#    # 公钥模式(优先):配置了 alipay-public-key 即走公钥模式
+#    app-private-key: ${ALIPAY_APP_PRIVATE_KEY:MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPZKA2tBUzhVUOlQkoSaovA6Mg9hTyHuI/lK0eqx5C478MKi4Ve1LMDdatY7jKhY0HVT5vu8VQ+NYyzP6CXTlRhWRCzzWa6BYPzWvR3JAIj+mJN/7jRsWvMx8DFjzR+1VIaO2eA3FHU673pCJwuM8PTgM0t/dW0NuzoYus6/cEUXAKYoM7Bm8qewHjSVIB7bC9Dy1IUwMwbXLtBjb6IPquYfCkXqcgfLSXspxqQ5PTYbWkUxeI24kP+4IcoDo3C8LwOseXD8EHPo1J9N2IbiPD8mfEmTJPo4F+vvlI8oudFcLLVd46YP5gRcmkZkM3F6863vHumXwciFZgVsqqdOSVAgMBAAECggEAbcoxXoQ67Qlam9ud+2vz4eulAiADuqFK2KnWc/PWk0aJruDtgIXGsStqSGQQQXYt0IH6CaB3toPksQQAf/bmDxf0mcGj+RuAAmGXAIQo4797vB6emqaupC8F7dZp4CeTYD/b+tiLsVzFtWd4q//J7ao43EG/eIAcrp2ImcRZAjk+arRv+ZftP98bNwD5H3zPL+M66Y2mVqYSWq5N6ZNTMdrL8rU3s46piKhLQvcjwtiX3EoCju2ubQswDt0Z4K0gQ+TqL6HctIc4mltJlSzxqBSUb3WyifPzhbZWqFPKbuDU+aU5HSJFA6j0YJgFSV8Lg1NYh3BxCu/l1v2UNE0+CQKBgQDta+QAzNdh0P0je/lMPTcqW5X6s0uCA5hCcOGMhVbDxP9fI6sBOnUeNVp0H3wgG1vFRbeZeIqO6qSrdDOG+TfKuG+8lumQCLg0UxpkBvAS0zyTVsHg5N9+qtJQ0lf2jE2BWiP5x/7vqqlLb0x4tD8QIe2itCdRlZTZthhmbDQRXwKBgQDfnzI8uuT+CF3WNlhyvC8APmvKsplxeIlE2Ejn930hQOmSgomwbgSoSkanzZ3yxF9othOc7gDNyz833aWOQrhZ59t3s3QfrkI/ZHlAhfmQ+/+CuxEMvN+RPbkA9a2dLWNMzU9yPDKcVq0JNmlGvrrkpbG4V0Koaf1iMrYkARZKiwKBgAG6YE11MDghCkag116rr2QaiIvrlCgdGJWbNrkMG2aPX4B0xQDcSQ/wOXlQ5VlieZeE+2Idk3yYzpwE238m/zX1g7KusoJ+3cBbvnxt5H9ulClKX1zgrZPBcIhwD33W5ZWEb3CY+Q0Lg/HtnHPgc/kNFiqx/vtECHJRNfpA3VwnAoGBAKTFxtyc0dKlE4+d57pTE0bYt9BZnVrQ+ER0IfRalicEj5bW/OG1j1T5teo7ReD19V9Qmk8NyCXbVy+w8CZM303P3BTtc+lMG6BeRkHW9jdwl6sab8u09/8rPcCJRm4EXaDfv2RrGte7vjCFmTj5nPiRZsxv77vsrTnIKBGLg4NVAoGBAKei4bfOwPsh33KT+e0jMJFlDO3lJ3KvQ47j6kela2LyFvG8rAkwDfqQjdUp2HfrumSQ8+WeJ1fYKpfKEn5XMRAtsCBhCYr0wQyhevG3m1JcuJqioAqo1WljYfUL9PJUFskssAmF9FxrG14mthdbiIlo5H0hYLWTn8aKYv3uvS+e}
+#    alipay-public-key: ${ALIPAY_PUBLIC_KEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ZB3OubP0KcNtCVLFsXdkvwMGXnzlLse6lFifFTjo5z3UJQQCRjRgFVgPO5idGiUT4TQaV5IzqDGoP/S4w9l2+oFYw/6Lc5QBAjO441NlD8hf05K8+HEc6qtRAhzimzVyvGPsPFjHxmLaQbjiF0Oqvhez5UKUoFiZcBrqnpy+bB9ym5rMoUUPaTKV/fgdAnS3h4NxIzb2kP/nWMIEWt320yQkkeyIbiWcKmdHwwzgPOIjJm6gDfoN4KQvjuc8CwNXb21YTn4FFqfXMhCSIW5Weh4uS+MN5YL8JeqBaMtit9x1crqnBKKYLxhbrV381O/O9P6qbsI3tPk6ePLFVGxWQIDAQAB}
+#    # 证书模式(备选):未配置 alipay-public-key 时回退到证书模式,从 certs/dev/alipay/ 读取
+#    app-private-key-path: ${ALIPAY_APP_PRIVATE_KEY_PATH:certs/dev/alipay/appPrivateKey.txt}
+#    app-cert-path: ${ALIPAY_APP_CERT_PATH:certs/dev/alipay/appCertPublicKey_2021005188648051.crt}
+#    alipay-cert-path: ${ALIPAY_CERT_PATH:certs/dev/alipay/alipayCertPublicKey_RSA2.crt}
+#    alipay-root-cert-path: ${ALIPAY_ROOT_CERT_PATH:certs/dev/alipay/alipayRootCert.crt}
   # 微信支付(开发/测试环境):将测试商户证书文件复制到 certs/dev/wechat/ 目录下
   # 微信支付(开发/测试环境):将测试商户证书文件复制到 certs/dev/wechat/ 目录下
   wechat:
   wechat:
     app-id: ${WECHAT_PAY_APP_ID:wxf2f254f9981651dd}
     app-id: ${WECHAT_PAY_APP_ID:wxf2f254f9981651dd}

+ 5 - 4
zhijiayun-gateway/src/main/resources/application-prod.yml

@@ -108,14 +108,15 @@ payment:
   # 支付宝(生产环境):将生产证书文件复制到 certs/prod/alipay/ 或通过环境变量覆盖
   # 支付宝(生产环境):将生产证书文件复制到 certs/prod/alipay/ 或通过环境变量覆盖
   # 支付宝(开发/测试环境):将沙箱证书文件复制到 certs/dev/alipay/ 目录下
   # 支付宝(开发/测试环境):将沙箱证书文件复制到 certs/dev/alipay/ 目录下
   alipay:
   alipay:
-    app-id: ${ALIPAY_APP_ID:2021006169624093}
+    app-id: ${ALIPAY_APP_ID:2021006169624278}
     notify-url: ${ALIPAY_NOTIFY_URL:https://priceapi.kailin.com.cn/api/payment/callback/alipay}
     notify-url: ${ALIPAY_NOTIFY_URL:https://priceapi.kailin.com.cn/api/payment/callback/alipay}
+    return-url: ${ALIPAY_RETURN_URL:http:https://priceapi.kailin.com.cn/payment/success}
     server-url: ${ALIPAY_SERVER_URL:https://openapi.alipay.com/gateway.do}
     server-url: ${ALIPAY_SERVER_URL:https://openapi.alipay.com/gateway.do}
     # 公钥模式(优先):配置了 alipay-public-key 即走公钥模式
     # 公钥模式(优先):配置了 alipay-public-key 即走公钥模式
-    private-key: ${ALIPAY_PRIVATE_KEY:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCUw9VUpXoHxL6ycUd/3kaVBtZtkHFDyoCJVbS6QCaPA6Z+kTRGX7z81TWZbSUDZelI5TK6bIA+Duh4smr7vscdNadp65cQP+vIVRYyAbQKPWtwK4rfF07gMcW+CXI3KmJsKt8zZtqyD5cXs0IcGLEhF8EmUTVDSiWGSqHiXl7ZU94dFxDuY0h0iafxPghpDUCrCJTtkfQUsC82aWknRVvPqsyw/0G+V3xupkHbJlAlNwj0ITSt+SvgARZlHtcjVKgLDaO2aE/ydqGyp64nLb253mCc0Rt9tI75rgZiVK7JeMcVKUy8B1gpOZxkvfwxwVzpKi5a7Y3tlth+37vqhFXXAgMBAAECggEAKk11wFg91y+vOnbT6hx+3anQF9/XFXvkCSmB9uR/EnPrSFpqDwjyaMMINtS+jbTRHZVvU2u/G+aJXKj/mw6o+Dllnc0Ma+aBWbo4UYgCdlb40jDPkJjEKSR+Iof1HpLC4hde2PMKhU/Bm0+yIOM0m0ByAD9fcf8HpKRcXE6RkiLELaxMjEL14JHaKSa0l5F4sGbscChFVHgr0KVI0HYtWeV5bm3877QOD86DnlAkeKomXHeNhw+jVbHVzHkV9S0bhfizHloXIbFYaNAFpyBAIypyqhyPlfpvENRjn7y05X1QgobtOX7CBOMWiGbAd9F4RhtDpCvF3546iKMz6N3NQQKBgQDDPKpVii/OU6PD1miT4a3imWCRBY4N7sqQVZDxWge8TlJab7oPALVpK16+D/sH2o33cagsW60yFLwLwXUxbVBlsGznpZEBPGFmQ44jGFyiDwkHmiIh4Wck0PbeJcbQAg9AwSHKjJspZ8kUdOcBHMIainnf0qyOJu8mO7ls4fJiyQKBgQDDEI1XGsxPty7S3XIWGRCRv64aVmUUn5W5Icc/111pp+E4O1gNyvjCDWxIVEQipD5fuAUtC55PG+qa5kY2y8nlOxXB9Uu4fO5a/8U4UZtTHgXJQqcmMWblXHvAtsCwGTLTF4o7WiwCv2QtAYPja1kKxNegBuptygFlWfvhvfujnwKBgE3zDLb2YzvnnPz1v+87LafJQQrlwt/BNVMHNsWGAoymYK8G6KxQxh1lWYBc9QcftYrT8Cc1uyLBsIqpBNdlI7JWDEU4j3qE1wZzdm6ArYtuoVZNrc8FJZ9HZykuwAAQsnJSKeGq3dBcRaZLPmftSXo7S4mXnZlr5hPHyLK2S4YxAoGBALQ2X45AuM9Q2pZ/df5Ka89reirk9H/IK+gc99rYWnnZWq67zM75XTbSdd6g00351ZJTDZ0fJcPKc8VlxyqqvbzlCGXn/GvWJ9O0hv1QD6MLkqbsyEX10C1IcyCI9p36ucTh85h/5zcmoSMfSjzaGSpkzx0OJpkFzqJXLujQv2HbAoGAN6JTrmbZzdviJRBGysEzImUKfZh5MFmlc9de1dBAsGXu5n4DIvsESK4QivooSL7sAhoXj19VWxVAl5JLh2uIYVZyJXbWq6CgimYVtMNbUcIdyuhLM+LlUuAHI7z5erehBs7bxVh+4yKO53axXUSfU/hcxvz9g9wmz26HirzmVGo=}
-    alipay-public-key: ${ALIPAY_PUBLIC_KEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnVxW1gw1r7o5MhKl0OrKSLvoOeunFcdbwyObFi3D+WlD2y4Mr1uBMtEB9fo1Qqe5aNiIqGPRI+4D3ewmWlDEU3agzMbONDK17t+XRmkEuMoQBVkBoJ5ZbXqv5zX1w3Er0FU/ifbo7gepQDAWPqjZB7iZpkm9gVUVFhDLIudegljlu8qS1SIMx/bLcgMmKe8/Fl9G7ptpLRKqu8uiPXapwNNds5DC5wadGDThrszrv+VMSq9GKf9hIUgSDYKaJx44Cdgn0OsEHW4Sy2y0q3oInTU+lxteYaMmGZ3SDrA6GX3Y4YCI+j6RuZ2HL1LgQoA93381PKry6yPG7Pe264HbMwIDAQAB}
+    app-private-key: ${ALIPAY_APP_PRIVATE_KEY:MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPZKA2tBUzhVUOlQkoSaovA6Mg9hTyHuI/lK0eqx5C478MKi4Ve1LMDdatY7jKhY0HVT5vu8VQ+NYyzP6CXTlRhWRCzzWa6BYPzWvR3JAIj+mJN/7jRsWvMx8DFjzR+1VIaO2eA3FHU673pCJwuM8PTgM0t/dW0NuzoYus6/cEUXAKYoM7Bm8qewHjSVIB7bC9Dy1IUwMwbXLtBjb6IPquYfCkXqcgfLSXspxqQ5PTYbWkUxeI24kP+4IcoDo3C8LwOseXD8EHPo1J9N2IbiPD8mfEmTJPo4F+vvlI8oudFcLLVd46YP5gRcmkZkM3F6863vHumXwciFZgVsqqdOSVAgMBAAECggEAbcoxXoQ67Qlam9ud+2vz4eulAiADuqFK2KnWc/PWk0aJruDtgIXGsStqSGQQQXYt0IH6CaB3toPksQQAf/bmDxf0mcGj+RuAAmGXAIQo4797vB6emqaupC8F7dZp4CeTYD/b+tiLsVzFtWd4q//J7ao43EG/eIAcrp2ImcRZAjk+arRv+ZftP98bNwD5H3zPL+M66Y2mVqYSWq5N6ZNTMdrL8rU3s46piKhLQvcjwtiX3EoCju2ubQswDt0Z4K0gQ+TqL6HctIc4mltJlSzxqBSUb3WyifPzhbZWqFPKbuDU+aU5HSJFA6j0YJgFSV8Lg1NYh3BxCu/l1v2UNE0+CQKBgQDta+QAzNdh0P0je/lMPTcqW5X6s0uCA5hCcOGMhVbDxP9fI6sBOnUeNVp0H3wgG1vFRbeZeIqO6qSrdDOG+TfKuG+8lumQCLg0UxpkBvAS0zyTVsHg5N9+qtJQ0lf2jE2BWiP5x/7vqqlLb0x4tD8QIe2itCdRlZTZthhmbDQRXwKBgQDfnzI8uuT+CF3WNlhyvC8APmvKsplxeIlE2Ejn930hQOmSgomwbgSoSkanzZ3yxF9othOc7gDNyz833aWOQrhZ59t3s3QfrkI/ZHlAhfmQ+/+CuxEMvN+RPbkA9a2dLWNMzU9yPDKcVq0JNmlGvrrkpbG4V0Koaf1iMrYkARZKiwKBgAG6YE11MDghCkag116rr2QaiIvrlCgdGJWbNrkMG2aPX4B0xQDcSQ/wOXlQ5VlieZeE+2Idk3yYzpwE238m/zX1g7KusoJ+3cBbvnxt5H9ulClKX1zgrZPBcIhwD33W5ZWEb3CY+Q0Lg/HtnHPgc/kNFiqx/vtECHJRNfpA3VwnAoGBAKTFxtyc0dKlE4+d57pTE0bYt9BZnVrQ+ER0IfRalicEj5bW/OG1j1T5teo7ReD19V9Qmk8NyCXbVy+w8CZM303P3BTtc+lMG6BeRkHW9jdwl6sab8u09/8rPcCJRm4EXaDfv2RrGte7vjCFmTj5nPiRZsxv77vsrTnIKBGLg4NVAoGBAKei4bfOwPsh33KT+e0jMJFlDO3lJ3KvQ47j6kela2LyFvG8rAkwDfqQjdUp2HfrumSQ8+WeJ1fYKpfKEn5XMRAtsCBhCYr0wQyhevG3m1JcuJqioAqo1WljYfUL9PJUFskssAmF9FxrG14mthdbiIlo5H0hYLWTn8aKYv3uvS+e}
+    alipay-public-key: ${ALIPAY_PUBLIC_KEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ZB3OubP0KcNtCVLFsXdkvwMGXnzlLse6lFifFTjo5z3UJQQCRjRgFVgPO5idGiUT4TQaV5IzqDGoP/S4w9l2+oFYw/6Lc5QBAjO441NlD8hf05K8+HEc6qtRAhzimzVyvGPsPFjHxmLaQbjiF0Oqvhez5UKUoFiZcBrqnpy+bB9ym5rMoUUPaTKV/fgdAnS3h4NxIzb2kP/nWMIEWt320yQkkeyIbiWcKmdHwwzgPOIjJm6gDfoN4KQvjuc8CwNXb21YTn4FFqfXMhCSIW5Weh4uS+MN5YL8JeqBaMtit9x1crqnBKKYLxhbrV381O/O9P6qbsI3tPk6ePLFVGxWQIDAQAB}
     # 证书模式(备选):未配置 alipay-public-key 时回退到证书模式,从 certs/dev/alipay/ 读取
     # 证书模式(备选):未配置 alipay-public-key 时回退到证书模式,从 certs/dev/alipay/ 读取
-    private-key-path: ${ALIPAY_PRIVATE_KEY_PATH:certs/prod/alipay/appPrivateKey.txt}
+    app-private-key-path: ${ALIPAY_APP_PRIVATE_KEY_PATH:certs/prod/alipay/appPrivateKey.txt}
     app-cert-path: ${ALIPAY_APP_CERT_PATH:certs/prod/alipay/appCertPublicKey_2021005188648051.crt}
     app-cert-path: ${ALIPAY_APP_CERT_PATH:certs/prod/alipay/appCertPublicKey_2021005188648051.crt}
     alipay-cert-path: ${ALIPAY_CERT_PATH:certs/prod/alipay/alipayCertPublicKey_RSA2.crt}
     alipay-cert-path: ${ALIPAY_CERT_PATH:certs/prod/alipay/alipayCertPublicKey_RSA2.crt}
     alipay-root-cert-path: ${ALIPAY_ROOT_CERT_PATH:certs/prod/alipay/alipayRootCert.crt}
     alipay-root-cert-path: ${ALIPAY_ROOT_CERT_PATH:certs/prod/alipay/alipayRootCert.crt}

+ 1 - 1
zhijiayun-gateway/src/main/resources/application.yml

@@ -98,7 +98,7 @@ payment:
   # 支付宝配置(电脑网站支付 alipay.trade.page.pay)
   # 支付宝配置(电脑网站支付 alipay.trade.page.pay)
   alipay:
   alipay:
     app-id: ${ALIPAY_APP_ID:}
     app-id: ${ALIPAY_APP_ID:}
-    private-key: ${ALIPAY_PRIVATE_KEY:}
+    app-private-key: ${ALIPAY_APP_PRIVATE_KEY:}
     alipay-public-key: ${ALIPAY_PUBLIC_KEY:}
     alipay-public-key: ${ALIPAY_PUBLIC_KEY:}
     notify-url: ${ALIPAY_NOTIFY_URL:https://your-domain.com/api/payment/callback/alipay}
     notify-url: ${ALIPAY_NOTIFY_URL:https://your-domain.com/api/payment/callback/alipay}
     return-url: ${ALIPAY_RETURN_URL:https://your-domain.com/payment/success}
     return-url: ${ALIPAY_RETURN_URL:https://your-domain.com/payment/success}

+ 1 - 1
zhijiayun-gateway/src/main/resources/db/migration-v2.sql

@@ -50,7 +50,7 @@ ON DUPLICATE KEY UPDATE
 -- 4. DDL: 搜索记录表(完整比价快照)
 -- 4. DDL: 搜索记录表(完整比价快照)
 -- ============================================
 -- ============================================
 CREATE TABLE IF NOT EXISTS `t_search_record` (
 CREATE TABLE IF NOT EXISTS `t_search_record` (
-    `id`                BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `id`                BIGINT        NOT NULL COMMENT '主键ID(雪花算法)',
     `user_id`           BIGINT        NOT NULL COMMENT '用户ID',
     `user_id`           BIGINT        NOT NULL COMMENT '用户ID',
     `drug_name`         VARCHAR(200)  NOT NULL COMMENT '药品名称',
     `drug_name`         VARCHAR(200)  NOT NULL COMMENT '药品名称',
     `searched_at`       DATETIME      DEFAULT CURRENT_TIMESTAMP COMMENT '搜索时间',
     `searched_at`       DATETIME      DEFAULT CURRENT_TIMESTAMP COMMENT '搜索时间',

+ 2 - 2
zhijiayun-gateway/src/main/resources/db/schema.sql

@@ -269,7 +269,7 @@ CREATE TABLE IF NOT EXISTS `t_payment_order` (
     `amount`                DECIMAL(10,2) NOT NULL COMMENT '实付金额(元)',
     `amount`                DECIMAL(10,2) NOT NULL COMMENT '实付金额(元)',
     `channel`               VARCHAR(20)   NOT NULL COMMENT '支付渠道:WECHAT/ALIPAY',
     `channel`               VARCHAR(20)   NOT NULL COMMENT '支付渠道:WECHAT/ALIPAY',
     `status`                VARCHAR(20)   NOT NULL DEFAULT 'PENDING' COMMENT '状态:PENDING/PAID/EXPIRED/REFUNDED',
     `status`                VARCHAR(20)   NOT NULL DEFAULT 'PENDING' COMMENT '状态:PENDING/PAID/EXPIRED/REFUNDED',
-    `qr_code_url`           VARCHAR(512)  DEFAULT NULL COMMENT '二维码内容/URL',
+    `qr_code_url`           VARCHAR(2048) DEFAULT NULL COMMENT '二维码内容/URL(支付宝电脑网站支付为收银台跳转URL)',
     `trade_no`              VARCHAR(64)   DEFAULT NULL COMMENT '第三方交易号',
     `trade_no`              VARCHAR(64)   DEFAULT NULL COMMENT '第三方交易号',
     `paid_time`             DATETIME      DEFAULT NULL COMMENT '支付时间',
     `paid_time`             DATETIME      DEFAULT NULL COMMENT '支付时间',
     `expire_time`           DATETIME      NOT NULL COMMENT '订单过期时间',
     `expire_time`           DATETIME      NOT NULL COMMENT '订单过期时间',
@@ -468,7 +468,7 @@ CREATE TABLE IF NOT EXISTS `t_search_history` (
 -- 搜索记录表(完整比价快照)
 -- 搜索记录表(完整比价快照)
 -- ============================================
 -- ============================================
 CREATE TABLE IF NOT EXISTS `t_search_record` (
 CREATE TABLE IF NOT EXISTS `t_search_record` (
-    `id`                BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `id`                BIGINT        NOT NULL COMMENT '主键ID(雪花算法)',
     `user_id`           BIGINT        NOT NULL COMMENT '用户ID',
     `user_id`           BIGINT        NOT NULL COMMENT '用户ID',
     `drug_name`         VARCHAR(200)  NOT NULL COMMENT '药品名称',
     `drug_name`         VARCHAR(200)  NOT NULL COMMENT '药品名称',
     `searched_at`       DATETIME      DEFAULT CURRENT_TIMESTAMP COMMENT '搜索时间',
     `searched_at`       DATETIME      DEFAULT CURRENT_TIMESTAMP COMMENT '搜索时间',

+ 19 - 15
zhijiayun-payment/src/main/java/com/xuekairui/payment/config/PaymentConfig.java

@@ -40,7 +40,9 @@ public class PaymentConfig {
      * <p>优先从 classpath 读取(兼容 IDE 和 JAR),找不到则尝试文件系统路径
      * <p>优先从 classpath 读取(兼容 IDE 和 JAR),找不到则尝试文件系统路径
      */
      */
     private String readFileContent(String path) {
     private String readFileContent(String path) {
-        if (path == null || path.isBlank()) return null;
+        if (path == null || path.isBlank()) {
+            return null;
+        }
 
 
         // 优先从 classpath 读取
         // 优先从 classpath 读取
         ClassPathResource resource = new ClassPathResource(path);
         ClassPathResource resource = new ClassPathResource(path);
@@ -66,7 +68,9 @@ public class PaymentConfig {
      * 找不到则返回原始路径(适用于外部挂载证书的生产环境)
      * 找不到则返回原始路径(适用于外部挂载证书的生产环境)
      */
      */
     private String resolveFilePath(String path) {
     private String resolveFilePath(String path) {
-        if (path == null || path.isBlank()) return path;
+        if (path == null || path.isBlank()) {
+            return path;
+        }
 
 
         ClassPathResource resource = new ClassPathResource(path);
         ClassPathResource resource = new ClassPathResource(path);
         if (resource.exists()) {
         if (resource.exists()) {
@@ -87,8 +91,8 @@ public class PaymentConfig {
      *
      *
      * <p>支持两种模式,根据配置自动选择(公钥模式优先):
      * <p>支持两种模式,根据配置自动选择(公钥模式优先):
      * <ul>
      * <ul>
-     *   <li>公钥模式(优先):配置 private-key + alipay-public-key</li>
-     *   <li>证书模式(备选):配置 app-cert-path + alipay-cert-path + alipay-root-cert-path + private-key-path</li>
+     *   <li>公钥模式(优先):配置 app-private-key + alipay-public-key</li>
+     *   <li>证书模式(备选):配置 app-cert-path + alipay-cert-path + alipay-root-cert-path + app-private-key-path</li>
      * </ul>
      * </ul>
      * 公共配置:app-id、server-url、notify-url
      * 公共配置:app-id、server-url、notify-url
      */
      */
@@ -102,12 +106,12 @@ public class PaymentConfig {
             log.info("初始化支付宝客户端(公钥模式): appId={}, serverUrl={}, notifyUrl={}",
             log.info("初始化支付宝客户端(公钥模式): appId={}, serverUrl={}, notifyUrl={}",
                     al.getAppId(), al.getServerUrl(), al.getNotifyUrl());
                     al.getAppId(), al.getServerUrl(), al.getNotifyUrl());
 
 
-            // 如果配置了私钥文件路径且未直接配置私钥字符串,自动读取文件内容
-            String privateKey = al.getPrivateKey();
+            // 如果配置了应用私钥文件路径且未直接配置应用私钥字符串,自动读取文件内容
+            String privateKey = al.getAppPrivateKey();
             if ((privateKey == null || privateKey.isBlank())
             if ((privateKey == null || privateKey.isBlank())
-                    && al.getPrivateKeyPath() != null && !al.getPrivateKeyPath().isBlank()) {
-                privateKey = readFileContent(al.getPrivateKeyPath());
-                log.info("已从文件读取应用私钥: {}", al.getPrivateKeyPath());
+                    && al.getAppPrivateKeyPath() != null && !al.getAppPrivateKeyPath().isBlank()) {
+                privateKey = readFileContent(al.getAppPrivateKeyPath());
+                log.info("已从文件读取应用私钥: {}", al.getAppPrivateKeyPath());
             }
             }
 
 
             return new DefaultAlipayClient(
             return new DefaultAlipayClient(
@@ -126,11 +130,11 @@ public class PaymentConfig {
             log.info("初始化支付宝客户端(证书模式): appId={}, serverUrl={}, notifyUrl={}",
             log.info("初始化支付宝客户端(证书模式): appId={}, serverUrl={}, notifyUrl={}",
                     al.getAppId(), al.getServerUrl(), al.getNotifyUrl());
                     al.getAppId(), al.getServerUrl(), al.getNotifyUrl());
 
 
-            // 如果配置了私钥文件路径,自动读取文件内容(从 classpath 或文件系统)
-            String privateKey = al.getPrivateKey();
-            if (al.getPrivateKeyPath() != null && !al.getPrivateKeyPath().isBlank()) {
-                privateKey = readFileContent(al.getPrivateKeyPath());
-                log.info("已从文件读取应用私钥: {}", al.getPrivateKeyPath());
+            // 如果配置了应用私钥文件路径,自动读取文件内容(从 classpath 或文件系统)
+            String privateKey = al.getAppPrivateKey();
+            if (al.getAppPrivateKeyPath() != null && !al.getAppPrivateKeyPath().isBlank()) {
+                privateKey = readFileContent(al.getAppPrivateKeyPath());
+                log.info("应用私钥: {}", al.getAppPrivateKeyPath());
             }
             }
 
 
             // 证书路径解析为绝对路径(Alipay SDK 需要文件系统路径)
             // 证书路径解析为绝对路径(Alipay SDK 需要文件系统路径)
@@ -149,7 +153,7 @@ public class PaymentConfig {
         }
         }
 
 
         throw new IllegalStateException(
         throw new IllegalStateException(
-                "支付宝配置不完整:需要配置公钥模式(alipay-public-key)或证书模式(app-cert-path)");
+                "支付宝配置不完整:需要配置公钥模式(alipay-public-key)或证书模式(app-cert-path),并确保已配置应用私钥(app-private-key)");
     }
     }
 
 
     /**
     /**

+ 7 - 7
zhijiayun-payment/src/main/java/com/xuekairui/payment/config/PaymentProperties.java

@@ -73,18 +73,18 @@ public class PaymentProperties {
     /**
     /**
      * 支付宝配置(电脑网站支付 alipay.trade.page.pay)
      * 支付宝配置(电脑网站支付 alipay.trade.page.pay)
      * 支持两种模式(公钥模式优先):
      * 支持两种模式(公钥模式优先):
-     * 1. 公钥模式(优先):配置 private-key + alipay-public-key
-     * 2. 证书模式(备选):配置 private-key-path + app-cert-path + alipay-cert-path + alipay-root-cert-path
+     * 1. 公钥模式(优先):配置 app-private-key + alipay-public-key
+     * 2. 证书模式(备选):配置 app-private-key-path + app-cert-path + alipay-cert-path + alipay-root-cert-path
      * 文档:https://opendocs.alipay.com/open/028r8t
      * 文档:https://opendocs.alipay.com/open/028r8t
      */
      */
     @Data
     @Data
     public static class Alipay {
     public static class Alipay {
         /** 支付宝应用 appId */
         /** 支付宝应用 appId */
         private String appId;
         private String appId;
-        /** 应用私钥(RSA2),公钥模式时填写私钥字符串 */
-        private String privateKey;
-        /** 应用私钥文件路径,证书模式时填写(设置后自动读取文件内容作为 privateKey) */
-        private String privateKeyPath;
+        /** 应用私钥(RSA2),公钥模式时填写应用私钥字符串 */
+        private String appPrivateKey;
+        /** 应用私钥文件路径,证书模式时填写(设置后自动读取文件内容作为 appPrivateKey) */
+        private String appPrivateKeyPath;
         /** 支付宝公钥(公钥模式验签用) */
         /** 支付宝公钥(公钥模式验签用) */
         private String alipayPublicKey;
         private String alipayPublicKey;
         /** 应用公钥证书路径(证书模式,.crt 文件) */
         /** 应用公钥证书路径(证书模式,.crt 文件) */
@@ -97,7 +97,7 @@ public class PaymentProperties {
         private String notifyUrl;
         private String notifyUrl;
         /** 支付完成后同步跳转URL(用户付款后浏览器跳转回的页面) */
         /** 支付完成后同步跳转URL(用户付款后浏览器跳转回的页面) */
         private String returnUrl;
         private String returnUrl;
-        /** 支付宝网关地址(正式:https://openapi.alipay.com/gateway.do,沙箱:https://openapi-sandbox.dl.alipaydev.com/gateway.do) */
+        /** 支付宝网关地址 */
         private String serverUrl = "https://openapi.alipay.com/gateway.do";
         private String serverUrl = "https://openapi.alipay.com/gateway.do";
         /** 数据格式(固定 json) */
         /** 数据格式(固定 json) */
         private String format = "json";
         private String format = "json";

+ 1 - 1
zhijiayun-payment/src/main/java/com/xuekairui/payment/dto/OrderResponse.java

@@ -34,7 +34,7 @@ public class OrderResponse {
     /** 二维码 Base64 图片(data:image/png;base64,...) */
     /** 二维码 Base64 图片(data:image/png;base64,...) */
     private String qrCodeBase64;
     private String qrCodeBase64;
 
 
-    /** 支付链接(生产模式下微信/支付宝返回的支付跳转URL,前端可直转) */
+    /** 支付链接/码串(微信=code_url,支付宝precreate=qr_code码串) */
     private String payUrl;
     private String payUrl;
 
 
     /** 订单过期时间 */
     /** 订单过期时间 */

+ 27 - 34
zhijiayun-payment/src/main/java/com/xuekairui/payment/service/PaymentOrderService.java

@@ -2,13 +2,12 @@ package com.xuekairui.payment.service;
 
 
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.IdUtil;
 import com.alipay.api.AlipayClient;
 import com.alipay.api.AlipayClient;
-import com.alipay.api.domain.AlipayTradePagePayModel;
-import com.alipay.api.request.AlipayTradePagePayRequest;
-import com.alipay.api.response.AlipayTradePagePayResponse;
+import com.alipay.api.domain.AlipayTradePrecreateModel;
+import com.alipay.api.request.AlipayTradePrecreateRequest;
+import com.alipay.api.response.AlipayTradePrecreateResponse;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
 import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
 import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.service.WxPayService;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.xuekairui.common.BusinessException;
 import com.xuekairui.common.BusinessException;
 import com.xuekairui.common.ErrorCode;
 import com.xuekairui.common.ErrorCode;
 import com.xuekairui.payment.config.PaymentProperties;
 import com.xuekairui.payment.config.PaymentProperties;
@@ -81,7 +80,9 @@ public class PaymentOrderService {
             qrContent = prepayResult.getQrContent();
             qrContent = prepayResult.getQrContent();
             payUrl = prepayResult.getPayUrl();
             payUrl = prepayResult.getPayUrl();
         }
         }
-        String qrCodeBase64 = qrCodeService.generateBase64(qrContent);
+        // 仅在有二维码内容时生成二维码图片(微信Native/支付宝precreate均返回qrContent,由后端统一生成base64)
+        String qrCodeBase64 = (qrContent != null && !qrContent.isEmpty())
+                ? qrCodeService.generateBase64(qrContent) : null;
 
 
         // 保存订单
         // 保存订单
         PaymentOrder order = PaymentOrder.builder()
         PaymentOrder order = PaymentOrder.builder()
@@ -286,15 +287,10 @@ public class PaymentOrderService {
      */
      */
     private PrepayResult createPrepay(PaymentChannel channel, String orderNo,
     private PrepayResult createPrepay(PaymentChannel channel, String orderNo,
                                       java.math.BigDecimal amount, String subject) {
                                       java.math.BigDecimal amount, String subject) {
-        switch (channel) {
-            case WECHAT:
-                return createWechatPrepay(orderNo, amount, subject);
-            case ALIPAY:
-                return createAlipayPrepay(orderNo, amount, subject);
-            default:
-                throw new BusinessException(ErrorCode.PAY_CHANNEL_NOT_SUPPORT,
-                        "不支持的支付渠道: " + channel);
-        }
+        return switch (channel) {
+            case WECHAT -> createWechatPrepay(orderNo, amount, subject);
+            case ALIPAY -> createAlipayPrepay(orderNo, amount, subject);
+        };
     }
     }
 
 
     /**
     /**
@@ -353,17 +349,17 @@ public class PaymentOrderService {
     }
     }
 
 
     /**
     /**
-     * 支付宝电脑网站支付预下单(alipay.trade.page.pay
+     * 支付宝当面付预下单(alipay.trade.precreate
      *
      *
-     * <p>使用 AlipayTradePagePayRequest 调用支付宝电脑网站支付接口
-     * 通过 pageExecute() 生成支付跳转链接/HTML表单,前端引导用户跳转到支付宝收银台完成付款
-     * 支付宝异步回调 notifyUrl,同步跳转 returnUrl
+     * <p>调用支付宝「统一收单线下交易预创建」接口,生成支付码串(qr_code)
+     * 后端将其转换为二维码 base64 图片,前端在自定义弹窗中展示
+     * 用户用支付宝 App 扫码完成付款,支付宝异步回调 notifyUrl 通知支付结果
      *
      *
-     * @see <a href="https://opendocs.alipay.com/open/028r8t">统一收单下单并支付页面接口</a>
+     * @see <a href="https://opendocs.alipay.com/open/02ekfh">统一收单线下交易预创建</a>
      */
      */
     private PrepayResult createAlipayPrepay(String orderNo, java.math.BigDecimal amount, String subject) {
     private PrepayResult createAlipayPrepay(String orderNo, java.math.BigDecimal amount, String subject) {
         PaymentProperties.Alipay al = paymentProperties.getAlipay();
         PaymentProperties.Alipay al = paymentProperties.getAlipay();
-        if (al.getAppId() == null || al.getPrivateKey() == null) {
+        if (al.getAppId() == null || al.getAppPrivateKey() == null) {
             throw new BusinessException(ErrorCode.PAY_CHANNEL_NOT_CONFIGURED,
             throw new BusinessException(ErrorCode.PAY_CHANNEL_NOT_CONFIGURED,
                     "支付宝未配置,请检查 payment.alipay 参数");
                     "支付宝未配置,请检查 payment.alipay 参数");
         }
         }
@@ -374,25 +370,22 @@ public class PaymentOrderService {
                     "AlipayClient未初始化,请确认 payment.mock=false 且已配置支付宝参数");
                     "AlipayClient未初始化,请确认 payment.mock=false 且已配置支付宝参数");
         }
         }
 
 
-        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
+        AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
         request.setNotifyUrl(al.getNotifyUrl());
         request.setNotifyUrl(al.getNotifyUrl());
-        if (al.getReturnUrl() != null && !al.getReturnUrl().isBlank()) {
-            request.setReturnUrl(al.getReturnUrl());
-        }
 
 
-        AlipayTradePagePayModel model = new AlipayTradePagePayModel();
+        AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
         model.setOutTradeNo(orderNo);
         model.setOutTradeNo(orderNo);
         model.setTotalAmount(amount.toPlainString());
         model.setTotalAmount(amount.toPlainString());
         model.setSubject(subject);
         model.setSubject(subject);
+        model.setProductCode("QR_CODE_OFFLINE");
         model.setTimeoutExpress(paymentProperties.getOrderExpireMinutes() + "m");
         model.setTimeoutExpress(paymentProperties.getOrderExpireMinutes() + "m");
-        model.setProductCode("FAST_INSTANT_TRADE_PAY");
         request.setBizModel(model);
         request.setBizModel(model);
 
 
         try {
         try {
-            // 公钥模式优先:配置了支付宝公钥用 pageExecute,否则用 certificateExecute
+            // 公钥模式用 execute(),证书模式用 certificateExecute()
             boolean publicKeyMode = al.getAlipayPublicKey() != null && !al.getAlipayPublicKey().isBlank();
             boolean publicKeyMode = al.getAlipayPublicKey() != null && !al.getAlipayPublicKey().isBlank();
-            AlipayTradePagePayResponse response = publicKeyMode
-                    ? alipayClient.pageExecute(request)
+            AlipayTradePrecreateResponse response = publicKeyMode
+                    ? alipayClient.execute(request)
                     : alipayClient.certificateExecute(request);
                     : alipayClient.certificateExecute(request);
             if (!response.isSuccess()) {
             if (!response.isSuccess()) {
                 log.error("支付宝预下单失败: orderNo={}, code={}, msg={}, subCode={}, subMsg={}",
                 log.error("支付宝预下单失败: orderNo={}, code={}, msg={}, subCode={}, subMsg={}",
@@ -402,13 +395,13 @@ public class PaymentOrderService {
                         "支付宝下单失败: " + response.getSubMsg());
                         "支付宝下单失败: " + response.getSubMsg());
             }
             }
 
 
-            // pageExecute 返回的 body() 是支付表单HTML或跳转URL
-            String payForm = response.getBody();
-            log.info("支付宝预下单成功: orderNo={}, amount={}", orderNo, amount);
+            String qrCode = response.getQrCode();
+            log.info("支付宝预下单成功: orderNo={}, amount={}, qrCode={}", orderNo, amount, qrCode);
 
 
+            // 返回 qr_code 支付码串,后端生成base64二维码,前端在自定义弹窗中展示
             return PrepayResult.builder()
             return PrepayResult.builder()
-                    .qrContent(payForm)   // 表单内容可作为二维码跳转目标(备选)
-                    .payUrl(payForm)      // 前端直接跳转此URL/渲染此表单
+                    .qrContent(qrCode)
+                    .payUrl(qrCode)
                     .build();
                     .build();
         } catch (BusinessException e) {
         } catch (BusinessException e) {
             throw e;
             throw e;

+ 132 - 22
zhijiayun-test/src/main/resources/static/invite-test.html

@@ -98,6 +98,48 @@
         .admin-login-area .login-title { color: #e65100 !important; }
         .admin-login-area .login-title { color: #e65100 !important; }
         .admin-tabs .module-tab.active { color: #e65100; border-bottom-color: #e65100; }
         .admin-tabs .module-tab.active { color: #e65100; border-bottom-color: #e65100; }
         .admin-tabs .module-tab:hover { background: #fff3e0; color: #e65100; }
         .admin-tabs .module-tab:hover { background: #fff3e0; color: #e65100; }
+
+        /* 支付宝扫码支付弹窗 - DeepSeek 风格 */
+        .alipay-modal-overlay {
+            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
+            background: rgba(0,0,0,0.45); z-index: 1000;
+            display: flex; align-items: center; justify-content: center;
+            backdrop-filter: blur(2px);
+        }
+        .alipay-modal {
+            background: #fff; border-radius: 16px; width: 360px; max-width: 92vw;
+            padding: 28px 24px 22px; box-shadow: 0 20px 60px rgba(0,0,0,0.18);
+            text-align: center; position: relative; animation: modalIn 0.2s ease-out;
+        }
+        @keyframes modalIn {
+            from { opacity: 0; transform: scale(0.95) translateY(10px); }
+            to { opacity: 1; transform: scale(1) translateY(0); }
+        }
+        .alipay-modal-close {
+            position: absolute; top: 14px; right: 16px; width: 28px; height: 28px;
+            border: none; background: transparent; color: #999; font-size: 22px; line-height: 1;
+            cursor: pointer; border-radius: 50%; transition: all 0.2s;
+        }
+        .alipay-modal-close:hover { background: #f5f5f5; color: #333; }
+        .alipay-modal-logo {
+            display: flex; align-items: center; justify-content: center; gap: 6px;
+            margin-bottom: 16px; font-size: 18px; font-weight: 700; color: #1677ff;
+        }
+        .alipay-modal-logo svg { width: 26px; height: 26px; }
+        .alipay-modal-title { font-size: 15px; color: #666; margin-bottom: 6px; }
+        .alipay-modal-amount { font-size: 32px; font-weight: 700; color: #111; margin-bottom: 18px; }
+        .alipay-modal-amount span { font-size: 18px; margin-right: 2px; }
+        .alipay-modal-qr {
+            width: 200px; height: 200px; margin: 0 auto 14px;
+            border: 1px solid #f0f0f0; border-radius: 8px; padding: 8px; background: #fff;
+        }
+        .alipay-modal-qr img { width: 100%; height: 100%; object-fit: contain; }
+        .alipay-modal-hint {
+            font-size: 12px; color: #999; line-height: 1.6; margin-top: 8px;
+        }
+        .alipay-modal-status {
+            margin-top: 10px; font-size: 13px; color: #1677ff; min-height: 20px;
+        }
     </style>
     </style>
 </head>
 </head>
 <body>
 <body>
@@ -296,36 +338,36 @@ async function runTest(sectionId, method, path, body, requireAuth, validatorName
     if (durEl) durEl.textContent = result.duration + 'ms';
     if (durEl) durEl.textContent = result.duration + 'ms';
     if (rawEl) rawEl.textContent = JSON.stringify(result.data, null, 2);
     if (rawEl) rawEl.textContent = JSON.stringify(result.data, null, 2);
 
 
-    // 检测响应中的二维码并渲染图片预览
+    // 检测响应中的支付结果并渲染
     const qrBase64 = result.data?.data?.qrCodeBase64;
     const qrBase64 = result.data?.data?.qrCodeBase64;
     const payUrl = result.data?.data?.payUrl;
     const payUrl = result.data?.data?.payUrl;
     const channel = result.data?.data?.channel;
     const channel = result.data?.data?.channel;
-    let qrBox = document.getElementById(sectionId + '-qrcode');
-    if (qrBase64) {
+    const orderNo = result.data?.data?.orderNo;
+    const amount = result.data?.data?.amount;
+
+    // 自动回填订单号到相关输入框
+    if (orderNo) {
+        ['payment-query-order-orderNo', 'payment-mock-pay-orderNo',
+         'payment-callback-wechat-out_trade_no', 'payment-callback-alipay-out_trade_no']
+            .forEach(fid => { const el = document.getElementById(fid); if (el) el.value = orderNo; });
+    }
+
+    if (channel === 'ALIPAY' && qrBase64) {
+        // 支付宝:DeepSeek 风格自定义弹窗,展示二维码
+        showAlipayModal(qrBase64, amount, orderNo);
+    } else if (qrBase64) {
+        // 微信:显示扫码二维码
+        let qrBox = document.getElementById(sectionId + '-qrcode');
         if (!qrBox) {
         if (!qrBox) {
             qrBox = document.createElement('div');
             qrBox = document.createElement('div');
             qrBox.id = sectionId + '-qrcode';
             qrBox.id = sectionId + '-qrcode';
             qrBox.style.cssText = 'text-align:center;padding:12px;background:#fff;border-radius:4px;margin-top:8px;';
             qrBox.style.cssText = 'text-align:center;padding:12px;background:#fff;border-radius:4px;margin-top:8px;';
             rawEl.parentNode.insertBefore(qrBox, rawEl.nextSibling);
             rawEl.parentNode.insertBefore(qrBox, rawEl.nextSibling);
         }
         }
-        // 支付宝电脑网站支付:payUrl是跳转链接/表单,显示跳转按钮+二维码
-        if (channel === 'ALIPAY' && payUrl) {
-            qrBox.innerHTML = '<img src="' + qrBase64 + '" style="max-width:180px;border:1px solid #eee;border-radius:4px;opacity:0.5;">'
-                + '<div style="margin-top:8px;"><a href="' + payUrl + '" target="_blank" style="display:inline-block;padding:8px 20px;background:#1677ff;color:#fff;border-radius:4px;text-decoration:none;font-size:14px;">点击跳转支付宝收银台</a></div>'
-                + '<div style="margin-top:6px;font-size:12px;color:#999;">支付宝电脑网站支付(页面跳转模式,也可手动确认支付测试回调)</div>';
-        } else {
-            qrBox.innerHTML = '<img src="' + qrBase64 + '" style="max-width:220px;border:1px solid #eee;border-radius:4px;"><div style="margin-top:6px;font-size:12px;color:#666;">支付二维码(扫码付款后回调自动激活,也可手动确认支付测试回调)</div>';
-        }
-
-        // 自动回填订单号到相关输入框
-        const orderNo = result.data?.data?.orderNo;
-        if (orderNo) {
-            ['payment-query-order-orderNo', 'payment-mock-pay-orderNo',
-             'payment-callback-wechat-out_trade_no', 'payment-callback-alipay-out_trade_no']
-                .forEach(fid => { const el = document.getElementById(fid); if (el) el.value = orderNo; });
-        }
-    } else if (qrBox) {
-        qrBox.remove();
+        qrBox.innerHTML = '<img src="' + qrBase64 + '" style="max-width:220px;border:1px solid #eee;border-radius:4px;"><div style="margin-top:6px;font-size:12px;color:#666;">微信扫码付款后回调自动激活,也可手动确认支付测试回调</div>';
+    } else {
+        const oldBox = document.getElementById(sectionId + '-qrcode');
+        if (oldBox) oldBox.remove();
     }
     }
 
 
     let passed = false;
     let passed = false;
@@ -1186,7 +1228,7 @@ function renderPaymentModule() {
     ${testSection('payment-plans', '支付方案列表', 'GET', 'get', '/api/payment/plans', '公开', 'auth-public', null, true, null, '获取所有可用的支付方案列表(高级会员月卡/年卡)')}
     ${testSection('payment-plans', '支付方案列表', 'GET', 'get', '/api/payment/plans', '公开', 'auth-public', null, true, null, '获取所有可用的支付方案列表(高级会员月卡/年卡)')}
     ${testSection('payment-create-order', '创建支付订单', 'POST', 'post', '/api/payment/order/create', '需认证', 'auth-required',
     ${testSection('payment-create-order', '创建支付订单', 'POST', 'post', '/api/payment/order/create', '需认证', 'auth-required',
         [{label:'方案ID(planId)', name:'planId', placeholder:'1', default:'1'},
         [{label:'方案ID(planId)', name:'planId', placeholder:'1', default:'1'},
-         {label:'支付渠道(channel)', name:'channel', placeholder:'WECHAT/ALIPAY', default:'WECHAT'}], true, null, '创建支付订单并返回二维码(qrCodeBase64)和支付链接(payUrl),15分钟内未付款订单自动过期。mock模式返回模拟二维码,真实支付模式返回真实支付链接,也可点击「手动确认支付」按钮测试回调流程')}
+         {label:'支付渠道(channel)', name:'channel', placeholder:'WECHAT/ALIPAY', default:'WECHAT'}], true, null, '创建支付订单,微信返回二维码(qrCodeBase64)供扫码支付,支付宝通过alipay.trade.precreate返回qr_code,前端自定义弹窗展示二维码并自动轮询支付状态。15分钟内未付款订单自动过期')}
     ${testSection('payment-query-order', '查询订单', 'GET', 'get', '/api/payment/order/{orderNo}', '需认证', 'auth-required',
     ${testSection('payment-query-order', '查询订单', 'GET', 'get', '/api/payment/order/{orderNo}', '需认证', 'auth-required',
         [{label:'订单号(orderNo)', name:'orderNo', placeholder:'OP...', default:''}], true, null, '根据订单号查询支付订单详情,响应中expireHint含过期提示')}
         [{label:'订单号(orderNo)', name:'orderNo', placeholder:'OP...', default:''}], true, null, '根据订单号查询支付订单详情,响应中expireHint含过期提示')}
     ${testSection('payment-orders', '我的订单列表', 'GET', 'get', '/api/payment/orders', '需认证', 'auth-required', null, true, null, '查询当前用户的所有支付订单,待支付订单含expireHint过期提示')}
     ${testSection('payment-orders', '我的订单列表', 'GET', 'get', '/api/payment/orders', '需认证', 'auth-required', null, true, null, '查询当前用户的所有支付订单,待支付订单含expireHint过期提示')}
@@ -1288,6 +1330,7 @@ function renderSearchRecordModule() {
     </div>
     </div>
     ${testSection('sr-save', '保存搜索记录', 'POST', 'post', '/api/search-record/history', '需认证', 'auth-required',
     ${testSection('sr-save', '保存搜索记录', 'POST', 'post', '/api/search-record/history', '需认证', 'auth-required',
         [{label:'药品名称(drugName)', name:'drugName', placeholder:'阿莫西林', default:'阿莫西林'},
         [{label:'药品名称(drugName)', name:'drugName', placeholder:'阿莫西林', default:'阿莫西林'},
+         {label:'搜索时间(searchedAt)', name:'searchedAt', placeholder:'2026-06-26T08:30:00.000Z', default:'2026-06-26T08:30:00.000Z'},
          {label:'状态文案(statusText)', name:'statusText', placeholder:'已返回 2 个平台的可比价结果。', default:'已返回 2 个平台的可比价结果。'},
          {label:'状态文案(statusText)', name:'statusText', placeholder:'已返回 2 个平台的可比价结果。', default:'已返回 2 个平台的可比价结果。'},
          {label:'平台结果(platformResults)', name:'platformResults', placeholder:'[{"platformId":"yiyaocheng","platformName":"壹药城"}]', default:'[{"platformId":"yiyaocheng","platformName":"壹药城","response":{"status":"ok","products":[]}}]', type:'json'},
          {label:'平台结果(platformResults)', name:'platformResults', placeholder:'[{"platformId":"yiyaocheng","platformName":"壹药城"}]', default:'[{"platformId":"yiyaocheng","platformName":"壹药城","response":{"status":"ok","products":[]}}]', type:'json'},
          {label:'搜索日志(searchLog)', name:'searchLog', placeholder:'null', default:'null', type:'json'}], true, null, '保存一次完整的比价搜索快照,包含平台响应数据。platformResults和searchLog支持任意JSON结构')}
          {label:'搜索日志(searchLog)', name:'searchLog', placeholder:'null', default:'null', type:'json'}], true, null, '保存一次完整的比价搜索快照,包含平台响应数据。platformResults和searchLog支持任意JSON结构')}
@@ -1546,11 +1589,78 @@ function resetStats() {
 }
 }
 
 
 function clearAllResults() {
 function clearAllResults() {
+    stopAlipayPolling();
+    closeAlipayModal();
     document.querySelectorAll('.response-box').forEach(b => b.style.display = 'none');
     document.querySelectorAll('.response-box').forEach(b => b.style.display = 'none');
     document.querySelectorAll('.status-badge').forEach(b => { b.textContent = ''; b.className = 'status-badge'; });
     document.querySelectorAll('.status-badge').forEach(b => { b.textContent = ''; b.className = 'status-badge'; });
     resetStats();
     resetStats();
 }
 }
 
 
+// ===================== 支付宝支付弹窗 =====================
+let currentAlipayModal = null;
+let currentAlipayOrderNo = null;
+function showAlipayModal(qrBase64, amount, orderNo) {
+    closeAlipayModal();
+    currentAlipayOrderNo = orderNo;
+    const overlay = document.createElement('div');
+    overlay.className = 'alipay-modal-overlay';
+    overlay.id = 'alipay-modal-overlay';
+    overlay.onclick = (e) => { if (e.target === overlay) closeAlipayModal(); };
+    const amountText = amount ? '<span>¥</span>' + amount : '';
+    overlay.innerHTML = '<div class="alipay-modal">'
+        + '<button class="alipay-modal-close" onclick="closeAlipayModal()" title="关闭">×</button>'
+        + '<div class="alipay-modal-logo">'
+        + '<svg viewBox="0 0 1024 1024"><path fill="#1677ff" d="M876.8 128H147.2C114.9 128 89 154.4 89 186.7v650.6c0 32.3 25.9 58.7 58.2 58.7h729.6c32.3 0 58.2-26.4 58.2-58.7V186.7c.6-32.3-25.3-58.7-57.2-58.7zM512 714.7c-112 0-202.7-91.2-202.7-203.5 0-44.8 14.4-86.4 39.5-120.3C320 416 288 416 256 416V352c53.3 0 101.3-10.7 144-32 35.2-17.6 66.7-41.6 92.8-70.4 27.2-30.4 48-65.6 61.9-104h140.8v64h-64c8.5 30.9 13.3 63.5 13.3 97.1 0 48-9.6 92.8-27.7 133.3C690.7 464 729.6 480 768 480v64c-53.3 0-101.3-10.7-144-32-19.2 34.1-47.5 62.9-81.6 83.2 74.7 21.3 153.6 26.7 234.7 16v64c-93.9 10.7-185.6 3.2-270.9-26.7C464 693.3 432 704 400 704v-64c32 0 64-2.1 94.9-6.4C458.7 604.8 437.3 558.9 437.3 509.3c0-93.9 76.8-170.1 171.5-170.1 10.7 0 21.3 1.1 31.5 3.2-4.8-25.1-7.5-51.2-7.5-78.4 0-9.6.5-19.2 1.6-28.3-43.2 21.3-92.8 34.1-145.6 36.8V192c85.3 6.4 162.1 41.6 220.3 96 29.9-5.9 61.3-9.1 93.3-9.1V341.3c-26.7 0-52.8 2.7-77.9 7.5C755.2 384 768 416 768 448v32h-64v-32c0-53.3-43.7-96.5-97.1-96.5-42.7 0-78.9 27.7-92.3 65.6 36.3 15.5 61.9 51.7 61.9 94.1 0 56.5-46.1 102.4-102.9 102.4S370.7 565.3 370.7 509.3c0-71.5 61.3-129.6 136.5-129.6 4.3 0 8.5.5 12.8 1.1-8-29.9-12.3-61.3-12.3-93.9 0-4.3.5-8.5.5-12.8-52.3 4.8-100.3 22.4-140.3 49.6-34.1 23.5-62.4 53.3-83.2 88-18.7 31.5-29.3 68.3-29.3 107.2 0 112 90.7 203.5 202.7 203.5 62.9 0 118.9-28.8 156.3-73.6l48 42.7C660.5 677.3 590.9 714.7 512 714.7z"/></svg>'
+        + '支付宝'
+        + '</div>'
+        + '<div class="alipay-modal-title">扫一扫付款</div>'
+        + '<div class="alipay-modal-amount">' + amountText + '</div>'
+        + '<div class="alipay-modal-qr"><img src="' + qrBase64 + '" alt="支付宝付款码"></div>'
+        + '<div class="alipay-modal-status" id="alipay-modal-status">轮询支付状态中...</div>'
+        + '<div class="alipay-modal-hint">二维码有效期 15 分钟<br>请尽快完成付款</div>'
+        + '</div>';
+    document.body.appendChild(overlay);
+    currentAlipayModal = overlay;
+    if (orderNo) startAlipayPolling(orderNo);
+}
+function closeAlipayModal() {
+    if (currentAlipayModal) {
+        currentAlipayModal.remove();
+        currentAlipayModal = null;
+        currentAlipayOrderNo = null;
+    }
+}
+
+// ===================== 支付宝支付轮询 =====================
+let alipayPollTimer = null;
+function startAlipayPolling(orderNo) {
+    stopAlipayPolling();
+    const statusEl = document.getElementById('alipay-modal-status');
+    if (statusEl) statusEl.textContent = '轮询支付状态中...';
+    alipayPollTimer = setInterval(async () => {
+        try {
+            const res = await api('GET', '/api/payment/order/' + orderNo, null, true);
+            const status = res.data?.data?.status;
+            const el = document.getElementById('alipay-modal-status');
+            if (!el || !currentAlipayModal) { stopAlipayPolling(); return; }
+            if (status === 'PAID') {
+                stopAlipayPolling();
+                el.innerHTML = '<span style="color:#2e7d32;font-weight:600;">✓ 支付成功!订单已激活</span>';
+            } else if (status === 'EXPIRED') {
+                stopAlipayPolling();
+                el.innerHTML = '<span style="color:#c62828;">订单已过期</span>';
+            } else {
+                el.textContent = '轮询支付状态中... (当前: ' + (status || '未知') + ')';
+            }
+        } catch (e) {
+            console.error('轮询支付状态异常:', e);
+        }
+    }, 3000);
+}
+function stopAlipayPolling() {
+    if (alipayPollTimer) { clearInterval(alipayPollTimer); alipayPollTimer = null; }
+}
+
 // 用户侧快捷批量运行
 // 用户侧快捷批量运行
 function runAuthTests() { runBatchTests('auth'); }
 function runAuthTests() { runBatchTests('auth'); }
 function runCrawlerTests() { runBatchTests('crawler'); }
 function runCrawlerTests() { runBatchTests('crawler'); }

+ 4 - 5
zhijiayun-user/src/main/java/com/xuekairui/user/dto/PlatformSessionResponse.java

@@ -5,7 +5,6 @@ import lombok.Builder;
 import lombok.NoArgsConstructor;
 import lombok.NoArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 
 
-import java.time.LocalDateTime;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
@@ -32,11 +31,11 @@ public class PlatformSessionResponse {
     /** 账号标签 */
     /** 账号标签 */
     private String accountLabel;
     private String accountLabel;
 
 
-    /** 绑定元数据更新时间 */
-    private LocalDateTime updatedAt;
+    /** 绑定元数据更新时间(ISO-8601 UTC 字符串,如 2026-06-26T08:00:00.000Z) */
+    private String updatedAt;
 
 
-    /** Cookie 捕获时间 */
-    private LocalDateTime capturedAt;
+    /** Cookie 捕获时间(ISO-8601 UTC 字符串,如 2026-06-26T08:00:00.000Z) */
+    private String capturedAt;
 
 
     /** Cookie 列表(已解密) */
     /** Cookie 列表(已解密) */
     private List<Object> cookies;
     private List<Object> cookies;

+ 4 - 5
zhijiayun-user/src/main/java/com/xuekairui/user/dto/PlatformSessionSaveRequest.java

@@ -3,7 +3,6 @@ package com.xuekairui.user.dto;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotBlank;
 import lombok.Data;
 import lombok.Data;
 
 
-import java.time.LocalDateTime;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
@@ -28,11 +27,11 @@ public class PlatformSessionSaveRequest {
     /** 账号标签 */
     /** 账号标签 */
     private String accountLabel;
     private String accountLabel;
 
 
-    /** 绑定元数据更新时间 */
-    private LocalDateTime updatedAt;
+    /** 绑定元数据更新时间(ISO-8601 UTC 字符串,如 2026-06-26T08:00:00.000Z) */
+    private String updatedAt;
 
 
-    /** Cookie 捕获时间 */
-    private LocalDateTime capturedAt;
+    /** Cookie 捕获时间(ISO-8601 UTC 字符串,如 2026-06-26T08:00:00.000Z) */
+    private String capturedAt;
 
 
     /** Cookie 列表(Electron Cookie JSON) */
     /** Cookie 列表(Electron Cookie JSON) */
     private List<Object> cookies;
     private List<Object> cookies;

+ 3 - 0
zhijiayun-user/src/main/java/com/xuekairui/user/dto/SearchRecordSaveRequest.java

@@ -11,6 +11,9 @@ import java.time.LocalDateTime;
 @Data
 @Data
 public class SearchRecordSaveRequest {
 public class SearchRecordSaveRequest {
 
 
+    /** 记录ID(可选,后端采用雪花算法自生成 Long 类型ID) */
+    private String id;
+
     /** 药品名称 */
     /** 药品名称 */
     @NotBlank(message = "药品名称不能为空")
     @NotBlank(message = "药品名称不能为空")
     private String drugName;
     private String drugName;

+ 13 - 16
zhijiayun-user/src/main/java/com/xuekairui/user/service/PlatformSessionService.java

@@ -3,6 +3,7 @@ package com.xuekairui.user.service;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.xuekairui.common.ParseUtil;
 import com.xuekairui.user.dto.PlatformSessionResponse;
 import com.xuekairui.user.dto.PlatformSessionResponse;
 import com.xuekairui.user.dto.PlatformSessionSaveRequest;
 import com.xuekairui.user.dto.PlatformSessionSaveRequest;
 import com.xuekairui.user.dto.PlatformSessionUpdateResponse;
 import com.xuekairui.user.dto.PlatformSessionUpdateResponse;
@@ -32,11 +33,6 @@ public class PlatformSessionService {
     private final EncryptionService encryptionService;
     private final EncryptionService encryptionService;
     private final ObjectMapper objectMapper;
     private final ObjectMapper objectMapper;
 
 
-    /** 支持的平台代码 */
-    private static final Set<String> SUPPORTED_PLATFORMS = Set.of(
-            "yiyaocheng", "yaoshibang", "yaoyigou", "yaojiujiu", "ybm"
-    );
-
     /**
     /**
      * 上传或覆盖平台会话
      * 上传或覆盖平台会话
      */
      */
@@ -52,7 +48,7 @@ public class PlatformSessionService {
         // 加密 Cookie
         // 加密 Cookie
         String cookiesJson = toJsonString(request.getCookies());
         String cookiesJson = toJsonString(request.getCookies());
         String encryptedCookies = null;
         String encryptedCookies = null;
-        if (cookiesJson != null && !cookiesJson.equals("null")) {
+        if (cookiesJson != null && !"null".equals(cookiesJson)) {
             encryptedCookies = encryptionService.encrypt(cookiesJson);
             encryptedCookies = encryptionService.encrypt(cookiesJson);
         }
         }
 
 
@@ -61,14 +57,16 @@ public class PlatformSessionService {
 
 
         // 查找是否已有该平台的会话
         // 查找是否已有该平台的会话
         PlatformSession existing = platformSessionMapper.findByUserAndPlatform(userId, platformCode);
         PlatformSession existing = platformSessionMapper.findByUserAndPlatform(userId, platformCode);
+        LocalDateTime updatedAt = ParseUtil.parseIsoUtc(request.getUpdatedAt(), null);
+        LocalDateTime finalUpdatedAt = updatedAt != null ? updatedAt : LocalDateTime.now();
 
 
         if (existing != null) {
         if (existing != null) {
             // 更新
             // 更新
             existing.setPlatformName(request.getPlatformName());
             existing.setPlatformName(request.getPlatformName());
             existing.setLoginUrl(request.getLoginUrl());
             existing.setLoginUrl(request.getLoginUrl());
             existing.setAccountLabel(request.getAccountLabel());
             existing.setAccountLabel(request.getAccountLabel());
-            existing.setCapturedAt(request.getCapturedAt());
-            existing.setUpdatedAt(request.getUpdatedAt() != null ? request.getUpdatedAt() : LocalDateTime.now());
+            existing.setCapturedAt(ParseUtil.parseIsoUtc(request.getCapturedAt(), null));
+            existing.setUpdatedAt(finalUpdatedAt);
             existing.setCookiesEncrypted(encryptedCookies);
             existing.setCookiesEncrypted(encryptedCookies);
             existing.setVersion(request.getVersion() != null ? request.getVersion() : 1);
             existing.setVersion(request.getVersion() != null ? request.getVersion() : 1);
             existing.setExpiresAt(expiresAt);
             existing.setExpiresAt(expiresAt);
@@ -82,8 +80,8 @@ public class PlatformSessionService {
                     .platformName(request.getPlatformName())
                     .platformName(request.getPlatformName())
                     .loginUrl(request.getLoginUrl())
                     .loginUrl(request.getLoginUrl())
                     .accountLabel(request.getAccountLabel())
                     .accountLabel(request.getAccountLabel())
-                    .capturedAt(request.getCapturedAt())
-                    .updatedAt(request.getUpdatedAt() != null ? request.getUpdatedAt() : LocalDateTime.now())
+                    .capturedAt(ParseUtil.parseIsoUtc(request.getCapturedAt(), null))
+                    .updatedAt(finalUpdatedAt)
                     .cookiesEncrypted(encryptedCookies)
                     .cookiesEncrypted(encryptedCookies)
                     .version(request.getVersion() != null ? request.getVersion() : 1)
                     .version(request.getVersion() != null ? request.getVersion() : 1)
                     .expiresAt(expiresAt)
                     .expiresAt(expiresAt)
@@ -94,7 +92,7 @@ public class PlatformSessionService {
 
 
         return PlatformSessionUpdateResponse.builder()
         return PlatformSessionUpdateResponse.builder()
                 .platformCode(platformCode)
                 .platformCode(platformCode)
-                .updatedAt(request.getUpdatedAt() != null ? request.getUpdatedAt() : LocalDateTime.now())
+                .updatedAt(finalUpdatedAt)
                 .build();
                 .build();
     }
     }
 
 
@@ -126,7 +124,8 @@ public class PlatformSessionService {
         if (session.getCookiesEncrypted() != null) {
         if (session.getCookiesEncrypted() != null) {
             try {
             try {
                 String decrypted = encryptionService.decrypt(session.getCookiesEncrypted());
                 String decrypted = encryptionService.decrypt(session.getCookiesEncrypted());
-                cookies = objectMapper.readValue(decrypted, new TypeReference<List<Object>>() {});
+                cookies = objectMapper.readValue(decrypted, new TypeReference<>() {
+                });
             } catch (Exception e) {
             } catch (Exception e) {
                 log.error("Cookie解密失败: userId={}, platform={}", session.getUserId(), session.getPlatformCode());
                 log.error("Cookie解密失败: userId={}, platform={}", session.getUserId(), session.getPlatformCode());
                 return null; // 跳过解密失败的记录
                 return null; // 跳过解密失败的记录
@@ -139,8 +138,8 @@ public class PlatformSessionService {
                 .platformName(session.getPlatformName())
                 .platformName(session.getPlatformName())
                 .loginUrl(session.getLoginUrl())
                 .loginUrl(session.getLoginUrl())
                 .accountLabel(session.getAccountLabel())
                 .accountLabel(session.getAccountLabel())
-                .updatedAt(session.getUpdatedAt())
-                .capturedAt(session.getCapturedAt())
+                .updatedAt(ParseUtil.formatIsoUtc(session.getUpdatedAt()))
+                .capturedAt(ParseUtil.formatIsoUtc(session.getCapturedAt()))
                 .cookies(cookies)
                 .cookies(cookies)
                 .build();
                 .build();
     }
     }
@@ -149,7 +148,6 @@ public class PlatformSessionService {
      * 从 Cookie 列表中提取最早的 expirationDate
      * 从 Cookie 列表中提取最早的 expirationDate
      */
      */
     private LocalDateTime extractEarliestExpiration(List<Object> cookies) {
     private LocalDateTime extractEarliestExpiration(List<Object> cookies) {
-        if (cookies == null || cookies.isEmpty()) return null;
         try {
         try {
             long earliest = Long.MAX_VALUE;
             long earliest = Long.MAX_VALUE;
             for (Object cookie : cookies) {
             for (Object cookie : cookies) {
@@ -173,7 +171,6 @@ public class PlatformSessionService {
     }
     }
 
 
     private String toJsonString(Object obj) {
     private String toJsonString(Object obj) {
-        if (obj == null) return null;
         try {
         try {
             return objectMapper.writeValueAsString(obj);
             return objectMapper.writeValueAsString(obj);
         } catch (JsonProcessingException e) {
         } catch (JsonProcessingException e) {

+ 0 - 2
zhijiayun-user/src/main/java/com/xuekairui/user/service/SearchRecordService.java

@@ -108,7 +108,6 @@ public class SearchRecordService {
      * 对象序列化为JSON字符串(存储用)
      * 对象序列化为JSON字符串(存储用)
      */
      */
     private String toJsonString(Object obj) {
     private String toJsonString(Object obj) {
-        if (obj == null) return null;
         try {
         try {
             return objectMapper.writeValueAsString(obj);
             return objectMapper.writeValueAsString(obj);
         } catch (JsonProcessingException e) {
         } catch (JsonProcessingException e) {
@@ -121,7 +120,6 @@ public class SearchRecordService {
      * JSON字符串反序列化为对象(返回用)
      * JSON字符串反序列化为对象(返回用)
      */
      */
     private Object parseJson(String json) {
     private Object parseJson(String json) {
-        if (json == null || json.isBlank()) return null;
         try {
         try {
             return objectMapper.readValue(json, Object.class);
             return objectMapper.readValue(json, Object.class);
         } catch (JsonProcessingException e) {
         } catch (JsonProcessingException e) {