完善商福通支付接口,修复证书加载问题;数据库扩展支付平台字段并更新支付信息保存逻辑;日志中间件异步记录日志

This commit is contained in:
2025-06-17 10:53:05 +08:00
parent 692106ae5c
commit a4e5fc2af5
19 changed files with 444 additions and 421 deletions

View File

@@ -10,6 +10,8 @@
### 长期
异步日志记录,统一收集
模型字段修改,特定枚举字段使用自定义类型代替通用 int32
更新接口可以传输更结构化的数据,直接区分不同类型以加快更新速度

4
go.mod
View File

@@ -8,7 +8,6 @@ require (
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.26.0
github.com/go-redsync/redsync v1.4.2
github.com/go-redsync/redsync/v4 v4.13.0
github.com/gofiber/fiber/v2 v2.52.6
github.com/google/uuid v1.6.0
@@ -43,7 +42,6 @@ require (
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-redis/redis v6.15.9+incompatible // indirect
github.com/go-sql-driver/mysql v1.9.1 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
@@ -65,8 +63,6 @@ require (
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.37.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/smartwalle/ncrypto v1.0.4 // indirect

37
go.sum
View File

@@ -77,9 +77,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -96,14 +93,11 @@ github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOr
github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redsync/redsync v1.4.2 h1:KADEZ2rlaHMZWnlkthQCxfGP+8ZWwJLiSjOYN3mntKA=
github.com/go-redsync/redsync v1.4.2/go.mod h1:my8/M5YL986u2jBMtZTLkBIgBsKNNSixJWzWwISH6Uw=
github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA=
github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
@@ -140,12 +134,10 @@ github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORR
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -200,17 +192,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -267,7 +248,6 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
@@ -292,7 +272,6 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -302,16 +281,13 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -332,7 +308,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@@ -341,17 +316,12 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -401,7 +371,6 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
@@ -411,7 +380,6 @@ golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -430,16 +398,11 @@ google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwl
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

8
pkg/env/env.go vendored
View File

@@ -352,6 +352,7 @@ func loadAliyun() {
var (
SftPayEnable = false
SftPayAppId string
SftPayRouteId string
SftPayAppPrivateKey string
SftPayPublicKey string
)
@@ -375,6 +376,11 @@ func loadSftPay() {
SftPayAppId = value
}
value = os.Getenv("SFTPAY_ROUTE_ID")
if value != "" {
SftPayRouteId = value
}
value = os.Getenv("SFTPAY_APP_PRIVATE_KEY")
if value == "" {
panic("环境变量 SFTPAY_APP_PRIVATE_KEY 的值不能为空")
@@ -442,5 +448,5 @@ func Init() {
loadAlipay()
loadWechatPay()
loadAliyun()
// loadSftPay()
loadSftPay()
}

View File

@@ -805,7 +805,8 @@ create table trade (
amount decimal(12, 2) not null default 0,
payment decimal(12, 2) not null default 0,
method int not null,
acquirer int not null,
platform int not null,
acquirer int ,
status int not null default 0,
pay_url text,
paid_at timestamp,
@@ -832,6 +833,7 @@ comment on column trade.remark is '订单备注';
comment on column trade.amount is '订单总金额';
comment on column trade.payment is '支付金额';
comment on column trade.method is '支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信';
comment on column trade.platform is '支付平台1-电脑网站2-手机网站';
comment on column trade.acquirer is '收单机构1-支付宝2-微信3-银联';
comment on column trade.status is '订单状态0-待支付1-已支付2-已取消3-已退款';
comment on column trade.pay_url is '支付链接';

View File

@@ -49,7 +49,7 @@ type PageResp struct {
// region error
type BizErr struct {
type Err struct {
msg string
err error
@@ -58,18 +58,18 @@ type BizErr struct {
errFunc string
}
func (e *BizErr) Error() string {
func (e *Err) Error() string {
if e.err != nil {
return e.msg + "" + e.err.Error()
}
return e.msg
}
func (e *BizErr) Unwrap() error {
func (e *Err) Unwrap() error {
return e.err
}
func (e *BizErr) Source() *slog.Source {
func (e *Err) Source() *slog.Source {
return &slog.Source{
Function: e.errFunc,
File: e.errFile,
@@ -77,25 +77,37 @@ func (e *BizErr) Source() *slog.Source {
}
}
func NewBizErr(msg string, err ...error) (biz *BizErr) {
biz = &BizErr{
func newErr(msg string, err ...error) Err {
o := Err{
msg: msg,
}
if len(err) > 0 {
biz.err = err[0]
o.err = err[0]
}
if env.RunMode == env.RunModeDev {
pc, file, line, ok := runtime.Caller(1)
pc, file, line, ok := runtime.Caller(2)
if ok {
biz.errFile = file
biz.errLine = line
biz.errFunc = runtime.FuncForPC(pc).Name()
o.errFile = file
o.errLine = line
o.errFunc = runtime.FuncForPC(pc).Name()
}
}
return biz
return o
}
type BizErr struct{ Err }
func NewBizErr(msg string, err ...error) (biz *BizErr) {
return &BizErr{newErr(msg, err...)}
}
type ServErr struct{ Err }
func NewServErr(msg string, err ...error) *ServErr {
return &ServErr{newErr(msg, err...)}
}
// endregion

View File

@@ -17,6 +17,13 @@ const (
MethodSftWeChat // 商福通渠道指定微信
)
type Platform int32
const (
PlatformDesktop Platform = iota + 1 // 桌面网站
PlatformMobile // 手机网站
)
type Acquirer int32
const (

View File

@@ -46,7 +46,7 @@ func ErrorHandler(c *fiber.Ctx, err error) error {
// 所有未手动声明的错误类型
default:
slog.Debug("未处理的异常", slog.String("type", reflect.TypeOf(err).Name()), slog.String("error", err.Error()))
slog.Warn("未处理的异常", slog.String("type", reflect.TypeOf(err).Name()), slog.String("error", err.Error()))
}
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)

View File

@@ -6,7 +6,7 @@ import (
var Asynq *asynq.Client
func InitAsynq() {
func initAsynq() {
var client = asynq.NewClientFromRedisClient(Redis)
Asynq = client
}

View File

@@ -9,5 +9,6 @@ func Init() {
initRedis()
initOrm()
initProxy()
InitAsynq()
initAsynq()
initSft()
}

View File

@@ -6,13 +6,15 @@ import (
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"net/http"
"net/http/httputil"
"platform/pkg/env"
"platform/pkg/u"
"platform/web/core"
"strings"
"time"
)
@@ -21,29 +23,31 @@ var SFTPay SftClient
type SftClient struct {
appid string
routeId string
privateKey *rsa.PrivateKey
publicKey *rsa.PublicKey
}
func init() {
func initSft() {
if !env.SftPayEnable {
return
panic("商福通支付未启用,请检查环境变量 SFTPAY_ENABLE")
}
SFTPay = SftClient{
appid: env.SftPayAppId,
appid: env.SftPayAppId,
routeId: env.SftPayRouteId,
}
// 加载私钥
block, _ := pem.Decode([]byte(env.SftPayAppPrivateKey))
if block == nil || block.Type != "RSA PRIVATE KEY" {
panic("加载商福通私钥失败")
private, err := base64.StdEncoding.DecodeString(env.SftPayAppPrivateKey)
if err != nil {
panic("解析商福通私钥失败: " + err.Error())
}
var privateKey *rsa.PrivateKey
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
privateKey, err = x509.ParsePKCS1PrivateKey(private)
if err != nil {
pkcs8, err := x509.ParsePKCS8PrivateKey(block.Bytes)
pkcs8, err := x509.ParsePKCS8PrivateKey(private)
if err != nil {
panic("解析商福通私钥失败: " + err.Error())
}
@@ -57,13 +61,13 @@ func init() {
SFTPay.privateKey = privateKey
// 加载公钥
block, _ = pem.Decode([]byte(env.SftPayPublicKey))
if block == nil || block.Type != "PUBLIC KEY" {
panic("加载商福通公钥失败")
public, err := base64.StdEncoding.DecodeString(env.SftPayPublicKey)
if err != nil {
panic("解析商福通公钥失败: " + err.Error())
}
var publicKey *rsa.PublicKey
pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
pkix, err := x509.ParsePKIXPublicKey(public)
if err != nil {
panic("解析商福通公钥失败: " + err.Error())
}
@@ -78,11 +82,13 @@ func init() {
func (s *SftClient) PaymentScanPay(req *PaymentScanPayReq) (*PaymentScanPayResp, error) {
const url = "https://pay.rscygroup.com/api/open/payment/scanpay"
req.RouteNo = u.P(s.routeId)
return call[PaymentScanPayResp](s, url, req)
}
func (s *SftClient) PaymentH5Pay(req *PaymentH5PayReq) (*PaymentH5PayResp, error) {
const url = "https://pay.rscygroup.com/api/open/payment/h5pay"
req.RouteNo = u.P(s.routeId)
return call[PaymentH5PayResp](s, url, req)
}
@@ -91,6 +97,11 @@ func (s *SftClient) OrderClose(req *OrderCloseReq) (*OrderCloseResp, error) {
return call[OrderCloseResp](s, url, req)
}
func (s *SftClient) QueryTrade(req *QueryTradeReq) (*QueryTradeResp, error) {
const url = "https://pay.rscygroup.com/api/open/query/trade"
return call[QueryTradeResp](s, url, req)
}
type PaymentScanPayReq struct {
Subject string `json:"subject"`
Body string `json:"body"`
@@ -132,6 +143,11 @@ type PaymentH5PayReq struct {
LimitPay *int `json:"limitPay"`
}
type QueryTradeReq struct {
PayOrderId *string `json:"payOrderId"`
MchOrderNo *string `json:"mchOrderNo"`
}
type OrderCloseReq struct {
MchOrderNo *string `json:"mchOrderNo"`
PayOrderId *string `json:"payOrderId"`
@@ -197,6 +213,29 @@ type PaymentH5PayResp struct {
SettlementType *string `json:"settlementType"`
}
type QueryTradeResp struct {
Amount int64 `json:"amount"`
ChannelSendNo *string `json:"channelSendNo"`
IfCode *string `json:"ifCode"`
MercNo string `json:"mercNo"`
MchOrderNo string `json:"mchOrderNo"`
PayOrderId *string `json:"payOrderId"`
PayType string `json:"payType"`
ChannelTradeNo *string `json:"channelTradeNo"`
State SftTradeState `json:"state"`
RefundAmt *int64 `json:"refundAmt"`
RefundState int32 `json:"refundState"`
DrType *string `json:"drType"`
ExtParam *string `json:"extParam"`
PayTime *string `json:"payTime"`
Subject string `json:"subject"`
TradeFee *int64 `json:"tradeFee"`
CashFee *int64 `json:"cashFee"`
StoreId *string `json:"storeId"`
UserId *string `json:"userId"`
SettlementType *string `json:"settlementType"`
}
type OrderCloseResp struct {
MchOrderNo string `json:"mchOrderNo"`
PayOrderId string `json:"payOrderId"`
@@ -224,19 +263,30 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
if err != nil {
return nil, fmt.Errorf("创建请求失败:%w", err)
}
request.Header.Set("Content-Type", "application/json")
reqDump, err := httputil.DumpRequest(request, true)
if err != nil {
return nil, fmt.Errorf("请求内容转储失败:%w", err)
}
println(string(reqDump) + "\n\n")
response, err := http.DefaultClient.Do(request)
if err != nil {
return nil, fmt.Errorf("请求失败:%w", err)
}
respDump, err := httputil.DumpResponse(response, true)
if err != nil {
return nil, fmt.Errorf("响应内容转储失败:%w", err)
}
println(string(respDump) + "\n\n")
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("请求响应失败:%d", response.StatusCode)
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
}
defer func(body io.ReadCloser) {
_ = body.Close()
}(response.Body)
body, err := io.ReadAll(response.Body)
@@ -248,15 +298,9 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
if err != nil {
return nil, fmt.Errorf("解密响应内容失败:%w", err)
}
if decode.Code != "000000" {
return nil, fmt.Errorf("请求业务响应失败:%s", u.Z(decode.Msg))
}
if decode.BizData == nil {
return nil, nil
}
var resp = new(T)
err = json.Unmarshal([]byte(*decode.BizData), resp)
err = json.Unmarshal([]byte(decode), resp)
if err != nil {
return nil, fmt.Errorf("响应正文解析失败:%w", err)
}
@@ -290,24 +334,34 @@ func (s *SftClient) sign(msg any) (*request, error) {
return &body, nil
}
func (s *SftClient) verify(str []byte) (*response, error) {
func (s *SftClient) verify(str []byte) (string, error) {
var resp = new(response)
err := json.Unmarshal(str, resp)
if err != nil {
return nil, fmt.Errorf("解析响应正文失败:%w", err)
return "", fmt.Errorf("解析响应正文失败:%w", err)
}
if resp.Sign != nil || resp.SignType != nil || resp.BizData != nil {
hashed := sha256.Sum256([]byte(resp.String()))
err := rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], []byte(*resp.Sign))
if err != nil {
return nil, fmt.Errorf("验签失败:%w", err)
}
if resp.Code != "000000" {
return "", fmt.Errorf("请求业务响应失败:%s", u.Z(resp.Msg))
}
return resp, err
if resp.Sign == nil {
return "", core.NewServErr("响应数据签名为空")
}
ser, err := resp.String()
if err != nil {
return "", fmt.Errorf("格式化响应内容失败:%w", err)
}
hashed := sha256.Sum256([]byte(ser))
err = rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], []byte(*resp.Sign))
if err != nil {
return "", fmt.Errorf("验签失败:%w", err)
}
return *resp.BizData, nil
}
type request struct {
@@ -336,11 +390,17 @@ type response struct {
Timestamp string `json:"timestamp"`
}
func (r response) String() string {
func (r response) String() (string, error) {
if r.BizData == nil || r.Msg == nil || r.SignType == nil {
return "", core.NewServErr(fmt.Sprintf(
"上游数据返回有空值BizData %vMsg %v, SignType %v",
r.BizData == nil, r.Msg == nil, r.SignType == nil,
))
}
return fmt.Sprintf(
"bizData=%s&code=%s&msg=%s&signType=%s&timestamp=%s",
u.Z(r.BizData), r.Code, u.Z(r.Msg), u.Z(r.SignType), r.Timestamp,
)
), nil
}
type SftPayType string
@@ -350,3 +410,16 @@ const (
SftWeChat SftPayType = "WECHAT"
SftUnionPay SftPayType = "UNIONPAY"
)
type SftTradeState string
const (
SftInit SftTradeState = "INIT"
SftTradeAWAIT SftTradeState = "TRADE_WAIT"
SftTradeSuccess SftTradeState = "TRADE_SUCCESS"
SftTradeFail SftTradeState = "TRADE_FAIL"
SftTradeCancel SftTradeState = "TRADE_CANCEL"
SftTradeRefund SftTradeState = "TRADE_REFUND"
SftRefundIng SftTradeState = "REFUND_ING"
SftTradeClosed SftTradeState = "TRADE_CLOSED"
)

View File

@@ -293,7 +293,7 @@ func PrepareCreateResource(c *fiber.Ctx) error {
}
// 准备创建套餐
result, err := s.Resource.PrepareResource(authCtx.Payload.Id, time.Now(), req.Method, &req.CreateResourceSerializer)
result, err := s.Resource.PrepareResource(authCtx.Payload.Id, time.Now(), &req.CreateResourceSerializer)
if err != nil {
return err
}

View File

@@ -1,8 +1,10 @@
package handlers
import (
"fmt"
"github.com/shopspring/decimal"
"platform/web/auth"
"platform/web/core"
trade2 "platform/web/domains/trade"
m "platform/web/models"
q "platform/web/queries"
@@ -143,7 +145,9 @@ func UpdatePassword(c *fiber.Ctx) error {
// region /recharge
type RechargePrepareReq struct {
Amount string `json:"amount" validate:"required,numeric"`
Amount string `json:"amount" validate:"required,numeric"`
Platform trade2.Platform `json:"platform" validate:"required"`
Method trade2.Method `json:"method" validate:"required"`
}
type RechargePrepareResp struct {
@@ -159,7 +163,6 @@ type RechargeConfirmResp struct {
Status string `json:"status"`
}
// RechargePrepareAlipay 通过支付宝充值
func RechargePrepareAlipay(c *fiber.Ctx) error {
// 检查权限
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
@@ -181,12 +184,13 @@ func RechargePrepareAlipay(c *fiber.Ctx) error {
}
var result *s.TradeCreateResult
err = q.Q.Transaction(func(tx *q.Query) error {
result, err = s.Trade.SendCreateTradeByQrcode(tx, authContext.Payload.Id, now, &s.TradeCreateData{
result, err = s.Trade.CreateTrade(tx, authContext.Payload.Id, now, &s.TradeCreateData{
Subject: "账户充值 - " + amount.StringFixed(2) + "元",
Amount: amount,
ExpireAt: time.Now().Add(30 * time.Minute),
Type: trade2.TypeRecharge,
Method: trade2.MethodAlipay,
Platform: req.Platform,
})
return err
})
@@ -215,7 +219,7 @@ func RechargeConfirmAlipay(c *fiber.Ctx) error {
}
// 验证支付结果
result, err := s.Trade.VerifyCreateTrade(&s.TradeVerifyData{
result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{
TradeNo: req.TradeNo,
Method: trade2.MethodAlipay,
})
@@ -253,12 +257,13 @@ func RechargePrepareWechat(c *fiber.Ctx) error {
}
var result *s.TradeCreateResult
err = q.Q.Transaction(func(tx *q.Query) error {
result, err = s.Trade.SendCreateTradeByQrcode(tx, authContext.Payload.Id, now, &s.TradeCreateData{
result, err = s.Trade.CreateTrade(tx, authContext.Payload.Id, now, &s.TradeCreateData{
Subject: "账户充值 - " + amount.StringFixed(2) + "元",
Amount: amount,
ExpireAt: now.Add(30 * time.Minute),
Type: trade2.TypeRecharge,
Method: trade2.MethodWeChat,
Platform: req.Platform,
})
return err
})
@@ -289,7 +294,82 @@ func RechargeConfirmWechat(c *fiber.Ctx) error {
}
// 验证支付结果
result, err := s.Trade.VerifyCreateTrade(&s.TradeVerifyData{
result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{
TradeNo: req.TradeNo,
Method: trade2.MethodWeChat,
})
if err != nil {
return err
}
// 更新数据库
err = s.User.RechargeConfirm(req.TradeNo, result)
if err != nil {
return err
}
return c.JSON(fiber.Map{"status": "success"})
}
func RechargePrepare(c *fiber.Ctx) error {
// 检查权限
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
if err != nil {
return err
}
// 解析请求参数
req := new(RechargePrepareReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 保存交易信息
var now = time.Now()
amount, err := decimal.NewFromString(req.Amount)
if err != nil {
return core.NewBizErr(fmt.Sprintf("金额格式错误: %s", err.Error()))
}
var result *s.TradeCreateResult
err = q.Q.Transaction(func(tx *q.Query) error {
result, err = s.Trade.CreateTrade(tx, authContext.Payload.Id, now, &s.TradeCreateData{
Subject: "账户充值 - " + amount.StringFixed(2) + "元",
Amount: amount,
ExpireAt: now.Add(30 * time.Minute),
Type: trade2.TypeRecharge,
Method: req.Method,
Platform: req.Platform,
})
return err
})
if err != nil {
return err
}
// 返回结果
return c.JSON(RechargePrepareResp{
TradeNo: result.TradeNo,
PayURL: result.PayURL,
})
}
func RechargeConfirm(c *fiber.Ctx) error {
// 检查权限
_, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
if err != nil {
return err
}
// 解析请求参数
req := new(struct {
TradeNo string `json:"trade_no" validate:"required"`
})
if err := c.BodyParser(req); err != nil {
return err
}
// 验证支付结果
result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{
TradeNo: req.TradeNo,
Method: trade2.MethodWeChat,
})

View File

@@ -24,7 +24,7 @@ type Trade struct {
Remark *string `gorm:"column:remark;type:character varying(255);comment:订单备注" json:"remark"` // 订单备注
Amount decimal.Decimal `gorm:"column:amount;type:numeric(12,2);not null;comment:订单总金额" json:"amount"` // 订单总金额
Payment decimal.Decimal `gorm:"column:payment;type:numeric(12,2);not null;comment:支付金额" json:"payment"` // 支付金额
Method int32 `gorm:"column:method;type:integer;not null;comment:支付方式1-支付宝2-微信3-商福通" json:"method"` // 支付方式1-支付宝2-微信3-商福通
Method int32 `gorm:"column:method;type:integer;not null;comment:支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信" json:"method"` // 支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信
Status int32 `gorm:"column:status;type:integer;not null;comment:订单状态0-待支付1-已支付2-已取消3-已退款" json:"status"` // 订单状态0-待支付1-已支付2-已取消3-已退款
PayURL *string `gorm:"column:pay_url;type:text;comment:支付链接" json:"pay_url"` // 支付链接
PaidAt *orm.LocalDateTime `gorm:"column:paid_at;type:timestamp without time zone;comment:支付时间" json:"paid_at"` // 支付时间
@@ -33,6 +33,7 @@ type Trade struct {
UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间
Acquirer int32 `gorm:"column:acquirer;type:integer;not null;comment:收单机构1-支付宝2-微信3-银联" json:"acquirer"` // 收单机构1-支付宝2-微信3-银联
Platform int32 `gorm:"column:platform;type:integer;not null;comment:支付平台1-电脑网站2-手机网站" json:"platform"` // 支付平台1-电脑网站2-手机网站
}
// TableName Trade's table name

View File

@@ -45,6 +45,7 @@ func newTrade(db *gorm.DB, opts ...gen.DOOption) trade {
_trade.UpdatedAt = field.NewField(tableName, "updated_at")
_trade.DeletedAt = field.NewField(tableName, "deleted_at")
_trade.Acquirer = field.NewInt32(tableName, "acquirer")
_trade.Platform = field.NewInt32(tableName, "platform")
_trade.fillFieldMap()
@@ -64,7 +65,7 @@ type trade struct {
Remark field.String // 订单备注
Amount field.Field // 订单总金额
Payment field.Field // 支付金额
Method field.Int32 // 支付方式1-支付宝2-微信3-商福通
Method field.Int32 // 支付方式1-支付宝2-微信3-商福通渠道支付宝4-商福通渠道微信
Status field.Int32 // 订单状态0-待支付1-已支付2-已取消3-已退款
PayURL field.String // 支付链接
PaidAt field.Field // 支付时间
@@ -73,6 +74,7 @@ type trade struct {
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
Acquirer field.Int32 // 收单机构1-支付宝2-微信3-银联
Platform field.Int32 // 支付平台1-电脑网站2-手机网站
fieldMap map[string]field.Expr
}
@@ -107,6 +109,7 @@ func (t *trade) updateTableName(table string) *trade {
t.UpdatedAt = field.NewField(table, "updated_at")
t.DeletedAt = field.NewField(table, "deleted_at")
t.Acquirer = field.NewInt32(table, "acquirer")
t.Platform = field.NewInt32(table, "platform")
t.fillFieldMap()
@@ -123,7 +126,7 @@ func (t *trade) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
}
func (t *trade) fillFieldMap() {
t.fieldMap = make(map[string]field.Expr, 18)
t.fieldMap = make(map[string]field.Expr, 19)
t.fieldMap["id"] = t.ID
t.fieldMap["user_id"] = t.UserID
t.fieldMap["inner_no"] = t.InnerNo
@@ -142,6 +145,7 @@ func (t *trade) fillFieldMap() {
t.fieldMap["updated_at"] = t.UpdatedAt
t.fieldMap["deleted_at"] = t.DeletedAt
t.fieldMap["acquirer"] = t.Acquirer
t.fieldMap["platform"] = t.Platform
}
func (t trade) clone(db *gorm.DB) trade {

View File

@@ -28,6 +28,8 @@ func ApplyRouters(app *fiber.App) {
user.Post("/recharge/confirm/alipay", handlers.RechargeConfirmAlipay)
user.Post("/recharge/prepare/wechat", handlers.RechargePrepareWechat)
user.Post("/recharge/confirm/wechat", handlers.RechargeConfirmWechat)
user.Post("/recharge/prepare", handlers.RechargePrepare)
user.Post("/recharge/confirm", handlers.RechargeConfirm)
// 白名单
whitelist := api.Group("/whitelist")

View File

@@ -87,7 +87,7 @@ func (s *resourceService) CreateResource(uid int32, now time.Time, ser *CreateRe
return nil
}
func (s *resourceService) PrepareResource(uid int32, now time.Time, method trade2.Method, ser *CreateResourceSerializer) (*TradeCreateResult, error) {
func (s *resourceService) PrepareResource(uid int32, now time.Time, ser *CreateResourceSerializer) (*TradeCreateResult, error) {
data, err := ser.ToData()
if err != nil {
@@ -97,18 +97,22 @@ func (s *resourceService) PrepareResource(uid int32, now time.Time, method trade
name := data.GetName()
amount := data.GetPrice()
method := ser.PaymentMethod
platform := ser.PaymentPlatform
// 保存到数据库
var result *TradeCreateResult
err = q.Q.Transaction(func(q *q.Query) error {
var err error
// 生成交易订单
result, err = Trade.SendCreateTradeByQrcode(q, uid, now, &TradeCreateData{
result, err = Trade.CreateTrade(q, uid, now, &TradeCreateData{
Subject: "购买套餐 - " + name,
Amount: amount,
ExpireAt: time.Now().Add(30 * time.Minute),
Type: trade2.TypeRecharge,
Method: method,
Platform: platform,
})
if err != nil {
return err
@@ -124,7 +128,6 @@ func (s *resourceService) PrepareResource(uid int32, now time.Time, method trade
Uid: uid,
TradeId: result.Trade.ID,
BillId: result.Bill.ID,
Method: method,
CreateResourceSerializer: resourceSerializer,
}, 30*time.Minute).Err()
if err != nil {
@@ -158,9 +161,9 @@ func (s *resourceService) CompleteResource(tradeNo string, now time.Time, opResu
rs = opResult[0]
} else {
var err error
rs, err = Trade.VerifyCreateTrade(&TradeVerifyData{
rs, err = Trade.VerifyTrade(&TradeVerifyData{
TradeNo: tradeNo,
Method: cache.Method,
Method: cache.PaymentMethod,
})
if err != nil {
return err
@@ -229,7 +232,7 @@ func (s *resourceService) CancelResource(tradeNo string, now time.Time, opRevoke
// 取消交易
if len(opRevoked) <= 0 {
err = Trade.CancelTrade(tradeNo, cache.Method)
err = Trade.CancelTrade(tradeNo, cache.PaymentMethod)
if err != nil {
return err
}
@@ -403,9 +406,11 @@ func (data *CreateLongResourceData) GetPrice() decimal.Decimal {
}
type CreateResourceSerializer struct {
Type resource2.Type `json:"type" validate:"required"`
Short *CreateShortResourceData `json:"short,omitempty"`
Long *CreateLongResourceData `json:"long,omitempty"`
Type resource2.Type `json:"type" validate:"required"`
Short *CreateShortResourceData `json:"short,omitempty"`
Long *CreateLongResourceData `json:"long,omitempty"`
PaymentMethod trade2.Method `json:"payment_method" validate:"required"`
PaymentPlatform trade2.Platform `json:"payment_platform" validate:"required"`
}
func (s *CreateResourceSerializer) ToData() (CreateResourceData, error) {
@@ -433,10 +438,9 @@ func (s *CreateResourceSerializer) ByData(data CreateResourceData) error {
}
type CreateResourceCache struct {
Uid int32 `json:"uid"`
TradeId int32 `json:"trade_id"`
BillId int32 `json:"bill_id"`
Method trade2.Method `json:"method"`
Uid int32 `json:"uid"`
TradeId int32 `json:"trade_id"`
BillId int32 `json:"bill_id"`
*CreateResourceSerializer
}

View File

@@ -9,6 +9,7 @@ import (
"net/http"
"platform/pkg/env"
"platform/pkg/u"
"platform/web/core"
bill2 "platform/web/domains/bill"
coupon2 "platform/web/domains/coupon"
trade2 "platform/web/domains/trade"
@@ -29,11 +30,12 @@ var Trade = &tradeService{}
type tradeService struct {
}
func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.Time, data *TradeCreateData) (*TradeCreateResult, error) {
func (s *tradeService) CreateTrade(q *q.Query, uid int32, now time.Time, data *TradeCreateData) (*TradeCreateResult, error) {
var subject = data.Subject
var expire = data.ExpireAt
var tType = data.Type
var method = data.Method
var platform = data.Platform
var amount = data.Amount
// 实际支付金额,只在创建真实订单时使用
@@ -103,11 +105,10 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
// 创建支付订单
var payUrl string
var acquirer trade2.Acquirer
switch method {
switch {
// 调用支付宝支付接口
case trade2.MethodAlipay:
// 支付宝 + 电脑网站
case method == trade2.MethodAlipay && platform == trade2.PlatformDesktop:
resp, err := g.Alipay.TradePagePay(alipay.TradePagePay{
QRPayMode: "4",
QRCodeWidth: "196", // 二维码宽度需要-4支付宝页面布局有问题
@@ -123,10 +124,9 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
return nil, err
}
payUrl = resp.String()
acquirer = trade2.AcquirerAlipay
// 调用微信支付接口
case trade2.MethodWeChat:
// 微信 + 电脑网站
case method == trade2.MethodWeChat && platform == trade2.PlatformDesktop:
resp, _, err := g.WechatPay.Native.Prepay(context.Background(), native.PrepayRequest{
Appid: &env.WechatPayAppId,
Mchid: &env.WechatPayMchId,
@@ -142,10 +142,9 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
return nil, err
}
payUrl = *resp.CodeUrl
acquirer = trade2.AcquirerWeChat
// 调用商福通接口
case trade2.MethodSft:
// 商福通 + 电脑网站
case method == trade2.MethodSft && platform == trade2.PlatformDesktop:
resp, err := g.SFTPay.PaymentScanPay(&g.PaymentScanPayReq{
MchOrderNo: tradeNo,
Subject: subject,
@@ -159,155 +158,9 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
return nil, err
}
payUrl = u.Z(u.Z(resp.PayInfo).QrCodeUrl)
if payUrl == "" {
return nil, errors.New("支付接口未返回正确的二维码地址")
}
switch resp.PayType {
case g.SftAlipay:
acquirer = trade2.AcquirerAlipay
case g.SftWeChat:
acquirer = trade2.AcquirerWeChat
case g.SftUnionPay:
acquirer = trade2.AcquirerUnionPay
}
// 不支持的支付方式
default:
return nil, ErrTransactionNotSupported
}
// 保存交易订单
var billType bill2.Type
switch tType {
case trade2.TypeRecharge:
billType = bill2.TypeRecharge
case trade2.TypePurchase:
billType = bill2.TypeConsume
}
var trade = m.Trade{
UserID: uid,
InnerNo: tradeNo,
Subject: subject,
Method: int32(method),
Type: int32(tType),
Amount: amount,
PayURL: &payUrl,
Acquirer: int32(acquirer),
}
err = q.Trade.Create(&trade)
if err != nil {
return nil, err
}
// 保存用户帐单
var bill = m.Bill{
BillNo: ID.GenReadable("bil"),
UserID: uid,
TradeID: &trade.ID,
Info: &subject,
Type: int32(billType),
Amount: amount,
}
err = q.Bill.
Omit(q.Bill.ResourceID, q.Bill.RefundID).
Create(&bill)
if err != nil {
return nil, err
}
// 提交异步任务更新订单状态
_, err = g.Asynq.Enqueue(tasks.NewUpdateTrade(tradeNo, method))
if err != nil {
return nil, err
}
return &TradeCreateResult{
TradeNo: tradeNo,
PayURL: payUrl,
Bill: &bill,
Trade: &trade,
}, nil
}
func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time.Time, data *TradeCreateData) (*TradeCreateResult, error) {
var subject = data.Subject
var expire = data.ExpireAt
var tType = data.Type
var method = data.Method
var amount = data.Amount
// 实际支付金额,只在创建真实订单时使用
var amountReal = data.Amount
if env.RunMode == "debug" {
amountReal = decimal.NewFromFloat(0.01)
}
// 附加优惠券
if data.CouponCode != "" {
coupon, err := q.Coupon.
Where(
q.Coupon.Code.Eq(data.CouponCode),
q.Coupon.Status.Eq(int32(coupon2.StatusUnused)),
).
Take()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("优惠券不存在或已失效")
}
return nil, err
}
var expireAt = time.Time(u.Z(coupon.ExpireAt))
if !expireAt.IsZero() && expireAt.Before(now) {
_, err = q.Coupon.
Where(q.Coupon.ID.Eq(coupon.ID)).
Update(q.Coupon.Status, coupon2.StatusExpired)
if err != nil {
return nil, err
}
return nil, errors.New("优惠券已过期")
}
if amount.Cmp(coupon.MinAmount) < 0 {
return nil, errors.New("订单金额未达到使用优惠券的条件")
}
if coupon.UserID != nil {
switch *coupon.UserID {
// 指定用户的优惠券
case uid:
amount = amount.Sub(coupon.Amount)
if expireAt.IsZero() {
_, err = q.Coupon.
Where(q.Coupon.ID.Eq(coupon.ID)).
Update(q.Coupon.Status, int32(coupon2.StatusUsed))
if err != nil {
return nil, err
}
}
// 该优惠券不属于当前用户
default:
return nil, errors.New("优惠券不属于当前用户")
}
} else {
// 公开优惠券
amount = amount.Sub(coupon.Amount)
}
}
// 生成订单号
tradeNo, err := ID.GenSerial(context.Background())
if err != nil {
return nil, err
}
// 创建支付订单
var payUrl string
var acquirer trade2.Acquirer
switch method {
// 调用商福通接口
case trade2.MethodSftAlipay, trade2.MethodSftWeChat:
// 商福通 + 手机网站
case (method == trade2.MethodSftAlipay || method == trade2.MethodSftWeChat) && platform == trade2.PlatformMobile:
var payType g.SftPayType
if method == trade2.MethodSftAlipay {
payType = g.SftAlipay
@@ -328,24 +181,31 @@ func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time
return nil, err
}
payUrl = u.Z(u.Z(resp.PayInfo).PayUrl)
if payUrl == "" {
return nil, errors.New("支付接口未返回正确的二维码地址")
}
switch resp.PayType {
case g.SftAlipay:
acquirer = trade2.AcquirerAlipay
case g.SftWeChat:
acquirer = trade2.AcquirerWeChat
case g.SftUnionPay:
acquirer = trade2.AcquirerUnionPay
}
// 不支持的支付方式
default:
slog.Warn(ErrTransactionNotSupported.Error(), "method", method, "platform", platform)
return nil, ErrTransactionNotSupported
}
// 保存交易订单
var trade = m.Trade{
UserID: uid,
InnerNo: tradeNo,
Subject: subject,
Type: int32(tType),
Method: int32(method),
Platform: int32(platform),
Amount: amount,
PayURL: &payUrl,
}
err = q.Trade.Create(&trade)
if err != nil {
return nil, err
}
// 保存用户帐单
var billType bill2.Type
switch tType {
case trade2.TypeRecharge:
@@ -354,22 +214,6 @@ func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time
billType = bill2.TypeConsume
}
var trade = m.Trade{
UserID: uid,
InnerNo: tradeNo,
Subject: subject,
Method: int32(method),
Type: int32(tType),
Amount: amount,
PayURL: &payUrl,
Acquirer: int32(acquirer),
}
err = q.Trade.Create(&trade)
if err != nil {
return nil, err
}
// 保存用户帐单
var bill = m.Bill{
BillNo: ID.GenReadable("bil"),
UserID: uid,
@@ -378,6 +222,7 @@ func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time
Type: int32(billType),
Amount: amount,
}
err = q.Bill.
Omit(q.Bill.ResourceID, q.Bill.RefundID).
Create(&bill)
@@ -398,78 +243,12 @@ func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time
Trade: &trade,
}, nil
}
func (s *tradeService) VerifyCreateTrade(data *TradeVerifyData) (*TradeSuccessResult, error) {
var tradeNo = data.TradeNo
var method = data.Method
// 检查交易号是否存在
var transId string
var paidAt time.Time
var payment decimal.Decimal
switch method {
// 检查支付宝交易
case trade2.MethodAlipay:
resp, err := g.Alipay.TradeQuery(context.Background(), alipay.TradeQuery{
OutTradeNo: tradeNo,
})
if err != nil {
return nil, err
}
if resp.Code != alipay.CodeSuccess {
slog.Warn("支付宝交易查询失败", "code", resp.Code, "sub_code", resp.SubCode, "msg", resp.Msg)
return nil, errors.New("交易查询失败")
}
if resp.TradeStatus != alipay.TradeStatusSuccess {
return nil, ErrTransactionNotPaid
}
transId = resp.TradeNo
payment, err = decimal.NewFromString(resp.TotalAmount)
if err != nil {
return nil, err
}
paidAt, err = time.Parse("2006-01-02 15:04:05", resp.SendPayDate)
if err != nil {
return nil, err
}
// 检查微信交易
case trade2.MethodWeChat:
resp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(context.Background(), native.QueryOrderByOutTradeNoRequest{
OutTradeNo: &tradeNo,
Mchid: &env.WechatPayMchId,
})
if err != nil {
return nil, err
}
if *resp.TradeState != "SUCCESS" {
return nil, ErrTransactionNotPaid
}
transId = *resp.TransactionId
payment = decimal.NewFromInt(*resp.Amount.PayerTotal).Div(decimal.NewFromInt(100))
paidAt, err = time.Parse(time.RFC3339, *resp.SuccessTime)
if err != nil {
return nil, err
}
// 不支持的支付方式
default:
return nil, ErrTransactionNotSupported
}
return &TradeSuccessResult{
TransId: transId,
Payment: payment,
Time: paidAt,
}, nil
}
func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.Trade, error) {
var transId = data.TransId
var tradeNo = data.TradeNo
var payment = data.Payment
var paidAt = data.Time
var acquirer = data.Acquirer
// 获取交易信息
trade, err := q.Trade.
@@ -491,6 +270,7 @@ func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.T
trade.Status = int32(trade2.StatusSuccess)
trade.OuterNo = &transId
trade.Payment = payment
trade.Acquirer = int32(acquirer)
trade.PaidAt = u.P(orm.LocalDateTime(paidAt))
trade.PayURL = u.P("")
_, err = q.Trade.Updates(trade)
@@ -574,12 +354,106 @@ func (s *tradeService) OnTradeRefunded(q *q.Query, tradeNo string, now time.Time
panic("todo")
}
func (s *tradeService) VerifyTrade(data *TradeVerifyData) (*TradeSuccessResult, error) {
var tradeNo = data.TradeNo
var method = data.Method
// 检查交易号是否存在
var transId string
var paidAt time.Time
var payment decimal.Decimal
switch method {
// 检查支付宝交易
case trade2.MethodAlipay:
resp, err := g.Alipay.TradeQuery(context.Background(), alipay.TradeQuery{
OutTradeNo: tradeNo,
})
if err != nil {
return nil, err
}
if resp.Code != alipay.CodeSuccess {
slog.Warn("支付宝交易查询失败", "code", resp.Code, "sub_code", resp.SubCode, "msg", resp.Msg)
return nil, errors.New("交易查询失败")
}
if resp.TradeStatus != alipay.TradeStatusSuccess {
return nil, ErrTransactionNotPaid
}
transId = resp.TradeNo
payment, err = decimal.NewFromString(resp.TotalAmount)
if err != nil {
return nil, err
}
paidAt, err = time.Parse("2006-01-02 15:04:05", resp.SendPayDate)
if err != nil {
return nil, err
}
// 检查微信交易
case trade2.MethodWeChat:
resp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(context.Background(), native.QueryOrderByOutTradeNoRequest{
OutTradeNo: &tradeNo,
Mchid: &env.WechatPayMchId,
})
if err != nil {
return nil, err
}
if *resp.TradeState != "SUCCESS" {
return nil, ErrTransactionNotPaid
}
transId = *resp.TransactionId
payment = decimal.NewFromInt(*resp.Amount.PayerTotal).Div(decimal.NewFromInt(100))
paidAt, err = time.Parse(time.RFC3339, *resp.SuccessTime)
if err != nil {
return nil, err
}
// 检查商福通交易
case trade2.MethodSft, trade2.MethodSftAlipay, trade2.MethodSftWeChat:
resp, err := g.SFTPay.QueryTrade(&g.QueryTradeReq{
MchOrderNo: &tradeNo,
})
if err != nil {
return nil, err
}
if resp.State != g.SftTradeSuccess {
return nil, ErrTransactionNotPaid
}
if resp.PayOrderId == nil {
return nil, errors.New("商福通交易号不存在")
}
if resp.PayTime == nil {
return nil, errors.New("商福通交易时间不存在")
}
transId = *resp.PayOrderId
payment = decimal.NewFromInt(resp.Amount).Div(decimal.NewFromInt(100))
paidAt, err = time.Parse("2006-01-02 15:04:05", *resp.PayTime)
if err != nil {
return nil, err
}
// 不支持的支付方式
default:
return nil, ErrTransactionNotSupported
}
return &TradeSuccessResult{
TransId: transId,
Payment: payment,
Time: paidAt,
}, nil
}
type TradeCreateData struct {
Subject string
Amount decimal.Decimal
ExpireAt time.Time
Type trade2.Type
Method trade2.Method
Platform trade2.Platform
CouponCode string
}
@@ -596,9 +470,10 @@ type TradeVerifyData struct {
}
type TradeSuccessResult struct {
TransId string
Payment decimal.Decimal
Time time.Time
TransId string
Payment decimal.Decimal
Time time.Time
Acquirer trade2.Acquirer
}
type OnTradeCreateData struct {
@@ -606,13 +481,6 @@ type OnTradeCreateData struct {
TradeSuccessResult
}
type TradePlatform int
const (
TradePlatformDesktop TradePlatform = iota + 1 // 桌面端
TradePlatformMobile // 移动端
)
type TradeErr string
func (e TradeErr) Error() string {
@@ -620,6 +488,6 @@ func (e TradeErr) Error() string {
}
var (
ErrTransactionNotPaid = TradeErr("交易未支付")
ErrTransactionNotSupported = TradeErr("不支持的支付方式")
ErrTransactionNotPaid = core.NewBizErr("交易未支付")
ErrTransactionNotSupported = core.NewBizErr("不支持的支付方式")
)

View File

@@ -126,50 +126,52 @@ func newLogger() fiber.Handler {
return false
},
Done: func(c *fiber.Ctx, logBytes []byte) {
var logStr = strings.TrimPrefix(string(logBytes), "🚀")
var logVars = strings.Split(logStr, "|")
go func(ip, ua, method, path string, status int, logBytes []byte) {
var logStr = strings.TrimPrefix(string(logBytes), "🚀")
var logVars = strings.Split(logStr, "|")
var reqTimeStr = strings.TrimSpace(logVars[0])
reqTime, err := time.ParseInLocation("2006-01-02 15:04:05", reqTimeStr, time.Local)
if err != nil {
slog.Error("时间解析错误", slog.Any("err", err))
return
}
var reqTimeStr = strings.TrimSpace(logVars[0])
reqTime, err := time.ParseInLocation("2006-01-02 15:04:05", reqTimeStr, time.Local)
if err != nil {
slog.Error("时间解析错误", slog.Any("err", err))
return
}
var authInfo = strings.Split(strings.TrimSpace(logVars[1]), " ")
var authType = auth.PayloadTypeFromStr(strings.TrimSpace(authInfo[0]))
authID, err := strconv.Atoi(strings.TrimSpace(authInfo[1]))
if err != nil {
slog.Error("负载ID解析错误", slog.Any("err", err))
return
}
var authInfo = strings.Split(strings.TrimSpace(logVars[1]), " ")
var authType = auth.PayloadTypeFromStr(strings.TrimSpace(authInfo[0]))
authID, err := strconv.Atoi(strings.TrimSpace(authInfo[1]))
if err != nil {
slog.Error("负载ID解析错误", slog.Any("err", err))
return
}
var latency = strings.TrimSpace(logVars[4])
var latency = strings.TrimSpace(logVars[4])
var errStr = strings.TrimSpace(logVars[5])
var errStr = strings.TrimSpace(logVars[5])
var item = &m.LogsRequest{
IP: c.IP(),
Ua: u.P(c.Get("User-Agent")),
Method: c.Method(),
Path: c.Path(),
Latency: &latency,
Status: int32(c.Response().StatusCode()),
Error: &errStr,
Time: u.P(orm.LocalDateTime(reqTime)),
}
if authType != auth.PayloadNone {
item.Identity = u.P(int32(authType))
}
if authID != 0 {
item.Visitor = u.P(int32(authID))
}
var item = &m.LogsRequest{
IP: ip,
Ua: u.P(ua),
Method: method,
Path: path,
Latency: &latency,
Status: int32(status),
Error: &errStr,
Time: u.P(orm.LocalDateTime(reqTime)),
}
if authType != auth.PayloadNone {
item.Identity = u.P(int32(authType))
}
if authID != 0 {
item.Visitor = u.P(int32(authID))
}
err = q.LogsRequest.Create(item)
if err != nil {
slog.Error("日志记录错误", slog.Any("err", err))
return
}
err = q.LogsRequest.Create(item)
if err != nil {
slog.Error("日志记录错误", slog.Any("err", err))
return
}
}(c.IP(), c.Get("User-Agent"), c.Method(), c.Path(), c.Response().StatusCode(), logBytes)
},
})
}