Compare commits

..

63 Commits

Author SHA1 Message Date
Mrx
bdb851b701 ff 2026-05-25 17:01:52 +08:00
Mrx
a32ee2052d f 2026-05-25 12:04:30 +08:00
Mrx
e29e0f4d0f f 2026-05-25 10:54:17 +08:00
Mrx
d8b59829f1 f 2026-05-21 12:00:54 +08:00
Mrx
063fb158c9 f 2026-05-20 14:48:06 +08:00
Mrx
02cffff608 f 2026-05-18 13:54:42 +08:00
Mrx
c24f54ce8c f 2026-05-16 19:04:05 +08:00
Mrx
e18fe543cd f 2026-05-16 18:38:45 +08:00
Mrx
6e77c88f9d f 2026-05-16 18:32:01 +08:00
Mrx
c229ea2d5a f 2026-05-16 18:29:43 +08:00
Mrx
a81f5198e3 f 2026-05-16 15:45:05 +08:00
Mrx
867a1022dd f 2026-05-11 20:21:56 +08:00
Mrx
0d476fa477 f 2026-05-11 17:31:21 +08:00
Mrx
9e12db0cd4 f 2026-05-11 16:54:43 +08:00
Mrx
b0313b951e f 2026-05-11 16:21:29 +08:00
Mrx
0431ee605b f 2026-05-11 15:25:41 +08:00
Mrx
940bf64546 fix2 2026-05-11 15:16:16 +08:00
Mrx
4c49724353 f+ 2026-05-11 15:10:26 +08:00
Mrx
467f081a1a f 2026-05-11 15:02:57 +08:00
Mrx
9c4fd95955 f 2026-05-09 21:46:41 +08:00
Mrx
fe18036478 f 2026-05-09 21:39:32 +08:00
Mrx
f2ac09acdd f 2026-05-09 18:24:59 +08:00
Mrx
d68184fa59 f 2026-05-09 18:02:51 +08:00
Mrx
5d51b7a400 f 2026-05-09 17:04:49 +08:00
Mrx
6d4d1b2aed f 2026-05-09 15:28:24 +08:00
Mrx
a09ba16e8c f 2026-05-09 13:10:15 +08:00
Mrx
ad1ceaef17 f 2026-04-24 11:48:22 +08:00
Mrx
13dc22cfa9 f 2026-04-03 11:32:45 +08:00
Mrx
8ccda35a97 Merge branch 'main' of http://1.117.67.95:3000/team/tyc-server-v2 2026-03-13 14:12:21 +08:00
Mrx
71eb38e66f f 2026-03-13 14:10:49 +08:00
66d8d660f2 f 2026-03-04 20:07:41 +08:00
6e3c268b82 Merge branch 'main' of http://1.117.67.95:3000/team/tyc-server-v2 2026-03-02 16:51:15 +08:00
6b8e7eada6 f 2026-03-02 16:51:05 +08:00
Mrx
6a627dc474 f 2026-02-28 12:45:25 +08:00
ed35631900 f 2026-02-24 18:49:36 +08:00
d80076e2c7 f 2026-02-24 18:42:06 +08:00
2d40d589e2 Merge branch 'main' of http://1.117.67.95:3000/team/tyc-server-v2 2026-02-24 16:48:03 +08:00
2d7e241b76 add ali captcha 2026-02-24 16:47:46 +08:00
Mrx
de78857139 f 2026-02-24 15:25:05 +08:00
Mrx
15c0d20508 f 2026-02-24 15:14:33 +08:00
Mrx
e1f62efecd f 2026-02-24 15:00:40 +08:00
86bda66271 f 2026-02-12 19:48:23 +08:00
f5e8fa6558 f 2026-02-12 18:57:13 +08:00
be6aef3730 f 2026-02-12 17:29:03 +08:00
356b422879 f 2026-02-12 17:04:10 +08:00
c2591eec44 f 2026-02-12 16:54:09 +08:00
3d531301cb f 2026-02-12 16:48:21 +08:00
7aa875af19 f 2026-02-12 16:39:53 +08:00
2b8add736f f 2026-02-12 16:25:01 +08:00
ce34e426c4 f 2026-02-12 15:42:09 +08:00
ca6dcc1b24 f 2026-02-12 15:16:54 +08:00
07bf234b30 f 2026-02-06 14:12:07 +08:00
33a8baefb7 f 2026-02-06 14:05:53 +08:00
dc4fcbf857 f 2026-02-06 13:34:49 +08:00
9648fcd1ec f 2026-02-02 19:35:58 +08:00
7beac0d733 Merge branch 'main' of http://1.117.67.95:3000/team/tyc-server-v2 2026-02-02 18:26:50 +08:00
a92f8a777f f 2026-02-02 18:26:01 +08:00
Mrx
b3f2c5c6b6 Merge branch 'main' of http://1.117.67.95:3000/team/tyc-server-v2 2026-02-02 16:07:57 +08:00
Mrx
d74a1514ab f 2026-02-02 16:07:48 +08:00
c1b3709ba1 f 2026-02-01 19:03:28 +08:00
a4f8e17f32 f 2026-02-01 17:35:35 +08:00
bcbecc402d Merge branch 'main' of http://1.117.67.95:3000/team/tyc-server-v2 2026-02-01 16:37:56 +08:00
35d9ecbb6c f 2026-02-01 16:33:20 +08:00
90 changed files with 15420 additions and 968 deletions

10
.gitignore vendored
View File

@@ -5,6 +5,13 @@
.DS_Store .DS_Store
**/.DS_Store **/.DS_Store
# 忽略 IDE/调试生成的临时二进制文件
__debug_bin*
*.exe
*.exe~
*.dll
*.so
*.dylib
#deploy data #deploy data
data/* data/*
@@ -20,3 +27,6 @@ data/*
/tmp/ /tmp/
/app/api /app/api
**/__debug_bin*.exe
**/api.exe
**/*.exe

View File

@@ -267,6 +267,7 @@ type (
Amount float64 `json:"amount"` // 金额 Amount float64 `json:"amount"` // 金额
ActualAmount float64 `json:"actual_amount"` // 实际到账金额(扣税后) ActualAmount float64 `json:"actual_amount"` // 实际到账金额(扣税后)
TaxAmount float64 `json:"tax_amount"` // 扣税金额 TaxAmount float64 `json:"tax_amount"` // 扣税金额
TaxRate float64 `json:"tax_rate"` // 扣税比例,如 0.06 表示 6%
Status int64 `json:"status"` // 状态 Status int64 `json:"status"` // 状态
PayeeAccount string `json:"payee_account"` // 收款账户 PayeeAccount string `json:"payee_account"` // 收款账户
Remark string `json:"remark"` // 备注 Remark string `json:"remark"` // 备注
@@ -471,6 +472,7 @@ type (
WithdrawalId int64 `json:"withdrawal_id"` // 提现记录ID WithdrawalId int64 `json:"withdrawal_id"` // 提现记录ID
Action int64 `json:"action"` // 操作:1-确认,2-拒绝 Action int64 `json:"action"` // 操作:1-确认,2-拒绝
Remark string `json:"remark"` // 备注(拒绝时必填) Remark string `json:"remark"` // 备注(拒绝时必填)
TaxRate *float64 `json:"tax_rate,optional"` // 扣税比例,如 0.06 表示 6%,不传则默认 6%
} }
// 银行卡提现审核响应 // 银行卡提现审核响应

View File

@@ -41,7 +41,12 @@ type AdminGetQueryDetailByOrderIdResp {
Id int64 `json:"id"` // 主键ID Id int64 `json:"id"` // 主键ID
OrderId int64 `json:"order_id"` // 订单ID OrderId int64 `json:"order_id"` // 订单ID
UserId int64 `json:"user_id"` // 用户ID UserId int64 `json:"user_id"` // 用户ID
ProductName string `json:"product_name"` // 产品ID ProductName string `json:"product_name"` // 产品名称
OrderNo string `json:"order_no"` // 商户订单号
PlatformOrderId string `json:"platform_order_id"` // 支付订单号
PaymentStatus string `json:"payment_status"` // 支付状态
PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间(有退款时)
QueryParams map[string]interface{} `json:"query_params"` QueryParams map[string]interface{} `json:"query_params"`
QueryData []AdminQueryItem `json:"query_data"` QueryData []AdminQueryItem `json:"query_data"`
CreateTime string `json:"create_time"` // 创建时间 CreateTime string `json:"create_time"` // 创建时间

View File

@@ -61,6 +61,7 @@ type (
Nickname string `json:"nickname"` // 昵称 Nickname string `json:"nickname"` // 昵称
Info string `json:"info"` // 备注信息 Info string `json:"info"` // 备注信息
Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否
Disable int64 `json:"disable"` // 是否封禁 0-可用 1-禁用
CreateTime string `json:"create_time"` // 创建时间 CreateTime string `json:"create_time"` // 创建时间
UpdateTime string `json:"update_time"` // 更新时间 UpdateTime string `json:"update_time"` // 更新时间
} }
@@ -77,6 +78,7 @@ type (
Nickname string `json:"nickname"` // 昵称 Nickname string `json:"nickname"` // 昵称
Info string `json:"info"` // 备注信息 Info string `json:"info"` // 备注信息
Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否
Disable int64 `json:"disable"` // 是否封禁 0-可用 1-禁用
CreateTime string `json:"create_time"` // 创建时间 CreateTime string `json:"create_time"` // 创建时间
UpdateTime string `json:"update_time"` // 更新时间 UpdateTime string `json:"update_time"` // 更新时间
} }
@@ -103,6 +105,7 @@ type (
Nickname *string `json:"nickname,optional"` // 昵称 Nickname *string `json:"nickname,optional"` // 昵称
Info *string `json:"info,optional"` // 备注信息 Info *string `json:"info,optional"` // 备注信息
Inside *int64 `json:"inside,optional"` // 是否内部用户 1-是 0-否 Inside *int64 `json:"inside,optional"` // 是否内部用户 1-是 0-否
Disable *int64 `json:"disable,optional"` // 是否封禁 0-可用 1-禁用
} }
// 更新响应 // 更新响应

View File

@@ -363,6 +363,7 @@ type (
Remark string `json:"remark"` Remark string `json:"remark"`
payeeAccount string `json:"payee_account"` payeeAccount string `json:"payee_account"`
CreateTime string `json:"create_time"` CreateTime string `json:"create_time"`
TaxRate float64 `json:"tax_rate"` // 扣税比例,如 0.06 表示 6%
} }
GetWithdrawalReq { GetWithdrawalReq {
Page int64 `form:"page"` // 页码 Page int64 `form:"page"` // 页码

View File

@@ -46,7 +46,7 @@ service main {
type ( type (
PaymentReq { PaymentReq {
Id string `json:"id"` Id string `json:"id"`
PayMethod string `json:"pay_method"` PayMethod string `json:"pay_method" validate:"required,oneof=wechat alipay appleiap test"`
PayType string `json:"pay_type" validate:"required,oneof=query agent_vip"` PayType string `json:"pay_type" validate:"required,oneof=query agent_vip"`
} }
PaymentResp { PaymentResp {

View File

@@ -55,6 +55,7 @@ type (
Data string `json:"data" validate:"required"` Data string `json:"data" validate:"required"`
AgentIdentifier string `json:"agent_identifier,optional"` AgentIdentifier string `json:"agent_identifier,optional"`
App bool `json:"app,optional"` App bool `json:"app,optional"`
CaptchaVerifyParam string `json:"captchaVerifyParam,optional"`
} }
QueryServiceResp { QueryServiceResp {
Id string `json:"id"` Id string `json:"id"`
@@ -139,6 +140,7 @@ type (
QueryListReq { QueryListReq {
Page int64 `form:"page"` // 页码 Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数据量 PageSize int64 `form:"page_size"` // 每页数据量
Source string `form:"source,optional"` // 来源: miniapp 小程序过滤非车辆产品
} }
QueryListResp { QueryListResp {
Total int64 `json:"total"` // 总记录数 Total int64 `json:"total"` // 总记录数

View File

@@ -0,0 +1,19 @@
syntax = "v1"
info (
title: "天远异步回调"
desc: "天远车辆类接口异步回调入口"
version: "v1"
)
// 该服务只提供第三方回调入口,不依赖登录态
@server (
prefix: api/v1
group: tianyuan
)
service main {
@doc "天远车辆类接口异步回调"
@handler vehicleCallback
post /tianyuan/vehicle/callback
}

View File

@@ -0,0 +1,32 @@
syntax = "v1"
info (
title: "工具箱服务"
desc: "免费小工具手机号归属地、VIN解析、车牌归属地等"
version: "v1"
)
// ==================== 通用请求/响应 ====================
type (
ToolboxQueryReq {
ToolKey string `json:"tool_key" validate:"required"`
Params map[string]interface{} `json:"params"`
}
ToolboxQueryResp {
ToolKey string `json:"tool_key"`
Result map[string]interface{} `json:"result"`
}
)
// ==================== 免费接口(无需登录) ====================
@server (
prefix: api/v1
group: toolbox
)
service main {
@doc "通用工具查询"
@handler toolboxQuery
post /toolbox/query (ToolboxQueryReq) returns (ToolboxQueryResp)
}

View File

@@ -0,0 +1,39 @@
syntax = "v1"
info (
title: "上传"
desc: "图片上传,用于行驶证等需 URL 的接口"
version: "v1"
)
type (
UploadImageReq {
ImageBase64 string `json:"image_base64" validate:"required"` // 图片 base64不含 data URL 前缀)
}
UploadImageResp {
Url string `json:"url"` // 可公网访问的图片 URL
}
ServeUploadedFileReq {
FileName string `path:"fileName"` // 文件名,如 uuid.jpg
}
// 实际由 Handler 根据 FilePath/ContentType 写文件流,不返回 JSON
ServeUploadedFileResp {
FilePath string `json:"-"` // 内部:本地文件路径
ContentType string `json:"-"` // 内部Content-Type
}
)
@server (
prefix: api/v1
group: upload
)
service main {
@doc "上传图片,返回可访问 URL如行驶证限制 3MB"
@handler UploadImage
post /upload/image (UploadImageReq) returns (UploadImageResp)
@doc "访问已上传文件(供第三方或前端通过返回的 URL 拉取)"
@handler ServeUploadedFile
get /upload/file/:fileName (ServeUploadedFileReq) returns (ServeUploadedFileResp)
}

View File

@@ -143,6 +143,24 @@ type (
sendSmsReq { sendSmsReq {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply realName bindMobile"` ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply realName bindMobile"`
CaptchaVerifyParam string `json:"captchaVerifyParam"`
}
)
//============================> captcha v1 <============================
@server (
prefix: api/v1
group: captcha
)
service main {
@doc "get encrypted scene id for aliyun captcha"
@handler getEncryptedSceneId
post /captcha/encryptedSceneId returns (GetEncryptedSceneIdResp)
}
type (
GetEncryptedSceneIdResp {
EncryptedSceneId string `json:"encryptedSceneId"`
} }
) )

View File

@@ -14,6 +14,8 @@ import "./front/product.api"
import "./front/agent.api" import "./front/agent.api"
import "./front/app.api" import "./front/app.api"
import "./front/authorization.api" import "./front/authorization.api"
import "./front/upload.api"
import "./front/tianyuan.api"
// 后台 // 后台
import "./admin/auth.api" import "./admin/auth.api"
import "./admin/menu.api" import "./admin/menu.api"

View File

@@ -14,32 +14,45 @@ VerifyCode:
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9" AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65" AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
EndpointURL: "dysmsapi.aliyuncs.com" EndpointURL: "dysmsapi.aliyuncs.com"
SignName: "天远查" SignName: "海南海宇大数据"
TemplateCode: "SMS_302641455" TemplateCode: "SMS_302641455"
ValidTime: 300 ValidTime: 300
Captcha:
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
EndpointURL: "captcha.cn-shanghai.aliyuncs.com"
SceneID: "wynt39to"
EKey: "xdhf5JbWVmFXx+2K+6kBk2aH++GtQBEI8Gmzdeen90o="
Encrypt: Encrypt:
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f" SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
Alipay: Alipay:
AppID: "2021004161631930" AppID: "2021006121698606"
PrivateKey: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCRrZNr8DNs4LhPSulTLEg4RLREWVSFGS+Nl5Q2FxQ8DgkUYV+p3kfi4XmB2W/Ruz4egPxEB0V/xj75OktVphVKY8rI6OaNnVoFVe5NqGa5MTj3wLwBIv/hMHA1VAru2KLIv9R1FR7LpWmreHSkpJ65CD2mZqYuMCekOfzMQZIGgSagEU4my0bLbFWw7M3qZz4vm2KUtm4Ew28OUJDkqygjPzXgS5l5niYQvqPjiQNdnTtoIcNcHo07tS8lmf/hdgq9EtVfY7Y0brESfgvOoVJeg1hTHEj0hyWnnWPeA4HD2izANP/5ObRX4ZVqpVju+7PSpbeFd71fxbR1blAVnrqTAgMBAAECggEASpkwHN3r9507xJ7/zG+oq+fCyB1WgrHbAA7W/rviyL4HOECE1F/XP/9mUXAfKq9PqB81D0EJ/dxu8wE/AqUB0g44EZnyNiKVrpXKakoKEFt8aKJxo8NgdNhxHV3kG1skQNi62xntoysZaY1NbeI+xVHLACMghhZytk5bfd02Ac3rMBz3X8Cl1R+3mgU0zFc5f476VRxywiRQM+QNJIaHDNB4vw1TKI0K92mEKD8lOuNZD7d5TCBZi3r08t8FFAkMjIMDiFvFRFmAqMg3NyaIGUkLVDU2zUP0Vlzmo9ghCV9hluqDqeP4RhxQydOw+rxGBk+crYQBhPyYOI/I9PFXAQKBgQDHSRRTPqYbCfztmwk3AIH7VN6izyU3FljEXAsdf+UVJpRa8429J3e+sB96jxhiwVlCzX4CDjsa/Pu0iQQx22a0AZs5GTE0MJ1FVydfGlyqF6/hRS4TswSkklW3be7/KDAjgj2+/wap+mN7rRmDkdvxgCJG6MiWuRAthhg/g16wIwKBgQC7Iu7D4yQXRKheL6p6pbMtE+oD58/EJ2vO8ZUz3LiPc9pZ6+bp4nkBP6JOuYiB5jkzWQifKe6hsXpv06kWzaBEzz4f4SUpWDmdBchNoct3pB/k66FaxHLO/pG4RV86hqscqTdutmdC62bbwM6yCtJ+3rS5rlCxDGQkGJbM+wM60QKBgH5nQyYeCbwC1NRdTzX883VYerLoEyHi4cEC5OX8NnD4/IbIDzJYc2KXUhAp7XzOSPDPaMqi/ih7KKh1dByvnnA0yKEp8oS5BThzNHzlOruEtMF9YOGL3jkIvKfRahOcCRSsyr94AWEVeb57qEBE5y5CaPtzMbAwiCtn779xc0DjAoGAZwEGXWokDm6rIhSoiJO2OQSyFW4+LSDptWHCF2bRa5yAPmiblHck1awaAa0b1yxKpdnG5hzljbirxOvDMZsDMXzFHDUICGbYZ3asVxbMcNE1AQM1sElbTFZRDRWaIhPIEaGOsnDSC8KYvjK1UsikLlMVNPMe1SUV5cxnDPLJR1ECgYEAw8M09uLylPtfGq7oyE2R6xC2kUA8EJ6aapJgUs/UZ6dtjtvudbYzUo0Cgnb12hpN3hfLc5O0/P4nRzZ72Hm43cMiRNLJi4BYCa0m/mCxq+RcoBWYQTIraHnR17yIQhxt5IBRVjgbvYCnryx5Jd5wjOvv7DdnGFJLepzSJwlGqeU=" PrivateKey: "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCx3TqaCUgZIMfG8Lu8fqkKqKLzONB9NBp4qCM2CAgxkUK1fd48XZ3ANDzfzNoeIbLbR/VL3ZUgyX/F8jO7Cit71JS58X9b1ngeRyBdaiBhLZgHb7Ngg0f1zDJuq8H6/i3Tiu9PaH0iOFKeaFCCpkakFyXjp//rqkcj2bPfa6lX0bD4YcILz8DC3iPy/cXHrHwaBryhYouYA3bKFROHykfF5D+tS2PZdR13BiDVElwDuquZCMdRdISfTw8snp5HSxA0TyRZIiGrtU3WvSKxYi2s8smZl/uYN1uj4w4GOivnTrkBdLy6DBqvgqRsCeYDDV5UyjtbAhvksZH60EWS3HTfAgMBAAECggEAK4dv+xjAa13pZpet6nC5ICGrV4kVBT9GJzdG/scycicRw2cdh3qFy+884qy4yN0Ib8AJmVqOT6rguWoQHPtdLv4Us/kVaT1wwkA3/ISnjgDhjxhYNwuKBe7GfO1OGQYx4u7CqJVy4ngUSC5RXdghu7Dqle+co2lV5cE20zv/Ar2nYo4p9fxcl/XAttqdRyby3ge6zJZP46Ru4CHzFzyUsrJYC575R3Jq68Zrr/+v0BmpEm/wAmoQE8W0ElMMH+Jw0twj+4l6PaUcq9oUVIL9wzl7ay1B6dKyxEOyinGYrmd65NzsTu8HhEpFdvQ+1O27XyahLsNWpSamJlV23ns/EQKBgQDd2UiQQRrj+itfa1/Zehy8MstEICw73LMDk129yNG9cU8U4y5vaTJkAcDTfme08B3uVVhsIUhJsFdgnh4ayyYW3jsB5BBBgszhwYQciH7f/3nE3rcX/TIs6yQAQkPhQi2RNSdn3SrCIhxgu9TwrZIDyQYJ6kk/9qEmZd93RrhOBwKBgQDNPpS1jFt4l08kzCHzfTvTbIu5z/6NPxmRSpcrdkoOAUfzlKSkRnKJuzkFSJEmWpFXILfNiza1dBYncg60Lu58yXpaGlLbVmxeFem+DdQm55lgl28d8Ssg6nlCTqE/tjB5LZ6qn8KEkWSj6gXH37pU1XFZh0A3+EIytnL7NnrsaQKBgGMaGUw3iSemLZHmiV7BKez4U80PAjOLl3xVbF7HQsp5v3X5NlkWiSgbkGPp57HwQa6h+Wn0RDKGz8GdYJ1fephklb92fbyGDbgblkSYxPSTT3Yed3QD61IdiGuFLoWF5o0jTYMcTWmDi2G7BpitMLj4J/Zt7mLgbYSVpYnG0bYpAoGAS7Tfya/CNdMqQFqD03rITI5nY9zS+mriFXO8Gy4A1vWmArU7ndTWfvNubwJ7d/hEUC0jX1AQmBH/8gDiZ5hAJAt1dDLtiTZxtqrCk3YqYUdgjf6N4C+LRxL2M30pgYTEkI5BTpKrf5bZ1pSGGVnvM0egDfQTvhF26ZnfA8buxLECgYAssWTLQ2Ou2NJ8N3HJx54kpKLwf218ulINsH6opcot22hbVfj0rc7lAcMe9FTGgmSgrbZG9QqAC2BpSHDeSC4C57iSYhG86tq/jwvNsV1miPV9RRj3CvwZkbHzCwqhRvCPS/QUgTzG3Z9pljYLkZjuMkb9jp814GbwEGPeoucefw=="
AlipayPublicKey: "" AlipayPublicKey: ""
AppCertPath: "etc/merchant/appCertPublicKey_2021004161631930.crt" AppCertPath: "etc/merchant/appCertPublicKey_2021006121698606.crt"
AlipayCertPath: "etc/merchant/alipayCertPublicKey_RSA2.crt" AlipayCertPath: "etc/merchant/alipayCertPublicKey_RSA2.crt"
AlipayRootCertPath: "etc/merchant/alipayRootCert.crt" AlipayRootCertPath: "etc/merchant/alipayRootCert.crt"
AppIDBak: "2021004161631930"
PrivateKeyBak: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCRrZNr8DNs4LhPSulTLEg4RLREWVSFGS+Nl5Q2FxQ8DgkUYV+p3kfi4XmB2W/Ruz4egPxEB0V/xj75OktVphVKY8rI6OaNnVoFVe5NqGa5MTj3wLwBIv/hMHA1VAru2KLIv9R1FR7LpWmreHSkpJ65CD2mZqYuMCekOfzMQZIGgSagEU4my0bLbFWw7M3qZz4vm2KUtm4Ew28OUJDkqygjPzXgS5l5niYQvqPjiQNdnTtoIcNcHo07tS8lmf/hdgq9EtVfY7Y0brESfgvOoVJeg1hTHEj0hyWnnWPeA4HD2izANP/5ObRX4ZVqpVju+7PSpbeFd71fxbR1blAVnrqTAgMBAAECggEASpkwHN3r9507xJ7/zG+oq+fCyB1WgrHbAA7W/rviyL4HOECE1F/XP/9mUXAfKq9PqB81D0EJ/dxu8wE/AqUB0g44EZnyNiKVrpXKakoKEFt8aKJxo8NgdNhxHV3kG1skQNi62xntoysZaY1NbeI+xVHLACMghhZytk5bfd02Ac3rMBz3X8Cl1R+3mgU0zFc5f476VRxywiRQM+QNJIaHDNB4vw1TKI0K92mEKD8lOuNZD7d5TCBZi3r08t8FFAkMjIMDiFvFRFmAqMg3NyaIGUkLVDU2zUP0Vlzmo9ghCV9hluqDqeP4RhxQydOw+rxGBk+crYQBhPyYOI/I9PFXAQKBgQDHSRRTPqYbCfztmwk3AIH7VN6izyU3FljEXAsdf+UVJpRa8429J3e+sB96jxhiwVlCzX4CDjsa/Pu0iQQx22a0AZs5GTE0MJ1FVydfGlyqF6/hRS4TswSkklW3be7/KDAjgj2+/wap+mN7rRmDkdvxgCJG6MiWuRAthhg/g16wIwKBgQC7Iu7D4yQXRKheL6p6pbMtE+oD58/EJ2vO8ZUz3LiPc9pZ6+bp4nkBP6JOuYiB5jkzWQifKe6hsXpv06kWzaBEzz4f4SUpWDmdBchNoct3pB/k66FaxHLO/pG4RV86hqscqTdutmdC62bbwM6yCtJ+3rS5rlCxDGQkGJbM+wM60QKBgH5nQyYeCbwC1NRdTzX883VYerLoEyHi4cEC5OX8NnD4/IbIDzJYc2KXUhAp7XzOSPDPaMqi/ih7KKh1dByvnnA0yKEp8oS5BThzNHzlOruEtMF9YOGL3jkIvKfRahOcCRSsyr94AWEVeb57qEBE5y5CaPtzMbAwiCtn779xc0DjAoGAZwEGXWokDm6rIhSoiJO2OQSyFW4+LSDptWHCF2bRa5yAPmiblHck1awaAa0b1yxKpdnG5hzljbirxOvDMZsDMXzFHDUICGbYZ3asVxbMcNE1AQM1sElbTFZRDRWaIhPIEaGOsnDSC8KYvjK1UsikLlMVNPMe1SUV5cxnDPLJR1ECgYEAw8M09uLylPtfGq7oyE2R6xC2kUA8EJ6aapJgUs/UZ6dtjtvudbYzUo0Cgnb12hpN3hfLc5O0/P4nRzZ72Hm43cMiRNLJi4BYCa0m/mCxq+RcoBWYQTIraHnR17yIQhxt5IBRVjgbvYCnryx5Jd5wjOvv7DdnGFJLepzSJwlGqeU="
AlipayPublicKeyBak: ""
AppCertPathBak: "etc/merchant/bak/appCertPublicKey_2021004161631930.crt"
AlipayCertPathBak: "etc/merchant/bak/alipayCertPublicKey_RSA2.crt"
AlipayRootCertPathBak: "etc/merchant/bak/alipayRootCert.crt"
IsProduction: true IsProduction: true
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/alipay/callback" NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/alipay/callback"
ReturnURL: "https://www.tianyuancha.cn/payment/result" ReturnURL: "https://www.tianyuancha.cn/payment/result"
Wxpay: Wxpay:
AppID: "wxa581992dc74d860e" AppID: "wxd391e40295bd9dfb"
MchID: "1682635136" MchID: "1105276690"
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61" MchCertificateSerialNumber: "4F1738D21CAEB7F76193A770CDBA6D7002ED1CFD"
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f" MchApiv3Key: "K2d8F5gJ1sP7zQ3bN9mR4xV6c0hL5tU2"
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem" MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
MchPublicKeyID: "PUB_KEY_ID_0116826351362025060900382267001601" MchPublicKeyID: "PUB_KEY_ID_0111052766902026050900112134001605"
MchPublicKeyPath: "etc/merchant/pub_key.pem" MchPublicKeyPath: "etc/merchant/pub_key.pem"
MchPlatformRAS: "1FFEC3F62E31885FAB4C91ADCB8D7557E9488781" MchPlatformRAS: "PUB_KEY_ID_0111052766902026050900112134001605"
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/wechat/callback" NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://www.tianyuancha.cn/api/v1/wechat/refund_callback" RefundNotifyUrl: "https://www.tianyuancha.cn/api/v1/wechat/refund_callback"
Applepay: Applepay:
ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt" ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt"
SandboxVerifyURL: "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/receipt" SandboxVerifyURL: "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/receipt"
@@ -54,13 +67,14 @@ SystemConfig:
ThreeVerify: false ThreeVerify: false
CommissionSafeMode: false # 佣金安全防御模式true-冻结模式false-直接结算模式 CommissionSafeMode: false # 佣金安全防御模式true-冻结模式false-直接结算模式
WechatH5: WechatH5:
AppID: "wxa581992dc74d860e" AppID: "wxd391e40295bd9dfb"
AppSecret: "4de1fbf521712247542d49907fcd5dbf" AppSecret: "f0fa74f7ed8c3c9953677465d44a4c0c"
WechatMini: WechatMini:
AppID: "wx781abb66b3368963" # 小程序的AppID # AppID: "wx5bacc94add2da981" # 小程序的AppID
AppSecret: "c7d02cdb0fc23c35c93187af9243b00d" # 小程序的AppSecret # AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret
TycAppID: "wxe74617f3dd56c196" # TYC
TycAppSecret: "c8207e54aef5689b2a7c1f91ed7ae8a0" AppID: "wxe74617f3dd56c196"
AppSecret: "c8207e54aef5689b2a7c1f91ed7ae8a0"
Query: Query:
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒 ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
AdminConfig: AdminConfig:
@@ -79,4 +93,14 @@ Tianyuanapi:
Timeout: 60 Timeout: 60
Authorization: Authorization:
FileBaseURL: "https://www.tianyuancha.cn/api/v1/auth-docs" # 授权书文件访问基础URL FileBaseURL: "https://www.tianyuancha.cn/api/v1/auth-docs" # 授权书文件访问基础URL
Upload:
FileBaseURL: "https://www.tianyuancha.cn/api/v1/upload/file" # 上传图片访问基础 URL
TempFileMaxAgeH: 24 # 临时文件保留时长小时超时自动删除0 表示默认 24
PublicBaseURL: "https://www.tianyuancha.cn"
ExtensionTime: 24 # 佣金解冻延迟时间单位24小时 ExtensionTime: 24 # 佣金解冻延迟时间单位24小时
tianxingjuhe:
url: "https://apis.tianapi.com"
key: "4ceffb1ffb95b83230b9a9c9df2467e1"
timeout: 30

View File

@@ -16,9 +16,19 @@ VerifyCode:
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9" AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65" AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
EndpointURL: "dysmsapi.aliyuncs.com" EndpointURL: "dysmsapi.aliyuncs.com"
SignName: "天远查" SignName: "海南海宇大数据"
TemplateCode: "SMS_302641455" TemplateCode: "SMS_302641455"
ValidTime: 300 ValidTime: 300
Captcha:
# 建议与短信相同的 AccessKey或单独为验证码创建子账号
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
# 验证码服务 Endpoint国内一般为 captcha.cn-shanghai.aliyuncs.com
EndpointURL: "captcha.cn-shanghai.aliyuncs.com"
# 阿里云控制台中该场景的 SceneId请替换为真实值
SceneID: "wynt39to"
# 验证码控制台中的 ekey通常为 Base64 字符串),用于生成 EncryptedSceneId
EKey: ""
Encrypt: Encrypt:
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f" SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
WestConfig: WestConfig:
@@ -31,24 +41,31 @@ YushanConfig:
AcctID: "YSSJ843926726" AcctID: "YSSJ843926726"
Url: "https://api.yushanshuju.com/credit-gw/service" Url: "https://api.yushanshuju.com/credit-gw/service"
Alipay: Alipay:
AppID: "2021004161631930" AppID: "2021006121698606"
PrivateKey: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCRrZNr8DNs4LhPSulTLEg4RLREWVSFGS+Nl5Q2FxQ8DgkUYV+p3kfi4XmB2W/Ruz4egPxEB0V/xj75OktVphVKY8rI6OaNnVoFVe5NqGa5MTj3wLwBIv/hMHA1VAru2KLIv9R1FR7LpWmreHSkpJ65CD2mZqYuMCekOfzMQZIGgSagEU4my0bLbFWw7M3qZz4vm2KUtm4Ew28OUJDkqygjPzXgS5l5niYQvqPjiQNdnTtoIcNcHo07tS8lmf/hdgq9EtVfY7Y0brESfgvOoVJeg1hTHEj0hyWnnWPeA4HD2izANP/5ObRX4ZVqpVju+7PSpbeFd71fxbR1blAVnrqTAgMBAAECggEASpkwHN3r9507xJ7/zG+oq+fCyB1WgrHbAA7W/rviyL4HOECE1F/XP/9mUXAfKq9PqB81D0EJ/dxu8wE/AqUB0g44EZnyNiKVrpXKakoKEFt8aKJxo8NgdNhxHV3kG1skQNi62xntoysZaY1NbeI+xVHLACMghhZytk5bfd02Ac3rMBz3X8Cl1R+3mgU0zFc5f476VRxywiRQM+QNJIaHDNB4vw1TKI0K92mEKD8lOuNZD7d5TCBZi3r08t8FFAkMjIMDiFvFRFmAqMg3NyaIGUkLVDU2zUP0Vlzmo9ghCV9hluqDqeP4RhxQydOw+rxGBk+crYQBhPyYOI/I9PFXAQKBgQDHSRRTPqYbCfztmwk3AIH7VN6izyU3FljEXAsdf+UVJpRa8429J3e+sB96jxhiwVlCzX4CDjsa/Pu0iQQx22a0AZs5GTE0MJ1FVydfGlyqF6/hRS4TswSkklW3be7/KDAjgj2+/wap+mN7rRmDkdvxgCJG6MiWuRAthhg/g16wIwKBgQC7Iu7D4yQXRKheL6p6pbMtE+oD58/EJ2vO8ZUz3LiPc9pZ6+bp4nkBP6JOuYiB5jkzWQifKe6hsXpv06kWzaBEzz4f4SUpWDmdBchNoct3pB/k66FaxHLO/pG4RV86hqscqTdutmdC62bbwM6yCtJ+3rS5rlCxDGQkGJbM+wM60QKBgH5nQyYeCbwC1NRdTzX883VYerLoEyHi4cEC5OX8NnD4/IbIDzJYc2KXUhAp7XzOSPDPaMqi/ih7KKh1dByvnnA0yKEp8oS5BThzNHzlOruEtMF9YOGL3jkIvKfRahOcCRSsyr94AWEVeb57qEBE5y5CaPtzMbAwiCtn779xc0DjAoGAZwEGXWokDm6rIhSoiJO2OQSyFW4+LSDptWHCF2bRa5yAPmiblHck1awaAa0b1yxKpdnG5hzljbirxOvDMZsDMXzFHDUICGbYZ3asVxbMcNE1AQM1sElbTFZRDRWaIhPIEaGOsnDSC8KYvjK1UsikLlMVNPMe1SUV5cxnDPLJR1ECgYEAw8M09uLylPtfGq7oyE2R6xC2kUA8EJ6aapJgUs/UZ6dtjtvudbYzUo0Cgnb12hpN3hfLc5O0/P4nRzZ72Hm43cMiRNLJi4BYCa0m/mCxq+RcoBWYQTIraHnR17yIQhxt5IBRVjgbvYCnryx5Jd5wjOvv7DdnGFJLepzSJwlGqeU=" PrivateKey: "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCx3TqaCUgZIMfG8Lu8fqkKqKLzONB9NBp4qCM2CAgxkUK1fd48XZ3ANDzfzNoeIbLbR/VL3ZUgyX/F8jO7Cit71JS58X9b1ngeRyBdaiBhLZgHb7Ngg0f1zDJuq8H6/i3Tiu9PaH0iOFKeaFCCpkakFyXjp//rqkcj2bPfa6lX0bD4YcILz8DC3iPy/cXHrHwaBryhYouYA3bKFROHykfF5D+tS2PZdR13BiDVElwDuquZCMdRdISfTw8snp5HSxA0TyRZIiGrtU3WvSKxYi2s8smZl/uYN1uj4w4GOivnTrkBdLy6DBqvgqRsCeYDDV5UyjtbAhvksZH60EWS3HTfAgMBAAECggEAK4dv+xjAa13pZpet6nC5ICGrV4kVBT9GJzdG/scycicRw2cdh3qFy+884qy4yN0Ib8AJmVqOT6rguWoQHPtdLv4Us/kVaT1wwkA3/ISnjgDhjxhYNwuKBe7GfO1OGQYx4u7CqJVy4ngUSC5RXdghu7Dqle+co2lV5cE20zv/Ar2nYo4p9fxcl/XAttqdRyby3ge6zJZP46Ru4CHzFzyUsrJYC575R3Jq68Zrr/+v0BmpEm/wAmoQE8W0ElMMH+Jw0twj+4l6PaUcq9oUVIL9wzl7ay1B6dKyxEOyinGYrmd65NzsTu8HhEpFdvQ+1O27XyahLsNWpSamJlV23ns/EQKBgQDd2UiQQRrj+itfa1/Zehy8MstEICw73LMDk129yNG9cU8U4y5vaTJkAcDTfme08B3uVVhsIUhJsFdgnh4ayyYW3jsB5BBBgszhwYQciH7f/3nE3rcX/TIs6yQAQkPhQi2RNSdn3SrCIhxgu9TwrZIDyQYJ6kk/9qEmZd93RrhOBwKBgQDNPpS1jFt4l08kzCHzfTvTbIu5z/6NPxmRSpcrdkoOAUfzlKSkRnKJuzkFSJEmWpFXILfNiza1dBYncg60Lu58yXpaGlLbVmxeFem+DdQm55lgl28d8Ssg6nlCTqE/tjB5LZ6qn8KEkWSj6gXH37pU1XFZh0A3+EIytnL7NnrsaQKBgGMaGUw3iSemLZHmiV7BKez4U80PAjOLl3xVbF7HQsp5v3X5NlkWiSgbkGPp57HwQa6h+Wn0RDKGz8GdYJ1fephklb92fbyGDbgblkSYxPSTT3Yed3QD61IdiGuFLoWF5o0jTYMcTWmDi2G7BpitMLj4J/Zt7mLgbYSVpYnG0bYpAoGAS7Tfya/CNdMqQFqD03rITI5nY9zS+mriFXO8Gy4A1vWmArU7ndTWfvNubwJ7d/hEUC0jX1AQmBH/8gDiZ5hAJAt1dDLtiTZxtqrCk3YqYUdgjf6N4C+LRxL2M30pgYTEkI5BTpKrf5bZ1pSGGVnvM0egDfQTvhF26ZnfA8buxLECgYAssWTLQ2Ou2NJ8N3HJx54kpKLwf218ulINsH6opcot22hbVfj0rc7lAcMe9FTGgmSgrbZG9QqAC2BpSHDeSC4C57iSYhG86tq/jwvNsV1miPV9RRj3CvwZkbHzCwqhRvCPS/QUgTzG3Z9pljYLkZjuMkb9jp814GbwEGPeoucefw=="
AlipayPublicKey: "" AlipayPublicKey: ""
AppCertPath: "etc/merchant/appCertPublicKey_2021004161631930.crt" AppCertPath: "etc/merchant/appCertPublicKey_2021006121698606.crt"
AlipayCertPath: "etc/merchant/alipayCertPublicKey_RSA2.crt" AlipayCertPath: "etc/merchant/alipayCertPublicKey_RSA2.crt"
AlipayRootCertPath: "etc/merchant/alipayRootCert.crt" AlipayRootCertPath: "etc/merchant/alipayRootCert.crt"
AppIDBak: "2021004161631930"
PrivateKeyBak: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCRrZNr8DNs4LhPSulTLEg4RLREWVSFGS+Nl5Q2FxQ8DgkUYV+p3kfi4XmB2W/Ruz4egPxEB0V/xj75OktVphVKY8rI6OaNnVoFVe5NqGa5MTj3wLwBIv/hMHA1VAru2KLIv9R1FR7LpWmreHSkpJ65CD2mZqYuMCekOfzMQZIGgSagEU4my0bLbFWw7M3qZz4vm2KUtm4Ew28OUJDkqygjPzXgS5l5niYQvqPjiQNdnTtoIcNcHo07tS8lmf/hdgq9EtVfY7Y0brESfgvOoVJeg1hTHEj0hyWnnWPeA4HD2izANP/5ObRX4ZVqpVju+7PSpbeFd71fxbR1blAVnrqTAgMBAAECggEASpkwHN3r9507xJ7/zG+oq+fCyB1WgrHbAA7W/rviyL4HOECE1F/XP/9mUXAfKq9PqB81D0EJ/dxu8wE/AqUB0g44EZnyNiKVrpXKakoKEFt8aKJxo8NgdNhxHV3kG1skQNi62xntoysZaY1NbeI+xVHLACMghhZytk5bfd02Ac3rMBz3X8Cl1R+3mgU0zFc5f476VRxywiRQM+QNJIaHDNB4vw1TKI0K92mEKD8lOuNZD7d5TCBZi3r08t8FFAkMjIMDiFvFRFmAqMg3NyaIGUkLVDU2zUP0Vlzmo9ghCV9hluqDqeP4RhxQydOw+rxGBk+crYQBhPyYOI/I9PFXAQKBgQDHSRRTPqYbCfztmwk3AIH7VN6izyU3FljEXAsdf+UVJpRa8429J3e+sB96jxhiwVlCzX4CDjsa/Pu0iQQx22a0AZs5GTE0MJ1FVydfGlyqF6/hRS4TswSkklW3be7/KDAjgj2+/wap+mN7rRmDkdvxgCJG6MiWuRAthhg/g16wIwKBgQC7Iu7D4yQXRKheL6p6pbMtE+oD58/EJ2vO8ZUz3LiPc9pZ6+bp4nkBP6JOuYiB5jkzWQifKe6hsXpv06kWzaBEzz4f4SUpWDmdBchNoct3pB/k66FaxHLO/pG4RV86hqscqTdutmdC62bbwM6yCtJ+3rS5rlCxDGQkGJbM+wM60QKBgH5nQyYeCbwC1NRdTzX883VYerLoEyHi4cEC5OX8NnD4/IbIDzJYc2KXUhAp7XzOSPDPaMqi/ih7KKh1dByvnnA0yKEp8oS5BThzNHzlOruEtMF9YOGL3jkIvKfRahOcCRSsyr94AWEVeb57qEBE5y5CaPtzMbAwiCtn779xc0DjAoGAZwEGXWokDm6rIhSoiJO2OQSyFW4+LSDptWHCF2bRa5yAPmiblHck1awaAa0b1yxKpdnG5hzljbirxOvDMZsDMXzFHDUICGbYZ3asVxbMcNE1AQM1sElbTFZRDRWaIhPIEaGOsnDSC8KYvjK1UsikLlMVNPMe1SUV5cxnDPLJR1ECgYEAw8M09uLylPtfGq7oyE2R6xC2kUA8EJ6aapJgUs/UZ6dtjtvudbYzUo0Cgnb12hpN3hfLc5O0/P4nRzZ72Hm43cMiRNLJi4BYCa0m/mCxq+RcoBWYQTIraHnR17yIQhxt5IBRVjgbvYCnryx5Jd5wjOvv7DdnGFJLepzSJwlGqeU="
AlipayPublicKeyBak: ""
AppCertPathBak: "etc/merchant/bak/appCertPublicKey_2021004161631930.crt"
AlipayCertPathBak: "etc/merchant/bak/alipayCertPublicKey_RSA2.crt"
AlipayRootCertPathBak: "etc/merchant/bak/alipayRootCert.crt"
IsProduction: true IsProduction: true
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/alipay/callback" NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/alipay/callback"
ReturnURL: "https://www.tianyuancha.cn/payment/result" ReturnURL: "https://www.tianyuancha.cn/payment/result"
Wxpay: Wxpay:
AppID: "wxa581992dc74d860e" AppID: "wxd391e40295bd9dfb"
MchID: "1682635136" MchID: "1105276690"
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61" MchCertificateSerialNumber: "4F1738D21CAEB7F76193A770CDBA6D7002ED1CFD"
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f" MchApiv3Key: "K2d8F5gJ1sP7zQ3bN9mR4xV6c0hL5tU2"
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem" MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
MchPublicKeyID: "PUB_KEY_ID_0116826351362025060900382267001601" MchPublicKeyID: "PUB_KEY_ID_0111052766902026050900112134001605"
MchPublicKeyPath: "etc/merchant/pub_key.pem" MchPublicKeyPath: "etc/merchant/pub_key.pem"
MchPlatformRAS: "1FFEC3F62E31885FAB4C91ADCB8D7557E9488781" MchPlatformRAS: "PUB_KEY_ID_0111052766902026050900112134001605"
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/wechat/callback" NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://www.tianyuancha.cn/api/v1/wechat/refund_callback" RefundNotifyUrl: "https://www.tianyuancha.cn/api/v1/wechat/refund_callback"
Applepay: Applepay:
@@ -65,11 +82,14 @@ SystemConfig:
ThreeVerify: true ThreeVerify: true
CommissionSafeMode: false # 佣金安全防御模式true-冻结模式false-直接结算模式 CommissionSafeMode: false # 佣金安全防御模式true-冻结模式false-直接结算模式
WechatH5: WechatH5:
AppID: "wxa581992dc74d860e" AppID: "wxd391e40295bd9dfb"
AppSecret: "4de1fbf521712247542d49907fcd5dbf" AppSecret: "f0fa74f7ed8c3c9953677465d44a4c0c"
WechatMini: WechatMini:
AppID: "wx781abb66b3368963" # 小程序的AppID # AppID: "wx5bacc94add2da981" # 小程序的AppID
AppSecret: "c7d02cdb0fc23c35c93187af9243b00d" # 小程序的AppSecret # AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret
# TYC
AppID: "wxe74617f3dd56c196"
AppSecret: "c8207e54aef5689b2a7c1f91ed7ae8a0"
Query: Query:
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒 ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
AdminConfig: AdminConfig:
@@ -86,6 +106,15 @@ Tianyuanapi:
Key: "74902aff197d72d1caa5593560cb281e" Key: "74902aff197d72d1caa5593560cb281e"
BaseURL: "https://api.tianyuanapi.com" BaseURL: "https://api.tianyuanapi.com"
Timeout: 60 Timeout: 60
tianxingjuhe:
url: "https://apis.tianapi.com"
key: "4ceffb1ffb95b83230b9a9c9df2467e1"
timeout: 30
Authorization: Authorization:
FileBaseURL: "https://www.tianyuancha.cn/api/v1/auth-docs" # 授权书文件访问基础URL FileBaseURL: "https://www.tianyuancha.cn/api/v1/auth-docs" # 授权书文件访问基础URL
Upload:
FileBaseURL: "https://www.tianyuanfcha.cn/api/v1/upload/file" # 上传图片访问基础 URL行驶证等
TempFileMaxAgeH: 24
PublicBaseURL: "https://www.tianyuancha.cn"
ExtensionTime: 24 # 佣金解冻延迟时间单位24小时 ExtensionTime: 24 # 佣金解冻延迟时间单位24小时

View File

@@ -1,21 +1,21 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDsjCCApqgAwIBAgIQICYBJSGeFVvaAxyDb0s3iTANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE MIIDsjCCApqgAwIBAgIQICYBIfYzsX0sOqARgYshuDANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0 BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0
aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs
YXNzIDIgUjEwHhcNMjYwMTI1MDc1MTEyWhcNMzEwMTI0MDc1MTEyWjCBkjELMAkGA1UEBhMCQ04x YXNzIDIgUjEwHhcNMjYwMTIxMDk0NDI2WhcNMzEwMTIwMDk0NDI2WjCBkjELMAkGA1UEBhMCQ04x
LTArBgNVBAoMJOW5v+ilv+emj+mTree9kee7nOenkeaKgOaciemZkOWFrOWPuDEPMA0GA1UECwwG LTArBgNVBAoMJOW5v+ilv+emj+mTree9kee7nOenkeaKgOaciemZkOWFrOWPuDEPMA0GA1UECwwG
QWxpcGF5MUMwQQYDVQQDDDrmlK/ku5jlrp0o5Lit5Zu9Kee9kee7nOaKgOacr+aciemZkOWFrOWP QWxpcGF5MUMwQQYDVQQDDDrmlK/ku5jlrp0o5Lit5Zu9Kee9kee7nOaKgOacr+aciemZkOWFrOWP
uC0yMDg4ODQxMzMwNzE0NjM4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomw6g4rB uC0yMDg4MjUxNjk3MTI0MTM5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhzfJRbBY
mCr/QoX3NI3DVLyDpkaUytZ2uFhdfQaegIDAuUfZfgpTCASlAtO82t8ISAbSOSyp9CUpwdGV4EYO 47FJpj2SQ2I4u1Z3KdyrtPG+jfX+wrVoubzVNC5f6AoULEeJcmDDXwj1t7Arz7F7F9Xbrn7JoSsn
iCBbLxMYB6taaHPiIjJ1zNT1EakJzWgU53hz1AVeABB9kdAvMqSvjH6KLoVupmqm4Li8ZwDW9M2A x6nuxVW8XXK/J9emy/SK7LS+Oofe2vwwvRSos8iYleYFYDYlYQB/+kK/Zsn9Qv4M5SfpWK7nU7AD
NAmyDfKgiF0Lt4aUUnaZktoCrTWTkpmtfRZCHNACj851IllvN2wyC4OL7dJq5UzOFxmn07Dy/2z4 A4lvaV7VCszSYYFsqtZ1uEdqJIpUdTuR3IxM29AE8oqSpAV1OIhlLGN7NXsRrl3kjYijxnmwUEcl
UAhaaSAyRVawpOui5AIYJTXZERLYL3KMyRnMuZoFq3xltzVTzRPM06nRa9RfeVNVwWVtGBIe/r8t 2ODJnIHF3oMmCdMU3hMr14CsEd95VVfVsc3UR2VxEylTO0P0ZZfRi17P9KI3aWNaNVLcFcYvpBkM
cg5wyhI57KUszGNOmUIm/se6G2lnAQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCA/gwDQYJKoZIhvcN /8GawCmi0aWkPRgw9SinJtjh8x082QIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCA/gwDQYJKoZIhvcN
AQELBQADggEBAFWBZ6o+6o1l0HRt3ZzMcFb9IqeWslnmbaE8bfvnk1qaxQmSKQGuTZOkK9jigXs5 AQELBQADggEBAISexDU5a00sssj4Oy4atS2kcUktdloapzp8w8t37Svd5iCSOtcN9qONmBTKjybs
APgs0o5Wtoxbg1+zKbXRjL1+l7l1zEvMtx3goUruXoEPTgBNpJPh0JAwWaX0r+9H1SgOK+o3zbHD zEHF4LJZRaum/V3i2WHXDX3WzJBhvmhk+Ryyp5dr+IiA3qR1+jdkQrHJVPD5phvrsk9Nj9Fw4jCW
xRjfAYYPbJeIyta8Rkvu2m7U5lVxQlDMZREpslAMZXLvbn0KP9OGwEoYqJNmklCncY4VpxpBYEci 3tSQLD7Y2oVPymiKhIRisoUNg39SHpAMak283KV7vNR9SRdUqTEJLmN7C5WasZ68X+6ZY0a/+3T6
cSairY4cFp8TSifV4jYkkG5VAWQLOjOFoi9n404ZKON2s1+R/uBDOrGelivu4h/rJA0en7aZivCG dDKIfFqztBYtdZ2CJs27fw6WBr8/zV64fkk0XinxiOhRQdF5D+GgtDhwa+d6I8QtNJTL+FBZgkFZ
UQVfym1r1HyaZLAdxWBOGY22NaEcJmQGjKe2uEdNNgvp7kFmOmE= H59XbYUpUd8BCfjuNImlXHxAGw65LoafjQR7Dbi11eghynqdcOU=
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIE4jCCAsqgAwIBAgIIYsSr5bKAMl8wDQYJKoZIhvcNAQELBQAwejELMAkGA1UEBhMCQ04xFjAU MIIE4jCCAsqgAwIBAgIIYsSr5bKAMl8wDQYJKoZIhvcNAQELBQAwejELMAkGA1UEBhMCQ04xFjAU

Binary file not shown.

View File

@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEKDCCAxCgAwIBAgIUTxc40hyut/dhk6dwzbptcALtHP0wDQYJKoZIhvcNAQEL
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
Q0EwHhcNMjYwNTA5MDU0MTQyWhcNMzEwNTA4MDU0MTQyWjCBgTETMBEGA1UEAwwK
MTEwNTI3NjY5MDEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMS0wKwYDVQQL
DCTlub/opb/npo/pk63nvZHnu5znp5HmioDmnInpmZDlhazlj7gxCzAJBgNVBAYT
AkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANOAJ29Xxux4wzqDqkOWa/dqwJNt/WkK8vecWhbKSLLVDLBYcMSaklU2
Cw5pHGeNZSF9uB+8T7mGzrmhDg2643hcrBN60EMDEQvmtQp8ftkTl8rpToOTZ3yT
qlSW1BdCFKgiZJc3uel0q83kAqlnXz/AQ8lFp967Zjmp0DVGO1Aj4ihIcVL3njM8
xvNwVe+E1aCKb2T07M1MoECtUC3+U/ypD2Rl17FJHaaTaLlr0buTvvHwRNbDCZHb
MRcwQoVNJZyX3cq6+AZqzDSyBBlYzFWVw1jgI3QtdqRXAADJGUv8dddUDrTK//O9
gP8XQ5M70TXnovTtZDXt6JyggTDMyq8CAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsG
A1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2Y2Eu
aXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRC
MDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJCMjdB
OUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQAqmiey
BVEG0EZVTsb41feExEANDiKAc1gL/coqZRqf4+zWHX8tARshK+HsgEM2d+pOkGAW
XK6T79gqUyXLaO1dwVNeg4zaPGQaTddGvwExkyBvxMSBggGqDNdoXc2DPJQsLFGL
0q7JPvzEFlupjlk1SM9XWJ24KzWBl1HZZPQASBmi/O4CoHTqy5b4gqi7EDzYN4gz
kNFwuU7eTKEPlqbgPY4beDsCMvF4NUu1YSo3hUJz9vKKIL9xIXw1Snf2YbwlVJyl
sjtzndM+oxfc5VvoGC96xILWP8yEgKadsPZrcO7J4JRv3eqU24pNgQnqtWQ5ghiJ
nCl6uF83dT49XUlC
-----END CERTIFICATE-----

View File

@@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCP6fWm1vXXybH MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTgCdvV8bseMM6
m3Ne6PjacGrN2+iMrzWZlzdHCZ31udDPqSUYaZ+78b441KZK/CJFQWeSJ/1h//A+ g6pDlmv3asCTbf1pCvL3nFoWykiy1QywWHDEmpJVNgsOaRxnjWUhfbgfvE+5hs65
BGsQDKvE/fj2QzN1KkOuQ8WJXNGpixE5uu5bv/QTN/ukurGdA1aO2aFCANumlOmB oQ4NuuN4XKwTetBDAxEL5rUKfH7ZE5fK6U6Dk2d8k6pUltQXQhSoImSXN7npdKvN
HkB/B2so57ii8iQQjwK2xM4r3oOU/IfcFGKL+9/QjLGFFp9PJXCDBCgrxxlZGaj1 5AKpZ18/wEPJRafeu2Y5qdA1RjtQI+IoSHFS954zPMbzcFXvhNWgim9k9OzNTKBA
3wowlfVOzlaX94gemQsCYVkuAFIYMAnFHs9cKNZQIU80somW/yy2Gy38N6n7NnbD rVAt/lP8qQ9kZdexSR2mk2i5a9G7k77x8ETWwwmR2zEXMEKFTSWcl93KuvgGasw0
nvFSaq4GoDROqRgKbRZ5e706d/p7A3aS/2oRqq1jomUIugK8g++LmoHFTgfhfQkI sgQZWMxVlcNY4CN0LXakVwAAyRlL/HXXVA60yv/zvYD/F0OTO9E156L07WQ17eic
v1aG/nPzAgMBAAECggEAD2RN31J2J42xm/V0YdviBCUOQXugZK1peN8jkSxw6Myt oIEwzMqvAgMBAAECggEAB3o3arNSwBeA53eyNFOEG6o1hsDTvbv6XRC1Cqs6KMBp
gBbuCo4sCw9vvD8VYjGyYXx6QXmLuV03YyKkfSQT5EsflBvlEu6jaEaUe3rwXhfX g26NBhZk6AhSGb9TlsGj+qwkL1MMCoYelud/xLJ/ykiUeNfVtxjLqH0Ol4FRRsFv
6JQoWPrP00oHVZk5g7CFBlK2VW2N+hgonIOSJr6mvhoGZlr7gphiZasYjx9Vm9N3 k2fuM66pcPkTg4wVkYDvlAYrQkFhHETYsh3GjlCuSGW2v83rom9DrPQ4/TkvbaTW
Pbnfru5ttzplYNniwH3DF6ph8VmdbD1nnbWSKLXvHCsXQT2wBcnsIagIH3vyq6K1 md3at3pfEN/UPhL6jYnIEYfZoFFrE3aMCCAc5v2VUMZlX8R6ziK+CXyAQHegzGFH
pc5abWsQJrixOPebpI8jD5w0HxHAqVLx58H/OC2zW/roAw1WS2AkueJ1j7dQ7Z0C zJp6XDvxuVjo/axD4LvKCvpzZIO9DUo1J5NA3/tar60AYCjMF9vBpkWRSwwekNbX
mc9Xexz5gcAP0nMAQv+LP7iYqsa/niFhfcTFWfdxkQKBgQD5JkKNmInU2/IVYCwO lT1/r2/iRDhNyHWSz7Wo0CnrhNhRfV4HB6p8pzZIUQKBgQD/9+LvfFuLRqIEh/R8
c483MCSv1+MnbRXlb7vut8T0IupHTU6hCge6C3q3HsjbKSBn8bRChtPUzvw9JFxK wNaZTWRDkp7paSc9kN7YXrKDkn20iDV4huk2Z+fL53Asvu5jMJzWJD7QU9bnasdE
QWKiQqQDPLDJ08AIKhfQD2JiLtoikkZN0bF6OTL+Soney1yGx51mlfHM194+PcCJ fL9oUGOldqj15mpi+2dpLwwSnkNIah3Nxwr/DmCEKq6YjzRDIw1QWPjCe+2NgFTD
jF7iWdMVbcBwHbgydNxxIS5cKQKBgQDHlvQ4lw6gvLILpGK494/vNYSJP/Jmd66V GACilmpw2G0ZsI3CgOenqLCVmQKBgQDThtuqIS3bivHr3QprMKHni9aaSm5jMnfd
3oSGYi84YRKTSwH4NlbBVVieb3Dv+pPugbsXEuFHBif7WsivbYgNTE9++8Yvt0gh eYqD12sop6HkSetdOCAFttJP7J+wegpvUwdPdgvL8A2ICNwrjfL4i3V6LtJOBuRo
duB1G4yh7m/ylQeSuipgQU9tozrU/15cWwmcCRV50wWXBGoVEM0kf7mzEKSxmjYk lbl8LHTegVQFd2nOXe57VtmxzADpmEao6rzc+IpYamFaRl8kvswx2MBL/c20vAKF
Qzko/zxSuwKBgQCY6Bc+SViFz3qSDdTcBaXma+CIHsmlH7ipd9px1kzEvEzl95cD moE9OY9/hwKBgQCE9UQh1dzPWZ8q71vluV0/QF4GY5C7+Wzyo9+9UGM1yNAXCHGN
FGHLl1H34qfIgUQHJvrHPXHyEBoT+CW/2MMM7DM2XV/ubctT92ln4pkxwqlTQExv 67YUFqDm3477DypQx52P9j/SgtosVuCvCIV8L9WyyxI0uL0mMSSkFFYXDbIxWT2l
Y/s1FLesAtj8Z/hgK0/5bprYab9WmZV5lTGCXzhB1XqeFE9AgCHuODv4iQKBgQC8 9/AUUGl9/ZW3lRs2jGyMAcslaq4YRELDHCKoUa10dFcncvgPwlmSUyN2cQKBgC/p
g2uwd5ytXQydymokYk9klJvWNrvw5GHV1BJAC0Smb6lnzZTSqCBRAxdsrb1yLK7E 40YVP+rz/TqdFigTmwj07waCB6EmpGohhtO4bwiFeDFa4Rp7hR1mPRtnkQCLlGOF
u2vGY2K7/qiM1DZw23eBd+4t9gg+0VIjqXBfq+GsoNTDvtckUwnrWER5PY831ut9 HinMpn1qgdYnk5+2BqxwAotLoc2U2BYsnBhZhZuFFgAq/WeGdWew05znhelj2dWX
N89fvYS3SAUjmlvIAdKBAtKWusWTqiAxJ/05J7oGOQKBgB5PSr5i0LlupIbKui9t XnJ7RfMjHawyNoj/QwejfmN4OSlQUQ3sYov4xt1vAoGBAKEq1xI1J1p/t+4j6Esc
XtXnRqGPxxrZZUpTkyrGOAnlCz/zq2QiwFpBWo/NMHOp0KmxzJpQ8yEY2LWlRZ61 frDvu+xW8IJSMHGNBz1Z4xA5KX2DmKH+aCjzTHMO6CJ353RH/2TNP44S5bHobAzK
Oc9m0J/HtPw3Ohi1treBosEVG/0NOI9Tq1Obny23N51MVibdW6zEIyGUp/DbFS8h GqNisApHHCMf7Q+R1dOdXhiAKa1Cm6CUztfDXr2mTEcyqkrVAqTMrP2TnX9cuRVE
5DljdOYX9IYIHHn3Ig4GeTGe xAft+Nal9d1gfPoGL5AcXLHI
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@@ -1,21 +1,21 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDsjCCApqgAwIBAgIQICYBIfYzsX0sOqARgYshuDANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE MIIDsjCCApqgAwIBAgIQICYBJSGeFVvaAxyDb0s3iTANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0 BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0
aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs
YXNzIDIgUjEwHhcNMjYwMTIxMDk0NDI2WhcNMzEwMTIwMDk0NDI2WjCBkjELMAkGA1UEBhMCQ04x YXNzIDIgUjEwHhcNMjYwMTI1MDc1MTEyWhcNMzEwMTI0MDc1MTEyWjCBkjELMAkGA1UEBhMCQ04x
LTArBgNVBAoMJOW5v+ilv+emj+mTree9kee7nOenkeaKgOaciemZkOWFrOWPuDEPMA0GA1UECwwG LTArBgNVBAoMJOW5v+ilv+emj+mTree9kee7nOenkeaKgOaciemZkOWFrOWPuDEPMA0GA1UECwwG
QWxpcGF5MUMwQQYDVQQDDDrmlK/ku5jlrp0o5Lit5Zu9Kee9kee7nOaKgOacr+aciemZkOWFrOWP QWxpcGF5MUMwQQYDVQQDDDrmlK/ku5jlrp0o5Lit5Zu9Kee9kee7nOaKgOacr+aciemZkOWFrOWP
uC0yMDg4MjUxNjk3MTI0MTM5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhzfJRbBY uC0yMDg4ODQxMzMwNzE0NjM4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomw6g4rB
47FJpj2SQ2I4u1Z3KdyrtPG+jfX+wrVoubzVNC5f6AoULEeJcmDDXwj1t7Arz7F7F9Xbrn7JoSsn mCr/QoX3NI3DVLyDpkaUytZ2uFhdfQaegIDAuUfZfgpTCASlAtO82t8ISAbSOSyp9CUpwdGV4EYO
x6nuxVW8XXK/J9emy/SK7LS+Oofe2vwwvRSos8iYleYFYDYlYQB/+kK/Zsn9Qv4M5SfpWK7nU7AD iCBbLxMYB6taaHPiIjJ1zNT1EakJzWgU53hz1AVeABB9kdAvMqSvjH6KLoVupmqm4Li8ZwDW9M2A
A4lvaV7VCszSYYFsqtZ1uEdqJIpUdTuR3IxM29AE8oqSpAV1OIhlLGN7NXsRrl3kjYijxnmwUEcl NAmyDfKgiF0Lt4aUUnaZktoCrTWTkpmtfRZCHNACj851IllvN2wyC4OL7dJq5UzOFxmn07Dy/2z4
2ODJnIHF3oMmCdMU3hMr14CsEd95VVfVsc3UR2VxEylTO0P0ZZfRi17P9KI3aWNaNVLcFcYvpBkM UAhaaSAyRVawpOui5AIYJTXZERLYL3KMyRnMuZoFq3xltzVTzRPM06nRa9RfeVNVwWVtGBIe/r8t
/8GawCmi0aWkPRgw9SinJtjh8x082QIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCA/gwDQYJKoZIhvcN cg5wyhI57KUszGNOmUIm/se6G2lnAQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCA/gwDQYJKoZIhvcN
AQELBQADggEBAISexDU5a00sssj4Oy4atS2kcUktdloapzp8w8t37Svd5iCSOtcN9qONmBTKjybs AQELBQADggEBAFWBZ6o+6o1l0HRt3ZzMcFb9IqeWslnmbaE8bfvnk1qaxQmSKQGuTZOkK9jigXs5
zEHF4LJZRaum/V3i2WHXDX3WzJBhvmhk+Ryyp5dr+IiA3qR1+jdkQrHJVPD5phvrsk9Nj9Fw4jCW APgs0o5Wtoxbg1+zKbXRjL1+l7l1zEvMtx3goUruXoEPTgBNpJPh0JAwWaX0r+9H1SgOK+o3zbHD
3tSQLD7Y2oVPymiKhIRisoUNg39SHpAMak283KV7vNR9SRdUqTEJLmN7C5WasZ68X+6ZY0a/+3T6 xRjfAYYPbJeIyta8Rkvu2m7U5lVxQlDMZREpslAMZXLvbn0KP9OGwEoYqJNmklCncY4VpxpBYEci
dDKIfFqztBYtdZ2CJs27fw6WBr8/zV64fkk0XinxiOhRQdF5D+GgtDhwa+d6I8QtNJTL+FBZgkFZ cSairY4cFp8TSifV4jYkkG5VAWQLOjOFoi9n404ZKON2s1+R/uBDOrGelivu4h/rJA0en7aZivCG
H59XbYUpUd8BCfjuNImlXHxAGw65LoafjQR7Dbi11eghynqdcOU= UQVfym1r1HyaZLAdxWBOGY22NaEcJmQGjKe2uEdNNgvp7kFmOmE=
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIE4jCCAsqgAwIBAgIIYsSr5bKAMl8wDQYJKoZIhvcNAQELBQAwejELMAkGA1UEBhMCQ04xFjAU MIIE4jCCAsqgAwIBAgIIYsSr5bKAMl8wDQYJKoZIhvcNAQELBQAwejELMAkGA1UEBhMCQ04xFjAU

View File

@@ -1,9 +1,9 @@
-----BEGIN PUBLIC KEY----- -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvwSy7dS/ICZV38tI0HxM MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4/k/xM+ybCzcWPi73Es5
SAIE7+Ug92qryuNlkNyaNDRjfsykHsrPCSsUUQEZblBNmZOLfLQxmAaWC+cQqWCv XHPPqR2oGFdI7TPvbSnNcCpUAdewiuLFJZ3x7/6uTPKRuaxJPA9n0wjQ9x3Crwpt
zfy4rXGAHE1widWFkHGzQzaw6cB0VdDXatK9yAt1PgXdp5jzBRzOn9Z3u4t0s771 t+XZW8+gHU8kq1vG1cg+oBZedyS3434i+FCovK8iFQnsr5W+ROtVq8pxys0w5P3Z
2zjuxCnLxMq84DovNgh2y0LBiuorWbtuTFTd8SXUGk2Jyuojq/02U3KTuyh+7SmW rYsdTZ7TxQ/3h/5hd7poehFUIIX1ZgO8tjCqq9wsvb70FRn4hf8NvyuxMtg+Va8U
ffJXKrzhrKwSpGh59e/fFxqX2xGlVoJ1kdohMZPo/7k+e5jP7qjrf93l7JVeUKYa XdvDiUCa8NeCkvxpfPjEoZTNPsNbEYyYEJ1NSlte/5bLUBKm0rU6QGITuzXdPcF/
V27hNVowJ4oho21WVCJ1AYo41IbPJWI+6WxlaVeoR4zKix0Mb2timaWayyLoN53y R5PweiMXFNy0l5A4eNFyO4I0evC36mB58geZ2rgdq92LZMQQTS6Rf63AtC2ECs5B
aQIDAQAB wQIDAQAB
-----END PUBLIC KEY----- -----END PUBLIC KEY-----

View File

@@ -11,20 +11,25 @@ type Config struct {
CacheRedis cache.CacheConf CacheRedis cache.CacheConf
JwtAuth JwtAuth // JWT 鉴权相关配置 JwtAuth JwtAuth // JWT 鉴权相关配置
VerifyCode VerifyCode VerifyCode VerifyCode
Captcha CaptchaConfig
Encrypt Encrypt Encrypt Encrypt
Alipay AlipayConfig Alipay AlipayConfig
Wxpay WxpayConfig Wxpay WxpayConfig
Applepay ApplepayConfig Applepay ApplepayConfig
Ali AliConfig Ali AliConfig
Tianyuanapi TianyuanapiConfig Tianyuanapi TianyuanapiConfig
Tianxingjuhe TianxingjuheConfig
SystemConfig SystemConfig SystemConfig SystemConfig
WechatH5 WechatH5Config WechatH5 WechatH5Config
Authorization AuthorizationConfig // 授权书配置 Authorization AuthorizationConfig // 授权书配置
Upload UploadConfig // 图片上传(行驶证等)配置
WechatMini WechatMiniConfig WechatMini WechatMiniConfig
Query QueryConfig Query QueryConfig
AdminConfig AdminConfig AdminConfig AdminConfig
AdminPromotion AdminPromotion AdminPromotion AdminPromotion
TaxConfig TaxConfig TaxConfig TaxConfig
// PublicBaseURL 前台访问域名,用于构造对外可访问链接(上传图片、回调等),例如 https://www.tianyuancha.cn
PublicBaseURL string
ExtensionTime int64 ExtensionTime int64
} }
@@ -42,6 +47,13 @@ type VerifyCode struct {
TemplateCode string TemplateCode string
ValidTime int ValidTime int
} }
type CaptchaConfig struct {
AccessKeyID string
AccessKeySecret string
EndpointURL string
SceneID string
EKey string
}
type Encrypt struct { type Encrypt struct {
SecretKey string SecretKey string
} }
@@ -53,6 +65,13 @@ type AlipayConfig struct {
AppCertPath string // 应用公钥证书路径 AppCertPath string // 应用公钥证书路径
AlipayCertPath string // 支付宝公钥证书路径 AlipayCertPath string // 支付宝公钥证书路径
AlipayRootCertPath string // 根证书路径 AlipayRootCertPath string // 根证书路径
// Bak 仅用于 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单退款
AppIDBak string
PrivateKeyBak string
AlipayPublicKeyBak string
AppCertPathBak string
AlipayCertPathBak string
AlipayRootCertPathBak string
IsProduction bool IsProduction bool
NotifyUrl string NotifyUrl string
ReturnURL string ReturnURL string
@@ -129,3 +148,16 @@ type TianyuanapiConfig struct {
type AuthorizationConfig struct { type AuthorizationConfig struct {
FileBaseURL string // 授权书文件访问基础URL FileBaseURL string // 授权书文件访问基础URL
} }
// UploadConfig 图片上传(行驶证等)配置,临时存储,按 hash 去重
type UploadConfig struct {
FileBaseURL string `json:"fileBaseURL,omitempty"` // 上传文件访问基础 URL如 https://xxx/api/v1/upload/file
TempFileMaxAgeH int `json:"tempFileMaxAgeH,omitempty"` // 临时文件保留时长小时超时删除0 表示默认 24 小时
}
// TianxingjuheConfig 天行聚合API配置
type TianxingjuheConfig struct {
URL string // API基础URL
Key string // API密钥
Timeout int // 超时时间默认30秒
}

View File

@@ -1,6 +1,7 @@
package auth package auth
import ( import (
"context"
"net/http" "net/http"
"tyc-server/app/main/api/internal/logic/auth" "tyc-server/app/main/api/internal/logic/auth"
@@ -23,7 +24,8 @@ func SendSmsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
result.ParamValidateErrorResult(r, w, err) result.ParamValidateErrorResult(r, w, err)
return return
} }
l := auth.NewSendSmsLogic(r.Context(), svcCtx) ctx := context.WithValue(r.Context(), auth.UserAgentContextKey, r.Header.Get("User-Agent"))
l := auth.NewSendSmsLogic(ctx, svcCtx)
err := l.SendSms(&req) err := l.SendSms(&req)
result.HttpResult(r, w, nil, err) result.HttpResult(r, w, nil, err)
} }

View File

@@ -0,0 +1,17 @@
package captcha
import (
"net/http"
"tyc-server/app/main/api/internal/logic/captcha"
"tyc-server/app/main/api/internal/svc"
"tyc-server/common/result"
)
func GetEncryptedSceneIdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := captcha.NewGetEncryptedSceneIdLogic(r.Context(), svcCtx)
resp, err := l.GetEncryptedSceneId()
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -22,10 +22,14 @@ import (
app "tyc-server/app/main/api/internal/handler/app" app "tyc-server/app/main/api/internal/handler/app"
auth "tyc-server/app/main/api/internal/handler/auth" auth "tyc-server/app/main/api/internal/handler/auth"
authorization "tyc-server/app/main/api/internal/handler/authorization" authorization "tyc-server/app/main/api/internal/handler/authorization"
captcha "tyc-server/app/main/api/internal/handler/captcha"
notification "tyc-server/app/main/api/internal/handler/notification" notification "tyc-server/app/main/api/internal/handler/notification"
pay "tyc-server/app/main/api/internal/handler/pay" pay "tyc-server/app/main/api/internal/handler/pay"
product "tyc-server/app/main/api/internal/handler/product" product "tyc-server/app/main/api/internal/handler/product"
query "tyc-server/app/main/api/internal/handler/query" query "tyc-server/app/main/api/internal/handler/query"
tianyuan "tyc-server/app/main/api/internal/handler/tianyuan"
toolbox "tyc-server/app/main/api/internal/handler/toolbox"
upload "tyc-server/app/main/api/internal/handler/upload"
user "tyc-server/app/main/api/internal/handler/user" user "tyc-server/app/main/api/internal/handler/user"
"tyc-server/app/main/api/internal/svc" "tyc-server/app/main/api/internal/svc"
@@ -952,6 +956,18 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1"), rest.WithPrefix("/api/v1"),
) )
server.AddRoutes(
[]rest.Route{
{
// get encrypted scene id for aliyun captcha
Method: http.MethodPost,
Path: "/captcha/encryptedSceneId",
Handler: captcha.GetEncryptedSceneIdHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1"),
)
server.AddRoutes( server.AddRoutes(
[]rest.Route{ []rest.Route{
{ {
@@ -1148,6 +1164,54 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1"), rest.WithPrefix("/api/v1"),
) )
server.AddRoutes(
[]rest.Route{
{
// 天远车辆类接口异步回调
Method: http.MethodPost,
Path: "/tianyuan/vehicle/callback",
Handler: tianyuan.VehicleCallbackHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1"),
)
server.AddRoutes(
[]rest.Route{
{
// 获取工具列表
Method: http.MethodGet,
Path: "/toolbox/list",
Handler: toolbox.ToolboxListHandler(serverCtx),
},
{
// 通用工具查询
Method: http.MethodPost,
Path: "/toolbox/query",
Handler: toolbox.ToolboxQueryHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1"),
)
server.AddRoutes(
[]rest.Route{
{
// 访问已上传文件(供第三方或前端通过返回的 URL 拉取)
Method: http.MethodGet,
Path: "/upload/file/:fileName",
Handler: upload.ServeUploadedFileHandler(serverCtx),
},
{
// 上传图片,返回可访问 URL如行驶证限制 3MB
Method: http.MethodPost,
Path: "/upload/image",
Handler: upload.UploadImageHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1"),
)
server.AddRoutes( server.AddRoutes(
[]rest.Route{ []rest.Route{
{ {

View File

@@ -0,0 +1,33 @@
package tianyuan
import (
"net/http"
tianyuanlogic "tyc-server/app/main/api/internal/logic/tianyuan"
"tyc-server/app/main/api/internal/svc"
"tyc-server/common/result"
"github.com/zeromicro/go-zero/rest/httpx"
)
// VehicleCallbackHandler 天远车辆类接口异步回调入口
// 约定:第三方在回调 URL 上携带 order_no / api_id 等标识,例如:/api/v1/tianyuan/vehicle/callback?order_no=Q_xxx&api_id=QCXG1U4U
// 回调 Body 为该接口最终的 JSON 结果。
func VehicleCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := tianyuanlogic.NewVehicleCallbackLogic(r.Context(), svcCtx)
if err := l.Handle(r); err != nil {
// 对第三方尽量返回 200避免无限重试这里使用统一 result 封装错误
result.HttpResult(r, w, map[string]interface{}{
"code": 500,
"msg": "fail",
}, err)
return
}
httpx.OkJson(w, map[string]interface{}{
"code": 200,
"msg": "success",
})
}
}

View File

@@ -0,0 +1,17 @@
package toolbox
import (
"net/http"
"tyc-server/app/main/api/internal/logic/toolbox"
"tyc-server/app/main/api/internal/svc"
"tyc-server/common/result"
)
func ToolboxListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := toolbox.NewToolboxListLogic(r.Context(), svcCtx)
resp, err := l.ToolboxList()
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -0,0 +1,29 @@
package toolbox
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"tyc-server/app/main/api/internal/logic/toolbox"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"tyc-server/common/result"
"tyc-server/pkg/lzkit/validator"
)
func ToolboxQueryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ToolboxQueryReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := toolbox.NewToolboxQueryLogic(r.Context(), svcCtx)
resp, err := l.ToolboxQuery(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -0,0 +1,47 @@
package upload
import (
"net/http"
"os"
"tyc-server/app/main/api/internal/logic/upload"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"tyc-server/common/result"
"tyc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ServeUploadedFileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ServeUploadedFileReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := upload.NewServeUploadedFileLogic(r.Context(), svcCtx)
resp, err := l.ServeUploadedFile(&req)
if err != nil {
result.HttpResult(r, w, nil, err)
return
}
if resp != nil && resp.FilePath != "" {
f, openErr := os.Open(resp.FilePath)
if openErr != nil {
result.HttpResult(r, w, nil, openErr)
return
}
defer f.Close()
w.Header().Set("Content-Type", resp.ContentType)
w.WriteHeader(http.StatusOK)
_, _ = f.WriteTo(w)
return
}
httpx.OkJson(w, resp)
}
}

View File

@@ -0,0 +1,29 @@
package upload
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"tyc-server/app/main/api/internal/logic/upload"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"tyc-server/common/result"
"tyc-server/pkg/lzkit/validator"
)
func UploadImageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UploadImageReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := upload.NewUploadImageLogic(r.Context(), svcCtx)
resp, err := l.UploadImage(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -1,11 +1,14 @@
package user package user
import ( import (
"context"
"net/http" "net/http"
"strings"
"tyc-server/app/main/api/internal/logic/user" "tyc-server/app/main/api/internal/logic/user"
"tyc-server/app/main/api/internal/svc" "tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types" "tyc-server/app/main/api/internal/types"
jwtx "tyc-server/common/jwt"
"tyc-server/common/result" "tyc-server/common/result"
"tyc-server/pkg/lzkit/validator" "tyc-server/pkg/lzkit/validator"
@@ -23,7 +26,14 @@ func WxH5AuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
result.ParamValidateErrorResult(r, w, err) result.ParamValidateErrorResult(r, w, err)
return return
} }
l := user.NewWxH5AuthLogic(r.Context(), svcCtx) ctx := r.Context()
// 本路由未挂 AuthInterceptor若前端已带登录态如手机号登录后再走静默授权需解析 JWT 以便把 openid 写入当前正式用户 user_auth
if authHeader := strings.TrimSpace(r.Header.Get("Authorization")); authHeader != "" {
if claims, err := jwtx.ParseJwtToken(authHeader, svcCtx.Config.JwtAuth.AccessSecret); err == nil {
ctx = context.WithValue(ctx, jwtx.ExtraKey, claims)
}
}
l := user.NewWxH5AuthLogic(ctx, svcCtx)
resp, err := l.WxH5Auth(&req) resp, err := l.WxH5Auth(&req)
result.HttpResult(r, w, resp, err) result.HttpResult(r, w, resp, err)
} }

View File

@@ -65,6 +65,12 @@ func (l *AdminGetAgentWithdrawalListLogic) AdminGetAgentWithdrawalList(req *type
item.PayeeName = v.PayeeName.String item.PayeeName = v.PayeeName.String
} }
// 从扣税记录中取扣税比例
taxModel, taxErr := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(l.ctx, v.Id)
if taxErr == nil {
item.TaxRate = taxModel.TaxRate
}
items = append(items, item) items = append(items, item)
} }
resp = &types.AdminGetAgentWithdrawalListResp{ resp = &types.AdminGetAgentWithdrawalListResp{

View File

@@ -3,6 +3,7 @@ package admin_agent
import ( import (
"context" "context"
"database/sql" "database/sql"
"math"
"time" "time"
"tyc-server/app/main/model" "tyc-server/app/main/model"
"tyc-server/common/xerr" "tyc-server/common/xerr"
@@ -85,9 +86,18 @@ func (l *AdminReviewBankCardWithdrawalLogic) AdminReviewBankCardWithdrawal(req *
return errors.Wrapf(xerr.NewErrMsg("提现类型不正确"), "提现类型验证失败") return errors.Wrapf(xerr.NewErrMsg("提现类型不正确"), "提现类型验证失败")
} }
// 确认时使用的扣税比例:请求中传入则用传入值,否则默认 6%
taxRate := 0.06
if req.Action == ReviewActionApprove && req.TaxRate != nil {
taxRate = *req.TaxRate
if taxRate < 0 || taxRate > 1 {
return errors.Wrapf(xerr.NewErrMsg("扣税比例必须在 0100% 之间"), "扣税比例验证失败")
}
}
if req.Action == ReviewActionApprove { if req.Action == ReviewActionApprove {
// 确认提现 // 确认提现(可带自定义扣税比例)
return l.approveWithdrawal(ctx, session, record) return l.approveWithdrawal(ctx, session, record, taxRate)
} else { } else {
// 拒绝提现 // 拒绝提现
return l.rejectWithdrawal(ctx, session, record, req.Remark) return l.rejectWithdrawal(ctx, session, record, req.Remark)
@@ -102,18 +112,48 @@ func (l *AdminReviewBankCardWithdrawalLogic) AdminReviewBankCardWithdrawal(req *
return resp, nil return resp, nil
} }
// 确认提现 // 确认提现taxRate 为扣税比例,如 0.06 表示 6%
func (l *AdminReviewBankCardWithdrawalLogic) approveWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error { func (l *AdminReviewBankCardWithdrawalLogic) approveWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, taxRate float64) error {
// 按审核时选择的扣税比例重新计算并更新提现记录与扣税记录
if err := l.applyReviewTaxRate(ctx, session, record, taxRate); err != nil {
return err
}
// 根据提现类型执行不同的操作 // 根据提现类型执行不同的操作
if record.WithdrawType == WithdrawTypeAlipay { if record.WithdrawType == WithdrawTypeAlipay {
// 支付宝提现:调用支付宝转账接口 // 支付宝提现:按更新后的实际到账金额调用支付宝转账
return l.approveAlipayWithdrawal(ctx, session, record) return l.approveAlipayWithdrawal(ctx, session, record)
} else { } else {
// 银行卡提现:直接更新状态为成功(线下转账) // 银行卡提现:直接更新状态为成功(线下转账)
return l.approveBankCardWithdrawal(ctx, session, record) return l.approveBankCardWithdrawal(ctx, session, record, taxRate)
} }
} }
// applyReviewTaxRate 按审核时选择的扣税比例更新提现记录与扣税记录
func (l *AdminReviewBankCardWithdrawalLogic) applyReviewTaxRate(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, taxRate float64) error {
// 金额保留两位小数,避免浮点误差并与前端/支付宝一致
newTaxAmount := math.Round(record.Amount*taxRate*100) / 100
newActualAmount := math.Round((record.Amount-newTaxAmount)*100) / 100
record.TaxAmount = newTaxAmount
record.ActualAmount = newActualAmount
if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录扣税金额失败: %v", err)
}
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
taxModel.TaxRate = taxRate
taxModel.TaxAmount = newTaxAmount
taxModel.ActualAmount = newActualAmount
taxModel.TaxableAmount = record.Amount
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
}
return nil
}
// 确认支付宝提现 // 确认支付宝提现
func (l *AdminReviewBankCardWithdrawalLogic) approveAlipayWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error { func (l *AdminReviewBankCardWithdrawalLogic) approveAlipayWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error {
// 同步调用支付宝转账 // 同步调用支付宝转账
@@ -146,7 +186,7 @@ func (l *AdminReviewBankCardWithdrawalLogic) approveAlipayWithdrawal(ctx context
} }
// 确认银行卡提现 // 确认银行卡提现
func (l *AdminReviewBankCardWithdrawalLogic) approveBankCardWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error { func (l *AdminReviewBankCardWithdrawalLogic) approveBankCardWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, taxRate float64) error {
// 更新提现记录状态为成功 // 更新提现记录状态为成功
record.Status = StatusSuccess record.Status = StatusSuccess
record.Remark = sql.NullString{String: "管理员确认提现", Valid: true} record.Remark = sql.NullString{String: "管理员确认提现", Valid: true}
@@ -193,10 +233,13 @@ func (l *AdminReviewBankCardWithdrawalLogic) approveBankCardWithdrawal(ctx conte
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
} }
// 注意:由于在 applyReviewTaxRate 中已经更新过 tax 记录version 已经递增)
// 这里只需要更新 tax_status 和 tax_time不需要再次调用 UpdateWithVersion
// 使用普通 Update 方法避免乐观锁冲突
if taxModel.TaxStatus == model.TaxStatusPending { if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功 taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { if _, err := l.svcCtx.AgentWithdrawalTaxModel.Update(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
} }
} }
@@ -264,10 +307,12 @@ func (l *AdminReviewBankCardWithdrawalLogic) rejectWithdrawal(ctx context.Contex
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
} }
// 注意:由于在 applyReviewTaxRate 中已经更新过 tax 记录version 已经递增)
// 这里只需要更新 tax_status 和 tax_time使用普通 Update 方法避免乐观锁冲突
if taxModel.TaxStatus == model.TaxStatusPending { if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败 taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { if _, err := l.svcCtx.AgentWithdrawalTaxModel.Update(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
} }
} }
@@ -324,10 +369,12 @@ func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalSuccess(ctx conte
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
} }
// 注意:由于在 applyReviewTaxRate 中已经更新过 tax 记录version 已经递增)
// 这里只需要更新 tax_status 和 tax_time使用普通 Update 方法避免乐观锁冲突
if taxModel.TaxStatus == model.TaxStatusPending { if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功 taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { if _, err := l.svcCtx.AgentWithdrawalTaxModel.Update(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
} }
} }
@@ -395,10 +442,12 @@ func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalFailure(ctx conte
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
} }
// 注意:由于在 applyReviewTaxRate 中已经更新过 tax 记录version 已经递增)
// 这里只需要更新 tax_status 和 tax_time使用普通 Update 方法避免乐观锁冲突
if taxModel.TaxStatus == model.TaxStatusPending { if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败 taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { if _, err := l.svcCtx.AgentWithdrawalTaxModel.Update(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
} }
} }

View File

@@ -75,16 +75,19 @@ func (l *AdminRefundOrderLogic) getAndValidateOrder(orderId int64, refundAmount
return order, nil return order, nil
} }
// handleAlipayRefund 处理支付宝退款 // handleAlipayRefund 处理支付宝退款(仅 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单走 bak 商户号)
func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) { func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
// 调用支付宝退款接口 orderPayTime := &order.CreateTime
refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.OrderNo, req.RefundAmount) if order.PayTime.Valid {
orderPayTime = &order.PayTime.Time
}
refundNo := l.generateRefundNo(order.OrderNo)
// 按订单记录的商户号 payment_merchant 选择支付宝商户;老订单未写入时由 AliRefund 内部按时间区间兜底。
refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.PaymentMerchant, order.OrderNo, req.RefundAmount, orderPayTime, refundNo)
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 支付宝退款失败 err: %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 支付宝退款失败 err: %v", err)
} }
refundNo := l.generateRefundNo(order.OrderNo)
if refundResp.IsSuccess() { if refundResp.IsSuccess() {
// 支付宝退款成功,创建成功记录 // 支付宝退款成功,创建成功记录
err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, model.OrderStatusRefunded, model.OrderRefundStatusSuccess) err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, model.OrderStatusRefunded, model.OrderRefundStatusSuccess)
@@ -144,6 +147,7 @@ func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Or
OrderId: order.Id, OrderId: order.Id,
UserId: order.UserId, UserId: order.UserId,
ProductId: order.ProductId, ProductId: order.ProductId,
PaymentMerchant: order.PaymentMerchant,
RefundAmount: req.RefundAmount, RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason), RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus, // 使用传入的状态,不再硬编码 Status: refundStatus, // 使用传入的状态,不再硬编码
@@ -172,6 +176,7 @@ func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *
OrderId: order.Id, OrderId: order.Id,
UserId: order.UserId, UserId: order.UserId,
ProductId: order.ProductId, ProductId: order.ProductId,
PaymentMerchant: order.PaymentMerchant,
RefundAmount: req.RefundAmount, RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason), RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus, Status: refundStatus,
@@ -185,9 +190,9 @@ func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *
return nil return nil
} }
// generateRefundNo 生成退款单号 // generateRefundNo 生成退款单号(同一订单多次退款需唯一,避免 unique_refund_no 冲突)
func (l *AdminRefundOrderLogic) generateRefundNo(orderNo string) string { func (l *AdminRefundOrderLogic) generateRefundNo(orderNo string) string {
return fmt.Sprintf("%s%s", RefundNoPrefix, orderNo) return fmt.Sprintf("%s%s-%d", RefundNoPrefix, orderNo, time.Now().UnixMilli())
} }
// createNullString 创建 sql.NullString // createNullString 创建 sql.NullString

View File

@@ -43,6 +43,7 @@ func (l *AdminGetPlatformUserDetailLogic) AdminGetPlatformUserDetail(req *types.
Nickname: "", Nickname: "",
Info: user.Info, Info: user.Info,
Inside: user.Inside, Inside: user.Inside,
Disable: user.Disable,
CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"), CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"), UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"),
} }

View File

@@ -31,7 +31,12 @@ func NewAdminGetPlatformUserListLogic(ctx context.Context, svcCtx *svc.ServiceCo
func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.AdminGetPlatformUserListReq) (resp *types.AdminGetPlatformUserListResp, err error) { func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.AdminGetPlatformUserListReq) (resp *types.AdminGetPlatformUserListResp, err error) {
builder := l.svcCtx.UserModel.SelectBuilder() builder := l.svcCtx.UserModel.SelectBuilder()
if req.Mobile != "" { if req.Mobile != "" {
builder = builder.Where("mobile = ?", req.Mobile) // 数据库手机号为密文,需加密明文手机号后再查询
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机号加密失败: %v", err)
}
builder = builder.Where("mobile = ?", encryptedMobile)
} }
if req.Nickname != "" { if req.Nickname != "" {
builder = builder.Where("nickname = ?", req.Nickname) builder = builder.Where("nickname = ?", req.Nickname)
@@ -72,6 +77,7 @@ func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.Admi
Nickname: "", Nickname: "",
Info: user.Info, Info: user.Info,
Inside: user.Inside, Inside: user.Inside,
Disable: user.Disable,
CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"), CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"), UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"),
} }

View File

@@ -52,6 +52,12 @@ func (l *AdminUpdatePlatformUserLogic) AdminUpdatePlatformUser(req *types.AdminU
} }
user.Inside = *req.Inside user.Inside = *req.Inside
} }
if req.Disable != nil {
if *req.Disable != 1 && *req.Disable != 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "封禁状态错误: %d", *req.Disable)
}
user.Disable = *req.Disable
}
if req.Password != nil { if req.Password != nil {
user.Password = sql.NullString{String: *req.Password, Valid: *req.Password != ""} user.Password = sql.NullString{String: *req.Password, Valid: *req.Password != ""}
} }

View File

@@ -72,7 +72,8 @@ func (l *AdminGetQueryDetailByOrderIdLogic) AdminGetQueryDetailByOrderId(req *ty
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err)
} }
query.ProductName = product.ProductName query.ProductName = product.ProductName
return &types.AdminGetQueryDetailByOrderIdResp{
resp = &types.AdminGetQueryDetailByOrderIdResp{
Id: query.Id, Id: query.Id,
OrderId: query.OrderId, OrderId: query.OrderId,
UserId: query.UserId, UserId: query.UserId,
@@ -82,7 +83,23 @@ func (l *AdminGetQueryDetailByOrderIdLogic) AdminGetQueryDetailByOrderId(req *ty
CreateTime: query.CreateTime, CreateTime: query.CreateTime,
UpdateTime: query.UpdateTime, UpdateTime: query.UpdateTime,
QueryState: query.QueryState, QueryState: query.QueryState,
}, nil }
// 从订单表补充:商户订单号、支付订单号、支付状态、支付时间、退款时间
orderModel, orderErr := l.svcCtx.OrderModel.FindOne(l.ctx, req.OrderId)
if orderErr == nil && orderModel != nil {
resp.OrderNo = orderModel.OrderNo
if orderModel.PlatformOrderId.Valid {
resp.PlatformOrderId = orderModel.PlatformOrderId.String
}
resp.PaymentStatus = orderModel.Status
if orderModel.PayTime.Valid {
resp.PayTime = orderModel.PayTime.Time.Format("2006-01-02 15:04:05")
}
if orderModel.RefundTime.Valid {
resp.RefundTime = orderModel.RefundTime.Time.Format("2006-01-02 15:04:05")
}
}
return resp, nil
} }
// ProcessQueryData 解密和反序列化 QueryData // ProcessQueryData 解密和反序列化 QueryData

View File

@@ -56,6 +56,11 @@ func (l *GetAgentWithdrawalLogic) GetAgentWithdrawal(req *types.GetWithdrawalReq
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理提现列表, %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理提现列表, %v", err)
} }
withdrawal.CreateTime = agentWithdrawalModel.CreateTime.Format("2006-01-02 15:04:05") withdrawal.CreateTime = agentWithdrawalModel.CreateTime.Format("2006-01-02 15:04:05")
// 从扣税记录中取扣税比例,供前端展示“扣了几个点”
taxModel, taxErr := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(l.ctx, agentWithdrawalModel.Id)
if taxErr == nil {
withdrawal.TaxRate = taxModel.TaxRate
}
list = append(list, withdrawal) list = append(list, withdrawal)
} }
} }

View File

@@ -6,6 +6,7 @@ import (
"math/rand" "math/rand"
"time" "time"
"tyc-server/common/xerr" "tyc-server/common/xerr"
"tyc-server/pkg/captcha"
"tyc-server/pkg/lzkit/crypto" "tyc-server/pkg/lzkit/crypto"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -34,7 +35,21 @@ func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLo
} }
} }
// UserAgentContextKey 用于从 context 读取 User-Agent如判断是否微信
const UserAgentContextKey = "user_agent"
func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error { func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
userAgent, _ := l.ctx.Value(UserAgentContextKey).(string)
cfg := l.svcCtx.Config.Captcha
if err := captcha.VerifyWithUserAgent(captcha.Config{
AccessKeyID: cfg.AccessKeyID,
AccessKeySecret: cfg.AccessKeySecret,
EndpointURL: cfg.EndpointURL,
SceneID: cfg.SceneID,
}, req.CaptchaVerifyParam, userAgent); err != nil {
return err
}
secretKey := l.svcCtx.Config.Encrypt.SecretKey secretKey := l.svcCtx.Config.Encrypt.SecretKey
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
if err != nil { if err != nil {

View File

@@ -0,0 +1,38 @@
package captcha
import (
"context"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"tyc-server/pkg/captcha"
"github.com/zeromicro/go-zero/core/logx"
)
type GetEncryptedSceneIdLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetEncryptedSceneIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetEncryptedSceneIdLogic {
return &GetEncryptedSceneIdLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetEncryptedSceneIdLogic) GetEncryptedSceneId() (resp *types.GetEncryptedSceneIdResp, err error) {
cfg := l.svcCtx.Config.Captcha
encrypted, genErr := captcha.GenerateEncryptedSceneID(cfg.SceneID, cfg.EKey, 3600)
if genErr != nil {
// 记录日志,返回通用错误
l.Errorf("generate encrypted scene id error: %+v", genErr)
return nil, genErr
}
return &types.GetEncryptedSceneIdResp{
EncryptedSceneId: encrypted,
}, nil
}

View File

@@ -32,22 +32,58 @@ func NewAlipayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Al
} }
func (l *AlipayCallbackLogic) AlipayCallback(w http.ResponseWriter, r *http.Request) error { func (l *AlipayCallbackLogic) AlipayCallback(w http.ResponseWriter, r *http.Request) error {
notification, err := l.svcCtx.AlipayService.HandleAliPaymentNotification(r) // 先解析表单,拿到 out_trade_no 用于查找订单和对应商户号
if err != nil { if err := r.ParseForm(); err != nil {
logx.Errorf("支付宝支付回调,%v", err) logx.Errorf("支付宝支付回调,解析请求表单失败: %v", err)
return nil return nil
} }
// 根据订单号前缀判断订单类型 orderNo := r.FormValue("out_trade_no")
orderNo := notification.OutTradeNo if orderNo == "" {
logx.Errorf("支付宝支付回调,缺少 out_trade_no")
return nil
}
// 根据订单号前缀判断订单类型,并查出对应商户标识
var merchant string
if strings.HasPrefix(orderNo, "Q_") { if strings.HasPrefix(orderNo, "Q_") {
// 查询订单处理 // 查询订单
return l.handleQueryOrderPayment(w, notification) order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
logx.Errorf("支付宝支付回调,查询订单失败: %v", err)
return nil
}
merchant = order.PaymentMerchant
} else if strings.HasPrefix(orderNo, "A_") { } else if strings.HasPrefix(orderNo, "A_") {
// 代理会员订单处理 // 代理会员订单
return l.handleAgentVipOrderPayment(w, notification) agentOrder, err := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
logx.Errorf("支付宝支付回调,查询代理会员订单失败: %v", err)
return nil
}
merchant = agentOrder.PaymentMerchant
} else { } else {
// 兼容旧订单,假设没有前缀的是查询订单 // 兼容旧订单,假设没有前缀的是查询订单
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
logx.Errorf("支付宝支付回调(旧订单),查询订单失败: %v", err)
return nil
}
merchant = order.PaymentMerchant
}
notification, err := l.svcCtx.AlipayService.HandleAliPaymentNotification(merchant, r.Form)
if err != nil {
logx.Errorf("支付宝支付回调,验签失败: %v", err)
return nil
}
// 再次根据订单号前缀分发到具体处理函数
if strings.HasPrefix(orderNo, "Q_") {
return l.handleQueryOrderPayment(w, notification)
} else if strings.HasPrefix(orderNo, "A_") {
return l.handleAgentVipOrderPayment(w, notification)
} else {
return l.handleQueryOrderPayment(w, notification) return l.handleQueryOrderPayment(w, notification)
} }
} }
@@ -218,7 +254,10 @@ func (l *AlipayCallbackLogic) handleRefund(order *model.AgentMembershipRechargeO
return refundErr return refundErr
} }
} else { } else {
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount) // 支付宝退款按订单记录的商户号 payment_merchant 走对应商户;
// 老订单若未写入商户号,则在 AliRefund 内按时间区间兜底。
orderPayTime := order.CreateTime
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.PaymentMerchant, order.OrderNo, order.Amount, &orderPayTime, "")
if refundErr != nil { if refundErr != nil {
return refundErr return refundErr
} }

View File

@@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
"tyc-server/app/main/api/internal/svc" "tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types" "tyc-server/app/main/api/internal/types"
@@ -32,6 +33,22 @@ type PaymentTypeResp struct {
outTradeNo string outTradeNo string
description string description string
orderID int64 orderID int64
payMerchantID string
}
// prepayDataMissing 判断第三方支付预创建是否未返回可用参数(含 interface 包了一层 nil 的情况)
func prepayDataMissing(v interface{}) bool {
if v == nil {
return true
}
switch t := v.(type) {
case string:
return t == ""
case map[string]string:
return len(t) == 0
default:
return false
}
} }
func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic { func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic {
@@ -43,10 +60,9 @@ func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLo
} }
func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, err error) { func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, err error) {
// 暂时关闭微信支付 req.PayMethod = strings.TrimSpace(req.PayMethod)
if req.PayMethod == "wechat" { req.PayType = strings.TrimSpace(req.PayType)
return nil, errors.Wrapf(xerr.NewErrMsg("微信支付暂时关闭"), "微信支付暂时关闭") req.Id = strings.TrimSpace(req.Id)
}
var paymentTypeResp *PaymentTypeResp var paymentTypeResp *PaymentTypeResp
var prepayData interface{} var prepayData interface{}
@@ -79,19 +95,53 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
if req.PayMethod == "wechat" { if req.PayMethod == "wechat" {
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo) prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
} else if req.PayMethod == "alipay" { } else if req.PayMethod == "alipay" {
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo) // 支付宝按订单写入的商户标识one/two创建支付订单
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.payMerchantID, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
} else if req.PayMethod == "appleiap" { } else if req.PayMethod == "appleiap" {
prepayData = l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo) iap := l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo)
prepayData = iap
if iap == "" {
createOrderErr = fmt.Errorf("获取 IAP 参数失败")
}
} else if req.PayMethod == "test" {
// 开发环境测试支付已在上方分支写入 prepayData若走到此处说明非 development
return errors.Wrapf(xerr.NewErrMsg("测试支付仅在开发环境可用"), "ENV!=development 且 pay_method=test")
} }
if createOrderErr != nil { if createOrderErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 创建支付订单失败: %+v", createOrderErr) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 创建支付订单失败: %+v", createOrderErr)
} }
// 在事务内校验并失败则回滚,避免订单已落库却无 prepay此前仅事务外校验会产生脏订单
if req.PayMethod == "wechat" || req.PayMethod == "alipay" {
if prepayDataMissing(prepayData) {
platformVal := l.ctx.Value("platform")
logx.WithContext(l.ctx).Errorf(
"[Payment] 事务内 prepay 为空将回滚: pay_method=%q pay_type=%q order_no=%s platform=%v prepayData_type=%T",
req.PayMethod, req.PayType, paymentTypeResp.outTradeNo, platformVal, prepayData,
)
return errors.Wrapf(
xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, "获取支付参数失败,请确认在微信内打开且已完成网页授权后重试"),
"创建支付失败: prepay 为空 platform=%v",
platformVal,
)
}
}
return nil return nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
if req.PayMethod == "wechat" || req.PayMethod == "alipay" {
if prepayDataMissing(prepayData) {
logx.WithContext(l.ctx).Errorf("[Payment] 事务提交后 prepay 仍为空(不应出现): pay_method=%q id=%q platform=%v",
req.PayMethod, req.Id, l.ctx.Value("platform"))
return nil, errors.Wrapf(
xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, "获取支付参数失败,请稍后重试"),
"创建支付失败: 未生成支付参数",
)
}
}
// 开发环境测试支付模式:事务提交后处理订单状态更新和后续流程(仅 pay_method=test 且 query 类型 orderID>0 // 开发环境测试支付模式:事务提交后处理订单状态更新和后续流程(仅 pay_method=test 且 query 类型 orderID>0
isDevTestPayment := os.Getenv("ENV") == "development" && req.PayMethod == "test" isDevTestPayment := os.Getenv("ENV") == "development" && req.PayMethod == "test"
if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != 0 { if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != 0 {
@@ -161,6 +211,11 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析缓存内容失败, %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析缓存内容失败, %v", err)
} }
// 代理推广链接订单仅支持支付宝,拒绝微信支付(与个人查询页选项一致)
if req.PayMethod == "wechat" && data.AgentIdentifier != "" {
return nil, errors.Wrapf(xerr.NewErrMsg("代理推广订单暂不支持微信支付"), "代理推广订单暂不支持微信支付")
}
product, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, data.Product) product, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, data.Product)
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 查找产品错误: %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 查找产品错误: %v", err)
@@ -186,12 +241,24 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
amount = 0.01 amount = 0.01
} }
var orderID int64 var orderID int64
// 默认支付宝商户号为 one若为代理推广订单存在 AgentIdentifier则使用 two。
paymentMerchant := ""
if req.PayMethod == "alipay" {
if data.AgentIdentifier != "" {
paymentMerchant = "two"
} else {
paymentMerchant = "one"
}
}
order := model.Order{ order := model.Order{
OrderNo: outTradeNo, OrderNo: outTradeNo,
UserId: userID, UserId: userID,
ProductId: product.Id, ProductId: product.Id,
PaymentPlatform: req.PayMethod, PaymentPlatform: req.PayMethod,
PaymentScene: "app", PaymentScene: "app",
PaymentMerchant: paymentMerchant,
Amount: amount, Amount: amount,
Status: "pending", Status: "pending",
} }
@@ -228,7 +295,13 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert)
} }
} }
return &PaymentTypeResp{amount: amount, outTradeNo: outTradeNo, description: product.ProductName, orderID: orderID}, nil return &PaymentTypeResp{
amount: amount,
outTradeNo: outTradeNo,
description: product.ProductName,
orderID: orderID,
payMerchantID: paymentMerchant,
}, nil
} }
func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) { func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx) userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
@@ -274,12 +347,19 @@ func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.
if user.Inside == 1 { if user.Inside == 1 {
amount = 0.01 amount = 0.01
} }
paymentMerchant := ""
if req.PayMethod == "alipay" {
paymentMerchant = "one"
}
agentMembershipRechargeOrder := model.AgentMembershipRechargeOrder{ agentMembershipRechargeOrder := model.AgentMembershipRechargeOrder{
OrderNo: req.Id, OrderNo: req.Id,
UserId: userID, UserId: userID,
AgentId: agentModel.Id, AgentId: agentModel.Id,
Amount: amount, Amount: amount,
PaymentMethod: req.PayMethod, PaymentMethod: req.PayMethod,
PaymentMerchant: paymentMerchant,
LevelName: agentVipCache.Type, LevelName: agentVipCache.Type,
Status: "pending", Status: "pending",
} }
@@ -287,7 +367,12 @@ func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理会员充值订单失败: %+v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理会员充值订单失败: %+v", err)
} }
return &PaymentTypeResp{amount: amount, outTradeNo: req.Id, description: fmt.Sprintf("%s代理会员充值", agentMembershipConfig.LevelName)}, nil return &PaymentTypeResp{
amount: amount,
outTradeNo: req.Id,
description: fmt.Sprintf("%s代理会员充值", agentMembershipConfig.LevelName),
payMerchantID: paymentMerchant,
}, nil
} }
func (l *PaymentLogic) agentParsing(agentIdentifier string) (*types.AgentIdentifier, error) { func (l *PaymentLogic) agentParsing(agentIdentifier string) (*types.AgentIdentifier, error) {
key, decodeErr := hex.DecodeString("8e3e7a2f60edb49221e953b9c029ed10") key, decodeErr := hex.DecodeString("8e3e7a2f60edb49221e953b9c029ed10")

View File

@@ -221,7 +221,10 @@ func (l *WechatPayCallbackLogic) handleRefund(order *model.AgentMembershipRechar
return refundErr return refundErr
} }
} else { } else {
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount) // 支付宝退款按订单记录的商户号 payment_merchant 走对应商户;
// 老订单若未写入商户号,则在 AliRefund 内按时间区间兜底。
orderPayTime := order.CreateTime
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.PaymentMerchant, order.OrderNo, order.Amount, &orderPayTime, "")
if refundErr != nil { if refundErr != nil {
return refundErr return refundErr
} }

View File

@@ -62,9 +62,16 @@ func (l *QueryDetailByOrderIdLogic) QueryDetailByOrderId(req *types.QueryDetailB
} }
// 检查订单状态 // 检查订单状态
if order.Status != "paid" { // - pending未支付阻止查看
// - refunded已退款允许进入但前端只展示“查询为空订单已退款”提示不再展示报告数据
// - 其他异常状态:按未找到处理
if order.Status == "pending" {
return nil, errors.Wrapf(xerr.NewErrMsg("订单未支付,无法查看报告"), "") return nil, errors.Wrapf(xerr.NewErrMsg("订单未支付,无法查看报告"), "")
} }
if order.Status != "paid" && order.Status != "refunded" {
// 使用逻辑层“未找到”错误码,前端按空结果处理
return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "订单状态不支持查看报告, status=%s", order.Status)
}
// 获取报告信息 // 获取报告信息
queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, req.OrderId) queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, req.OrderId)
@@ -105,6 +112,15 @@ func (l *QueryDetailByOrderIdLogic) QueryDetailByOrderId(req *types.QueryDetailB
} }
query.ProductName = product.ProductName query.ProductName = product.ProductName
query.Product = product.ProductEn query.Product = product.ProductEn
// 已退款订单:不再返回具体报告数据,仅告知前端“查询为空,订单已退款”
if order.Status == "refunded" {
query.QueryState = "failed" // 前端据此走空状态 + 退款提示
query.QueryData = []types.QueryItem{} // 不返回任何模块数据
if query.QueryParams == nil { // 参数可选保留,这里保险起见给个空 map
query.QueryParams = map[string]interface{}{}
}
}
return &types.QueryDetailByOrderIdResp{ return &types.QueryDetailByOrderIdResp{
Query: query, Query: query,
}, nil }, nil

View File

@@ -53,9 +53,16 @@ func (l *QueryDetailByOrderNoLogic) QueryDetailByOrderNo(req *types.QueryDetailB
} }
// 检查订单状态 // 检查订单状态
if order.Status != "paid" { // - pending未支付阻止查看
// - refunded已退款允许进入但前端只展示“查询为空订单已退款”提示不再展示报告数据
// - 其他异常状态:按未找到处理
if order.Status == "pending" {
return nil, errors.Wrapf(xerr.NewErrMsg("订单未支付,无法查看报告"), "") return nil, errors.Wrapf(xerr.NewErrMsg("订单未支付,无法查看报告"), "")
} }
if order.Status != "paid" && order.Status != "refunded" {
// 使用逻辑层“未找到”错误码,前端按空结果处理
return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "订单状态不支持查看报告, status=%s", order.Status)
}
// 获取报告信息 // 获取报告信息
queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id) queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id)
@@ -96,6 +103,15 @@ func (l *QueryDetailByOrderNoLogic) QueryDetailByOrderNo(req *types.QueryDetailB
} }
query.Product = product.ProductEn query.Product = product.ProductEn
query.ProductName = product.ProductName query.ProductName = product.ProductName
// 已退款订单:不再返回具体报告数据,仅告知前端“查询为空,订单已退款”
if order.Status == "refunded" {
query.QueryState = "failed" // 前端据此走空状态 + 退款提示
query.QueryData = []types.QueryItem{} // 不返回任何模块数据
if query.QueryParams == nil { // 参数可选保留,这里保险起见给个空 map
query.QueryParams = map[string]interface{}{}
}
}
return &types.QueryDetailByOrderNoResp{ return &types.QueryDetailByOrderNoResp{
Query: query, Query: query,
}, nil }, nil

View File

@@ -2,6 +2,7 @@ package query
import ( import (
"context" "context"
"strings"
"tyc-server/app/main/api/internal/svc" "tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types" "tyc-server/app/main/api/internal/types"
"tyc-server/app/main/model" "tyc-server/app/main/model"
@@ -28,25 +29,45 @@ func NewQueryListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryLi
} }
} }
// isVehicleProduct 判断 product_en 是否为车辆类产品
func isVehicleProduct(productEn string) bool {
vehiclePrefixes := []string{
"toc_Vehicle",
"toc_PersonVehicle",
}
for _, p := range vehiclePrefixes {
if strings.HasPrefix(productEn, p) {
return true
}
}
return false
}
func (l *QueryListLogic) QueryList(req *types.QueryListReq) (resp *types.QueryListResp, err error) { func (l *QueryListLogic) QueryList(req *types.QueryListReq) (resp *types.QueryListResp, err error) {
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx) userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil { if getUidErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告列表查询, 获取用户信息失败, %+v", getUidErr) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告列表查询, 获取用户信息失败, %+v", getUidErr)
} }
// 小程序端需要过滤非车辆产品,取更大分页后内存过滤
pageSize := req.PageSize
isMiniapp := req.Source == "miniapp"
if isMiniapp && pageSize < 200 {
pageSize = 200
}
// 直接构建查询query表的条件 // 直接构建查询query表的条件
build := l.svcCtx.QueryModel.SelectBuilder().Where(squirrel.Eq{ build := l.svcCtx.QueryModel.SelectBuilder().Where(squirrel.Eq{
"user_id": userID, "user_id": userID,
}) })
// 直接从query表分页查询 // 直接从query表分页查询
queryList, total, err := l.svcCtx.QueryModel.FindPageListByPageWithTotal(l.ctx, build, req.Page, req.PageSize, "create_time DESC") queryList, total, err := l.svcCtx.QueryModel.FindPageListByPageWithTotal(l.ctx, build, req.Page, pageSize, "create_time DESC")
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告列表查询, 查找报告列表错误, %+v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告列表查询, 查找报告列表错误, %+v", err)
} }
var list []types.Query var list []types.Query
if len(queryList) > 0 {
for _, queryModel := range queryList { for _, queryModel := range queryList {
var query types.Query var query types.Query
query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05") query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05")
@@ -60,6 +81,11 @@ func (l *QueryListLogic) QueryList(req *types.QueryListReq) (resp *types.QueryLi
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告列表查询, 获取商品信息失败, %+v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告列表查询, 获取商品信息失败, %+v", err)
} }
// 小程序端过滤非车辆类产品H5 端 source 为空,不做过滤)
if isMiniapp && !isVehicleProduct(product.ProductEn) {
continue
}
// 检查订单状态,如果订单已退款,则设置查询状态为已退款 // 检查订单状态,如果订单已退款,则设置查询状态为已退款
order, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, queryModel.OrderId) order, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, queryModel.OrderId)
if findOrderErr == nil && order.Status == model.OrderStatusRefunded { if findOrderErr == nil && order.Status == model.OrderStatusRefunded {
@@ -69,10 +95,15 @@ func (l *QueryListLogic) QueryList(req *types.QueryListReq) (resp *types.QueryLi
query.Product = product.ProductEn query.Product = product.ProductEn
list = append(list, query) list = append(list, query)
} }
// H5 端返回数据库原始 total小程序端返回过滤后的实际数量
respTotal := total
if isMiniapp {
respTotal = int64(len(list))
} }
return &types.QueryListResp{ return &types.QueryListResp{
Total: total, Total: respTotal,
List: list, List: list,
}, nil }, nil
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,187 @@
package tianyuan
import (
"context"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"time"
"tyc-server/app/main/api/internal/service"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/model"
"tyc-server/common/xerr"
"tyc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
// VehicleCallbackLogic 处理天远车辆类接口的异步回调
// 设计目标:
// - 按 order_no + api_id 找到对应的查询记录
// - 解密原有 query_data[]APIResponseData更新或追加当前 api_id 的结果
// - 再次加密写回 query.query_data必要时将 query_state 从 pending 更新为 success
type VehicleCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewVehicleCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VehicleCallbackLogic {
return &VehicleCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 与 paySuccessNotify 中一致:仅通过异步回调回写结果的车辆接口
var asyncVehicleApiIDs = map[string]bool{
"QCXG1U4U": true, "QCXG3Y6B": true, "QCXG3Z3L": true, "QCXGP00W": true,
}
// allAsyncVehicleReceived 判断 apiList 中所有“异步车辆接口”是否均已收到回调Success 为 true
func allAsyncVehicleReceived(apiList []service.APIResponseData) bool {
for _, item := range apiList {
if asyncVehicleApiIDs[item.ApiID] && !item.Success {
return false
}
}
return true
}
// Handle 入口:直接接收原始 *http.Request方便读取 query / body
func (l *VehicleCallbackLogic) Handle(r *http.Request) error {
apiID := r.URL.Query().Get("api_id")
orderNo := r.URL.Query().Get("order_no")
if apiID == "" || orderNo == "" {
return errors.Wrapf(
xerr.NewErrMsg("缺少 api_id 或 order_no"),
"tianyuan vehicle callback, api_id=%s, order_no=%s", apiID, orderNo,
)
}
l.Infof("tianyuan vehicle callback start, api_id=%s, order_no=%s", apiID, orderNo)
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取回调 Body 失败: %v", err)
}
l.Infof("tianyuan vehicle callback body received, api_id=%s, order_no=%s, body_len=%d", apiID, orderNo, len(bodyBytes))
// 回调包裹格式通常为 { code:0, msg:\"成功\", order_id:\"...\", data:{...} }
// 这里只把 data 字段提取出来存到 QueryData 里,便于前端直接渲染。
type callbackWrapper struct {
Code json.RawMessage `json:"code"`
Msg json.RawMessage `json:"msg"`
OrderID json.RawMessage `json:"order_id"`
Data json.RawMessage `json:"data"`
Extra map[string]any `json:"-"` // 预留
}
var wrapper callbackWrapper
payload := bodyBytes
if len(bodyBytes) > 0 {
if err := json.Unmarshal(bodyBytes, &wrapper); err != nil {
l.Errorf("tianyuan vehicle callback unmarshal body failed, api_id=%s, order_no=%s, err=%v", apiID, orderNo, err)
} else if len(wrapper.Data) > 0 {
payload = wrapper.Data
l.Infof("tianyuan vehicle callback extracted data field, api_id=%s, order_no=%s, data_len=%d", apiID, orderNo, len(payload))
}
}
// 1. 根据订单号找到订单
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
if err == model.ErrNotFound {
return errors.Wrapf(xerr.NewErrMsg("未找到订单"), "order_no=%s", orderNo)
}
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败, order_no=%s, err=%v", orderNo, err)
}
// 2. 根据订单ID找到对应查询记录
query, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id)
if err != nil {
if err == model.ErrNotFound {
return errors.Wrapf(xerr.NewErrMsg("未找到查询记录"), "order_no=%s, order_id=%d", orderNo, order.Id)
}
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询 query 记录失败, order_id=%d, err=%v", order.Id, err)
}
// 3. 获取加密密钥
secretKey := l.svcCtx.Config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析 AES 密钥失败: %v", decodeErr)
}
// 4. 解密 query_data反序列化为 []APIResponseData
var apiList []service.APIResponseData
if query.QueryData.Valid && query.QueryData.String != "" {
decrypted, decErr := crypto.AesDecrypt(query.QueryData.String, key)
if decErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密 query_data 失败: %v", decErr)
}
if len(decrypted) > 0 {
if err := json.Unmarshal(decrypted, &apiList); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析 query_data 为 APIResponseData 列表失败: %v", err)
}
}
}
// 5. 更新或追加当前 api_id 的结果
nowStr := time.Now().Format("2006-01-02 15:04:05")
updated := false
for i := range apiList {
if apiList[i].ApiID == apiID {
apiList[i].Data = json.RawMessage(payload)
apiList[i].Success = true
apiList[i].Timestamp = nowStr
apiList[i].Error = ""
updated = true
break
}
}
if !updated {
apiList = append(apiList, service.APIResponseData{
ApiID: apiID,
Data: json.RawMessage(payload),
Success: true,
Timestamp: nowStr,
})
}
// 6. 重新序列化并加密,写回查询记录
merged, err := json.Marshal(apiList)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "序列化合并后的 query_data 失败: %v", err)
}
enc, encErr := crypto.AesEncrypt(merged, key)
if encErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密合并后的 query_data 失败: %v", encErr)
}
query.QueryData.String = enc
query.QueryData.Valid = true
// 仅当所有异步车辆接口均已有回调结果时,才将 pending 置为 success并触发代理结算
wasPending := query.QueryState == "pending"
didSetSuccess := wasPending && allAsyncVehicleReceived(apiList)
if didSetSuccess {
query.QueryState = "success"
}
if err := l.svcCtx.QueryModel.UpdateWithVersion(l.ctx, nil, query); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新查询记录失败, query_id=%d, err=%v", query.Id, err)
}
if didSetSuccess {
if agentErr := l.svcCtx.AgentService.AgentProcess(l.ctx, order); agentErr != nil {
l.Errorf("tianyuan vehicle callback, AgentProcess failed, order_no=%s, err=%v", orderNo, agentErr)
// 不因代理处理失败而整体失败,回调已成功落库
}
}
l.Infof("tianyuan vehicle callback handled, order_no=%s, api_id=%s, query_id=%d", orderNo, apiID, query.Id)
return nil
}

View File

@@ -0,0 +1,43 @@
package toolbox
import (
"context"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ToolboxListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewToolboxListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ToolboxListLogic {
return &ToolboxListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ToolboxListLogic) ToolboxList() (resp *types.ToolboxListResp, err error) {
// 调用toolboxService获取工具列表
tools := l.svcCtx.ToolboxService.ListTools()
// 转换为响应格式
var toolInfos []types.ToolInfo
for _, tool := range tools {
toolInfos = append(toolInfos, types.ToolInfo{
Key: tool.Key,
Name: tool.Name,
Desc: tool.Desc,
})
}
return &types.ToolboxListResp{
Tools: toolInfos,
}, nil
}

View File

@@ -0,0 +1,37 @@
package toolbox
import (
"context"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ToolboxQueryLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewToolboxQueryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ToolboxQueryLogic {
return &ToolboxQueryLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ToolboxQueryLogic) ToolboxQuery(req *types.ToolboxQueryReq) (resp *types.ToolboxQueryResp, err error) {
// 调用toolboxService执行工具查询
result, err := l.svcCtx.ToolboxService.Query(l.ctx, req.ToolKey, req.Params)
if err != nil {
return nil, err
}
return &types.ToolboxQueryResp{
ToolKey: req.ToolKey,
Result: result,
}, nil
}

View File

@@ -0,0 +1,66 @@
package upload
import (
"context"
"os"
"path/filepath"
"strings"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"tyc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type ServeUploadedFileLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewServeUploadedFileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ServeUploadedFileLogic {
return &ServeUploadedFileLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ServeUploadedFileLogic) ServeUploadedFile(req *types.ServeUploadedFileReq) (resp *types.ServeUploadedFileResp, err error) {
fileName := strings.TrimSpace(req.FileName)
if fileName == "" {
return nil, errors.Wrap(xerr.NewErrMsg("缺少文件名"), "fileName empty")
}
// 只允许文件名,禁止路径穿越
if strings.Contains(fileName, "..") || filepath.Base(fileName) != fileName {
return nil, errors.Wrap(xerr.NewErrMsg("非法文件名"), fileName)
}
candidates := []string{
"data/uploads",
"../data/uploads",
"../../data/uploads",
"../../../data/uploads",
}
for _, c := range candidates {
abs, _ := filepath.Abs(c)
fullPath := filepath.Join(abs, fileName)
if info, err := os.Stat(fullPath); err == nil && !info.IsDir() {
contentType := "image/jpeg"
if strings.HasSuffix(strings.ToLower(fileName), ".png") {
contentType = "image/png"
} else if strings.HasSuffix(strings.ToLower(fileName), ".gif") {
contentType = "image/gif"
} else if strings.HasSuffix(strings.ToLower(fileName), ".webp") {
contentType = "image/webp"
}
return &types.ServeUploadedFileResp{
FilePath: fullPath,
ContentType: contentType,
}, nil
}
}
return nil, errors.Wrapf(xerr.NewErrMsg("文件不存在"), "fileName=%s", fileName)
}

View File

@@ -0,0 +1,137 @@
package upload
import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"tyc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
const maxImageSize = 3 * 1024 * 1024 // 3MB
const defaultTempFileMaxAgeH = 24
type UploadImageLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUploadImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImageLogic {
return &UploadImageLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UploadImageLogic) UploadImage(req *types.UploadImageReq) (resp *types.UploadImageResp, err error) {
decoded, decErr := base64.StdEncoding.DecodeString(req.ImageBase64)
if decErr != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("图片 base64 格式错误"), "%v", decErr)
}
if len(decoded) > maxImageSize {
return nil, errors.Wrapf(xerr.NewErrMsg("图片不能超过 3M"), "size=%d", len(decoded))
}
// 按文件内容 hash 命名,相同文件复用同一 URL避免重复传输与刷流量
hashSum := sha256.Sum256(decoded)
hashHex := hex.EncodeToString(hashSum[:])
fileName := hashHex + ".jpg"
dir := l.uploadStoragePath()
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建上传目录失败: %v", err)
}
filePath := filepath.Join(dir, fileName)
// 若已存在同 hash 文件,直接返回 URL不重复写入
if _, statErr := os.Stat(filePath); statErr == nil {
url := l.buildURL(fileName)
logx.Infof("upload image dedup by hash, file=%s", fileName)
return &types.UploadImageResp{Url: url}, nil
}
if err := os.WriteFile(filePath, decoded, 0644); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存图片失败: %v", err)
}
// 异步清理过期临时文件,不阻塞响应
go l.deleteOldUploads(dir)
url := l.buildURL(fileName)
logx.Infof("upload image ok, file=%s", fileName)
return &types.UploadImageResp{Url: url}, nil
}
func (l *UploadImageLogic) buildURL(fileName string) string {
baseURL := l.svcCtx.Config.Upload.FileBaseURL
if baseURL == "" && l.svcCtx.Config.PublicBaseURL != "" {
// 兜底:如果未单独配置 Upload.FileBaseURL则使用公共域名拼接默认上传路径
baseURL = strings.TrimRight(l.svcCtx.Config.PublicBaseURL, "/") + "/api/v1/upload/file"
}
if baseURL == "" {
return ""
}
return fmt.Sprintf("%s/%s", baseURL, fileName)
}
func (l *UploadImageLogic) uploadStoragePath() string {
candidates := []string{
"data/uploads",
"../data/uploads",
"../../data/uploads",
"../../../data/uploads",
}
for _, c := range candidates {
abs, _ := filepath.Abs(c)
if err := os.MkdirAll(abs, 0755); err == nil {
return abs
}
}
abs, _ := filepath.Abs(candidates[0])
return abs
}
// deleteOldUploads 删除目录下超过保留时长的临时文件
func (l *UploadImageLogic) deleteOldUploads(dir string) {
maxAgeH := l.svcCtx.Config.Upload.TempFileMaxAgeH
if maxAgeH <= 0 {
maxAgeH = defaultTempFileMaxAgeH
}
cutoff := time.Now().Add(-time.Duration(maxAgeH) * time.Hour)
entries, err := os.ReadDir(dir)
if err != nil {
l.Errorf("deleteOldUploads ReadDir: %v", err)
return
}
for _, e := range entries {
if e.IsDir() {
continue
}
path := filepath.Join(dir, e.Name())
info, err := os.Stat(path)
if err != nil {
continue
}
if info.ModTime().Before(cutoff) {
if err := os.Remove(path); err != nil {
l.Errorf("deleteOldUploads Remove %s: %v", path, err)
} else {
l.Infof("deleteOldUploads removed %s", path)
}
}
}
}

View File

@@ -38,7 +38,7 @@ func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (r
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err)
} }
// 检查验证码(开发环境可跳过) // 短信验证码校验(开发环境可跳过)
if os.Getenv("ENV") != "development" { if os.Getenv("ENV") != "development" {
redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile) redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey) cacheCode, err := l.svcCtx.Redis.Get(redisKey)

View File

@@ -6,8 +6,10 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings"
"time" "time"
"tyc-server/app/main/model" "tyc-server/app/main/model"
"tyc-server/common/ctxdata"
"tyc-server/common/xerr" "tyc-server/common/xerr"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -39,6 +41,11 @@ func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthRe
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取access_token失败: %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取access_token失败: %v", err)
} }
// 已登录正式用户 + 静默授权 code把当前公众号 openid 写入 user_auth(wxh5_openid),供微信内 JSAPI 支付使用
if claims, claimsErr := ctxdata.GetClaimsFromCtx(l.ctx); claimsErr == nil && claims != nil && claims.UserType == model.UserTypeNormal {
return l.bindWxh5OpenidToNormalUser(claims.UserId, accessTokenResp.Openid)
}
// Step 2: 查找用户授权信息 // Step 2: 查找用户授权信息
userAuth, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid) userAuth, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid)
if findErr != nil && !errors.Is(findErr, model.ErrNotFound) { if findErr != nil && !errors.Is(findErr, model.ErrNotFound) {
@@ -94,6 +101,53 @@ func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthRe
}, nil }, nil
} }
// bindWxh5OpenidToNormalUser 将 snsapi_base 换得的 openid 绑定到当前正式用户(插入或更新 user_auth
// 临时用户不能走此分支:需在绑定手机号时由 TempUserBindUser 写入 user_auth避免与临时转正逻辑冲突。
func (l *WxH5AuthLogic) bindWxh5OpenidToNormalUser(normalUserID int64, openid string) (*types.WXH5AuthResp, error) {
openid = strings.TrimSpace(openid)
if openid == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "微信 openid 为空")
}
existingByOpenid, err := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, openid)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询 wxh5_openid 失败: %v", err)
}
if existingByOpenid != nil && existingByOpenid.UserId != normalUserID {
return nil, errors.Wrapf(xerr.NewErrMsg("该微信已绑定其他账号"), "wxh5_openid 已被占用 user_id=%d", existingByOpenid.UserId)
}
rowByUser, err := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, normalUserID, model.UserAuthTypeWxh5OpenID)
if errors.Is(err, model.ErrNotFound) {
_, insErr := l.svcCtx.UserAuthModel.Insert(l.ctx, nil, &model.UserAuth{
UserId: normalUserID,
AuthType: model.UserAuthTypeWxh5OpenID,
AuthKey: openid,
})
if insErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "写入 wxh5_openid 失败: %v", insErr)
}
logx.WithContext(l.ctx).Infof("[WxH5Auth] 已写入 user_auth wxh5_openid user_id=%d", normalUserID)
} else if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户 wxh5_openid 失败: %v", err)
} else if rowByUser.AuthKey != openid {
rowByUser.AuthKey = openid
if _, updErr := l.svcCtx.UserAuthModel.Update(l.ctx, nil, rowByUser); updErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新 wxh5_openid 失败: %v", updErr)
}
logx.WithContext(l.ctx).Infof("[WxH5Auth] 已更新 user_auth wxh5_openid user_id=%d", normalUserID)
}
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, normalUserID, model.UserTypeNormal)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT token失败: %v", err)
}
now := time.Now().Unix()
return &types.WXH5AuthResp{
AccessToken: token,
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
}, nil
}
type AccessTokenResp struct { type AccessTokenResp struct {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
Openid string `json:"openid"` Openid string `json:"openid"`

View File

@@ -3,6 +3,7 @@ package middleware
import ( import (
"context" "context"
"net/http" "net/http"
"strings"
) )
const ( const (
@@ -12,7 +13,7 @@ const (
func GlobalSourceInterceptor(next http.HandlerFunc) http.HandlerFunc { func GlobalSourceInterceptor(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// 获取请求头 X-Platform 的值 // 获取请求头 X-Platform 的值
platform := r.Header.Get(PlatformKey) platform := strings.TrimSpace(r.Header.Get(PlatformKey))
// 将值放入新的 context 中 // 将值放入新的 context 中
ctx := r.Context() ctx := r.Context()

View File

@@ -0,0 +1,60 @@
package middleware
import (
"net/http"
"tyc-server/app/main/model"
"tyc-server/common/ctxdata"
"tyc-server/common/result"
"tyc-server/common/xerr"
"github.com/zeromicro/go-zero/rest/httpx"
)
// UserDisableInterceptorMiddleware 检查用户是否被封禁,封禁用户直接拦截并返回错误
type UserDisableInterceptorMiddleware struct {
UserModel model.UserModel
}
// NewUserDisableInterceptorMiddleware 创建用户封禁检查中间件
func NewUserDisableInterceptorMiddleware(userModel model.UserModel) *UserDisableInterceptorMiddleware {
return &UserDisableInterceptorMiddleware{
UserModel: userModel,
}
}
// Handle 处理请求:当用户已登录且被封禁时,直接返回错误
func (m *UserDisableInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
claims, err := ctxdata.GetClaimsFromCtx(r.Context())
if err != nil {
// 无登录信息,放行(由后续认证中间件处理)
next(w, r)
return
}
// 管理员不检查 user 表的 disable 字段
if claims.UserType == model.UserTypeAdmin {
next(w, r)
return
}
// 查询用户是否被封禁
user, err := m.UserModel.FindOne(r.Context(), claims.UserId)
if err != nil {
// 用户不存在(如管理员账号)或查询失败,放行
next(w, r)
return
}
// 1 表示已禁用
if user.Disable == 1 {
errcode := xerr.USER_DISABLED
errmsg := xerr.MapErrMsg(errcode)
httpx.WriteJson(w, http.StatusOK, result.Error(errcode, errmsg))
return
}
next(w, r)
}
}

View File

@@ -36,6 +36,26 @@ var payload struct {
OrderID int64 `json:"order_id"` OrderID int64 `json:"order_id"`
} }
// 仅通过异步回调回写结果的车辆接口 ApiID
var asyncVehicleApiIDs = map[string]bool{
"QCXG1U4U": true, // 车辆里程混合
"QCXG3Y6B": true, // 车辆维保简版
"QCXG3Z3L": true, // 车辆维保详细版
"QCXGP00W": true, // 车辆出险详版
}
func isAllAsyncVehicleQuery(responseData []service.APIResponseData) bool {
if len(responseData) == 0 {
return false
}
for _, r := range responseData {
if !asyncVehicleApiIDs[r.ApiID] {
return false
}
}
return true
}
func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.Task) error { func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
// 从任务的负载中解码数据 // 从任务的负载中解码数据
if err := json.Unmarshal(t.Payload(), &payload); err != nil { if err := json.Unmarshal(t.Payload(), &payload); err != nil {
@@ -76,11 +96,31 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
return fmt.Errorf("解密参数失败: %+v", aesdecryptErr) return fmt.Errorf("解密参数失败: %+v", aesdecryptErr)
} }
// 为避免 base64 图片等大字段撑爆 query.query_params 列,这里对参数做一次瘦身:
// 删除纯 base64 图片字段(如 vlphoto_data / photo_data / image_base64仅保留必要的文本信息。
var originalParams map[string]interface{}
if err := json.Unmarshal(decryptData, &originalParams); err != nil {
return fmt.Errorf("解析原始参数失败: %+v", err)
}
delete(originalParams, "vlphoto_data")
delete(originalParams, "photo_data")
delete(originalParams, "image_base64")
slimBytes, err := json.Marshal(originalParams)
if err != nil {
return fmt.Errorf("序列化精简后的参数失败: %+v", err)
}
slimEncrypted, err := crypto.AesEncrypt(slimBytes, key)
if err != nil {
return fmt.Errorf("加密精简后的参数失败: %+v", err)
}
query := &model.Query{ query := &model.Query{
OrderId: order.Id, OrderId: order.Id,
UserId: order.UserId, UserId: order.UserId,
ProductId: product.Id, ProductId: product.Id,
QueryParams: data.Params, // 仅保存精简后的参数,避免 base64 图片占满列
QueryParams: slimEncrypted,
QueryState: "pending", QueryState: "pending",
} }
result, insertQueryErr := l.svcCtx.QueryModel.Insert(ctx, nil, query) result, insertQueryErr := l.svcCtx.QueryModel.Insert(ctx, nil, query)
@@ -100,7 +140,7 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
return fmt.Errorf("获取插入后的查询记录失败: %+v", err) return fmt.Errorf("获取插入后的查询记录失败: %+v", err)
} }
// 解析解密后的参数获取用户信息 // 解析解密后的参数获取用户信息(这里依然使用完整参数,后续会在写回时再次瘦身)
var userInfo map[string]interface{} var userInfo map[string]interface{}
if err := json.Unmarshal(decryptData, &userInfo); err != nil { if err := json.Unmarshal(decryptData, &userInfo); err != nil {
return fmt.Errorf("解析用户信息失败: %+v", err) return fmt.Errorf("解析用户信息失败: %+v", err)
@@ -120,17 +160,31 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
downloadURL := l.buildAuthorizationDownloadURL(authDoc.FileName) downloadURL := l.buildAuthorizationDownloadURL(authDoc.FileName)
userInfo["authorization_url"] = downloadURL userInfo["authorization_url"] = downloadURL
// 重新序列化用户信息 // 1完整参数包含图片和授权书 URL用于后续调用天远接口
updatedDecryptData, marshalErr := json.Marshal(userInfo) updatedDecryptData, marshalErr := json.Marshal(userInfo)
if marshalErr != nil { if marshalErr != nil {
logx.Errorf("序列化用户信息失败: %v", marshalErr) logx.Errorf("序列化用户信息失败: %v", marshalErr)
} else { } else {
// 重新加密更新后的数据 decryptData = updatedDecryptData
encryptedUpdatedData, encryptErr := crypto.AesEncrypt(updatedDecryptData, key)
if encryptErr != nil { // 2精简版本去掉图片类大字段仅用于持久化到 query.QueryParams
logx.Errorf("重新加密用户信息失败: %v", encryptErr) slimUserInfo := make(map[string]interface{}, len(userInfo))
for k, v := range userInfo {
slimUserInfo[k] = v
}
delete(slimUserInfo, "vlphoto_data")
delete(slimUserInfo, "photo_data")
delete(slimUserInfo, "image_base64")
slimUpdatedBytes, marshalSlimErr := json.Marshal(slimUserInfo)
if marshalSlimErr != nil {
logx.Errorf("序列化精简用户信息失败: %v", marshalSlimErr)
} else {
// 重新加密精简后的数据写回数据库
encryptedUpdatedData, encryptErr := crypto.AesEncrypt(slimUpdatedBytes, key)
if encryptErr != nil {
logx.Errorf("重新加密精简用户信息失败: %v", encryptErr)
} else { } else {
// 更新查询记录中的参数
query.QueryParams = string(encryptedUpdatedData) query.QueryParams = string(encryptedUpdatedData)
updateParamsErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query) updateParamsErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
if updateParamsErr != nil { if updateParamsErr != nil {
@@ -139,7 +193,7 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
logx.Infof("成功更新查询参数包含授权书URL: %s", downloadURL) logx.Infof("成功更新查询参数包含授权书URL: %s", downloadURL)
} }
} }
decryptData = updatedDecryptData }
} }
} }
@@ -152,7 +206,8 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
responseData = []service.APIResponseData{} responseData = []service.APIResponseData{}
} else { } else {
var processErr error var processErr error
responseData, processErr = l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id) // 传入订单号,用于在 ApiRequestService 中为异步车辆接口生成回调地址return_url
responseData, processErr = l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id, order.OrderNo)
if processErr != nil { if processErr != nil {
return l.handleError(ctx, processErr, order, query) return l.handleError(ctx, processErr, order, query)
} }
@@ -160,7 +215,6 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
// 计算成功模块的总成本价 // 计算成功模块的总成本价
totalCostPrice := 0.0 totalCostPrice := 0.0
if responseData != nil {
for _, item := range responseData { for _, item := range responseData {
if item.Success { if item.Success {
// 根据API ID查找功能模块 // 根据API ID查找功能模块
@@ -173,7 +227,6 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
totalCostPrice += feature.CostPrice totalCostPrice += feature.CostPrice
} }
} }
}
// 更新订单的销售成本 // 更新订单的销售成本
order.SalesCost = totalCostPrice order.SalesCost = totalCostPrice
@@ -204,12 +257,15 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
return l.handleError(ctx, err, order, query) return l.handleError(ctx, err, order, query)
} }
// 若当前产品全部为异步车辆接口(结果通过回调回写),则保持 pending由回调再置为 success
if !isAllAsyncVehicleQuery(responseData) {
query.QueryState = "success" query.QueryState = "success"
updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query) updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
if updateQueryErr != nil { if updateQueryErr != nil {
updateQueryErr = fmt.Errorf("修改查询状态失败: %v", updateQueryErr) updateQueryErr = fmt.Errorf("修改查询状态失败: %v", updateQueryErr)
return l.handleError(ctx, updateQueryErr, order, query) return l.handleError(ctx, updateQueryErr, order, query)
} }
}
err = l.svcCtx.AgentService.AgentProcess(ctx, order) err = l.svcCtx.AgentService.AgentProcess(ctx, order)
if err != nil { if err != nil {
@@ -266,8 +322,13 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error
logx.Infof("已发起微信退款申请, orderID: %d, amount: %f", order.Id, order.Amount) logx.Infof("已发起微信退款申请, orderID: %d, amount: %f", order.Id, order.Amount)
return asynq.SkipRetry return asynq.SkipRetry
} else { } else {
// 支付宝退款为同步结果,这里直接根据返回结果更新订单和佣金/钱包 // 支付宝退款为同步结果,优先按订单记录的 payment_merchant 选择商户;
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount) // 老订单若未写入商户号,则在 AliRefund 内按时间区间兜底。
orderPayTime := &order.CreateTime
if order.PayTime.Valid {
orderPayTime = &order.PayTime.Time
}
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.PaymentMerchant, order.OrderNo, order.Amount, orderPayTime, "")
if refundErr != nil { if refundErr != nil {
logx.Error(refundErr) logx.Error(refundErr)
return asynq.SkipRetry return asynq.SkipRetry

View File

@@ -0,0 +1,113 @@
首页
生活服务
垃圾分类问答
垃圾分类问答接口
158 13540
垃圾分类问答正常服务支持MCP
提供垃圾分类知识判断题,帮助用户掌握干湿垃圾、可回收物等分类标准。
会员免费・升级会员畅享160+免费接口,立即升级>>
收藏
普通会员
100次/天
高级会员
1W次/天
黄金会员
50W次/天
钻石会员
不限次数
▼ 接口文档
价格
返回示例
参考代码
MCP服务
帮助
生成小程序
垃圾分类问答判断题,每次调用接口返回一个废弃物名称及正确分类。
接入点列表:
垃圾分类问答
相关资源:
垃圾分类问答功能演示
▼ 接口信息
随机返回带结果的垃圾分类知识库
接口地址https://apis.tianapi.com/anslajifenlei/index?key={apiKey}
支持协议http/https
请求方法get/post
返回格式utf-8 json
▼ 请求参数
上传文件时请使用标准表单格式 multipart/form-data
普通参数请使用默认表单格式 application/x-www-form-urlencoded
当参数值如url、base64包含特殊字符时建议urlencode编码后传递
名称 类型 必须 示例值/默认值 说明
key string 是 您自己的ApiKey注册账号后获得 API密钥
▼ 返回示例
接口数据示例仅作为预览参考,请以实际测试结果为准
旧接口域名返回的数据结构和现在略有不同,请查看说明
成功调用,返回内容并产生计费:
{
"msg": "success",
"code": 200,
"result": {
"name": "西红柿",
"type": 2,
"explain": "湿垃圾"
}
}
失败调用,查看接口错误码释义:
{
"code": 150,
"msg": "API可用次数不足"
}
▼ 返回参数
公共参数指所有接口都会返回的参数,应用参数每个接口都不同
建议对返回的字段进行存在性与类型校验可参考API安全接入指南
名称 类型 示例值 说明
公共参数
code int 200 状态码
msg string success 错误信息
result object {} 返回结果集
应用参数
name string 西红柿 垃圾物品
type int 2 垃圾分类0为可回收、1为有害、2为厨余(湿)、3为其他(干)
explain string 湿垃圾 中文释义
▼ 接口价格
本接口为会员免费类接口,可根据业务需求选择升级会员方案>>
不同会员方案仅每日调用量等配额上限不同,数据本身无区别
会员方案 免费接口数 每日调用量 QPS 价格
普通会员 10个 100次 3 免费
高级会员 不限 1万次 20 29元/月、348元/年169元/年惠
黄金会员 不限 50万次 30 89元/月、1068元/年529元/年惠
钻石会员 不限 不限次 60 3380元/年1699元/年惠

View File

@@ -5,7 +5,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net/http" "net/url"
"strconv" "strconv"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -16,9 +16,38 @@ import (
"github.com/smartwalle/alipay/v3" "github.com/smartwalle/alipay/v3"
) )
// bak 支付宝仅用于 [AlipayBakRefundStart, AlipayBakRefundEnd) 区间内支付订单的退款,区间外使用正式 clientCST
var (
AlipayBakRefundStart = time.Date(2026, 1, 25, 16, 38, 17, 0, time.FixedZone("CST", 8*3600)) // Sun Jan 25 2026 16:38:17 GMT+0800 之前用正式
AlipayBakRefundEnd = time.Date(2026, 2, 2, 18, 26, 0, 0, time.FixedZone("CST", 8*3600)) // 2026-02-02 18:26 之后用正式
)
type AliPayService struct { type AliPayService struct {
config config.AlipayConfig config config.AlipayConfig
AlipayClient *alipay.Client AlipayClient *alipay.Client
AlipayClientBak *alipay.Client // 仅用于 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单的退款
}
// clientForMerchant 根据商户标识与订单时间选择对应的支付宝 client。
// - merchant == "two" 且 Bak 存在:优先返回 Bak
// - merchant == "one" 或空:默认返回主商户;
// - merchant 为空且 orderPayTime 落在备份时间区间:兼容老订单,走 Bak。
func (a *AliPayService) clientForMerchant(merchant string, orderPayTime *time.Time) *alipay.Client {
// 显式指定 two则优先走 Bak
if merchant == "two" && a.AlipayClientBak != nil {
return a.AlipayClientBak
}
// 显式指定 one 或其他未知标识,一律走主商户
if merchant == "one" || merchant == "" {
// 对于老订单未写入 merchant 的情况,继续保留时间区间兜底逻辑
if merchant == "" && orderPayTime != nil && a.AlipayClientBak != nil &&
!orderPayTime.Before(AlipayBakRefundStart) && orderPayTime.Before(AlipayBakRefundEnd) {
return a.AlipayClientBak
}
return a.AlipayClient
}
// 兜底:未知标识时仍走主商户,避免因为配置问题导致整体不可用
return a.AlipayClient
} }
// NewAliPayService 是一个构造函数,用于初始化 AliPayService // NewAliPayService 是一个构造函数,用于初始化 AliPayService
@@ -44,14 +73,34 @@ func NewAliPayService(c config.Config) *AliPayService {
panic(fmt.Sprintf("加载根证书失败: %v", err)) panic(fmt.Sprintf("加载根证书失败: %v", err))
} }
return &AliPayService{ svc := &AliPayService{
config: c.Alipay, config: c.Alipay,
AlipayClient: client, AlipayClient: client,
} }
// 初始化 bak 支付宝客户端(仅用于 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单的退款)
if c.Alipay.AppIDBak != "" && c.Alipay.PrivateKeyBak != "" {
bakClient, err := alipay.New(c.Alipay.AppIDBak, c.Alipay.PrivateKeyBak, c.Alipay.IsProduction)
if err != nil {
panic(fmt.Sprintf("创建支付宝 bak 客户端失败: %v", err))
}
if err = bakClient.LoadAppCertPublicKeyFromFile(c.Alipay.AppCertPathBak); err != nil {
panic(fmt.Sprintf("加载 bak 应用公钥证书失败: %v", err))
}
if err = bakClient.LoadAlipayCertPublicKeyFromFile(c.Alipay.AlipayCertPathBak); err != nil {
panic(fmt.Sprintf("加载 bak 支付宝公钥证书失败: %v", err))
}
if err = bakClient.LoadAliPayRootCertFromFile(c.Alipay.AlipayRootCertPathBak); err != nil {
panic(fmt.Sprintf("加载 bak 根证书失败: %v", err))
}
svc.AlipayClientBak = bakClient
} }
func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, outTradeNo string) (string, error) { return svc
client := a.AlipayClient }
func (a *AliPayService) CreateAlipayAppOrder(merchant string, amount float64, subject string, outTradeNo string) (string, error) {
client := a.clientForMerchant(merchant, nil)
totalAmount := lzUtils.ToAlipayAmount(amount) totalAmount := lzUtils.ToAlipayAmount(amount)
// 构造移动支付请求 // 构造移动支付请求
p := alipay.TradeAppPay{ p := alipay.TradeAppPay{
@@ -74,8 +123,8 @@ func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, out
} }
// CreateAlipayH5Order 创建支付宝H5支付订单 // CreateAlipayH5Order 创建支付宝H5支付订单
func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outTradeNo string) (string, error) { func (a *AliPayService) CreateAlipayH5Order(merchant string, amount float64, subject string, outTradeNo string) (string, error) {
client := a.AlipayClient client := a.clientForMerchant(merchant, nil)
totalAmount := lzUtils.ToAlipayAmount(amount) totalAmount := lzUtils.ToAlipayAmount(amount)
// 构造H5支付请求 // 构造H5支付请求
p := alipay.TradeWapPay{ p := alipay.TradeWapPay{
@@ -97,8 +146,9 @@ func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outT
return payUrl.String(), nil return payUrl.String(), nil
} }
// CreateAlipayOrder 根据平台类型创建支付宝支付订单 // CreateAlipayOrder 根据平台类型和商户标识创建支付宝支付订单
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, subject string, outTradeNo string) (string, error) { // merchant: 商户标识,目前约定 "one"=主商户, "two"=备商户
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, merchant string, amount float64, subject string, outTradeNo string) (string, error) {
// 根据 ctx 中的 platform 判断平台 // 根据 ctx 中的 platform 判断平台
platform, platformOk := ctx.Value("platform").(string) platform, platformOk := ctx.Value("platform").(string)
if !platformOk { if !platformOk {
@@ -107,52 +157,58 @@ func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, s
switch platform { switch platform {
case model.PlatformApp: case model.PlatformApp:
// 调用App支付的创建方法 // 调用App支付的创建方法
return a.CreateAlipayAppOrder(amount, subject, outTradeNo) return a.CreateAlipayAppOrder(merchant, amount, subject, outTradeNo)
case model.PlatformH5: case model.PlatformH5:
// 调用H5支付的创建方法并传入 returnUrl // 调用H5支付的创建方法并传入 returnUrl
return a.CreateAlipayH5Order(amount, subject, outTradeNo) return a.CreateAlipayH5Order(merchant, amount, subject, outTradeNo)
default: default:
return "", fmt.Errorf("不支持的支付平台: %s", platform) return "", fmt.Errorf("不支持的支付平台: %s", platform)
} }
} }
// AliRefund 发起支付宝退款 // AliRefund 发起支付宝退款
func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refundAmount float64) (*alipay.TradeRefundRsp, error) { // merchant: 支付商户标识one/two。为空时按老逻辑仅在备份时间区间内使用 Bak。
// orderPayTime 为订单支付时间(或创建时间);用于老订单按时间区间选择商户;传 nil 则忽略时间区间。
// outRequestNo 为商户退款请求号,同一笔退款需唯一;传空则使用 "refund-"+outTradeNo重试时建议传入唯一号避免支付宝报重复。
func (a *AliPayService) AliRefund(ctx context.Context, merchant string, outTradeNo string, refundAmount float64, orderPayTime *time.Time, outRequestNo string) (*alipay.TradeRefundRsp, error) {
client := a.clientForMerchant(merchant, orderPayTime)
if outRequestNo == "" {
outRequestNo = fmt.Sprintf("refund-%s", outTradeNo)
}
refund := alipay.TradeRefund{ refund := alipay.TradeRefund{
OutTradeNo: outTradeNo, OutTradeNo: outTradeNo,
RefundAmount: lzUtils.ToAlipayAmount(refundAmount), RefundAmount: lzUtils.ToAlipayAmount(refundAmount),
OutRequestNo: fmt.Sprintf("refund-%s", outTradeNo), OutRequestNo: outRequestNo,
} }
// 发起退款请求 refundResp, err := client.TradeRefund(ctx, refund)
refundResp, err := a.AlipayClient.TradeRefund(ctx, refund)
if err != nil { if err != nil {
return nil, fmt.Errorf("支付宝退款请求错误:%v", err) return nil, fmt.Errorf("支付宝退款请求错误:%v", err)
} }
return refundResp, nil return refundResp, nil
} }
// HandleAliPaymentNotification 支付宝支付回调 // HandleAliPaymentNotification 支付宝支付回调验签。
func (a *AliPayService) HandleAliPaymentNotification(r *http.Request) (*alipay.Notification, error) { // 由上层根据 out_trade_no 查出订单并传入对应商户标识 merchant。
// 解析表单 func (a *AliPayService) HandleAliPaymentNotification(merchant string, form url.Values) (*alipay.Notification, error) {
err := r.ParseForm() client := a.clientForMerchant(merchant, nil)
if err != nil {
return nil, fmt.Errorf("解析请求表单失败:%v", err)
}
// 解析并验证通知DecodeNotification 会自动验证签名 // 解析并验证通知DecodeNotification 会自动验证签名
notification, err := a.AlipayClient.DecodeNotification(r.Form) notification, err := client.DecodeNotification(form)
if err != nil { if err != nil {
return nil, fmt.Errorf("验证签名失败: %v", err) return nil, fmt.Errorf("验证签名失败: %v", err)
} }
return notification, nil return notification, nil
} }
func (a *AliPayService) QueryOrderStatus(ctx context.Context, outTradeNo string) (*alipay.TradeQueryRsp, error) { // QueryOrderStatus 按商户标识查询支付宝订单状态
func (a *AliPayService) QueryOrderStatus(ctx context.Context, merchant string, outTradeNo string) (*alipay.TradeQueryRsp, error) {
client := a.clientForMerchant(merchant, nil)
queryRequest := alipay.TradeQuery{ queryRequest := alipay.TradeQuery{
OutTradeNo: outTradeNo, OutTradeNo: outTradeNo,
} }
// 发起查询请求 // 发起查询请求
resp, err := a.AlipayClient.TradeQuery(ctx, queryRequest) resp, err := client.TradeQuery(ctx, queryRequest)
if err != nil { if err != nil {
return nil, fmt.Errorf("查询支付宝订单失败: %v", err) return nil, fmt.Errorf("查询支付宝订单失败: %v", err)
} }

View File

@@ -2,6 +2,7 @@ package service
import ( import (
"context" "context"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -13,6 +14,7 @@ import (
"tyc-server/app/main/api/internal/config" "tyc-server/app/main/api/internal/config"
tianyuanapi "tyc-server/app/main/api/internal/service/tianyuanapi_sdk" tianyuanapi "tyc-server/app/main/api/internal/service/tianyuanapi_sdk"
"tyc-server/app/main/model" "tyc-server/app/main/model"
"tyc-server/pkg/lzkit/crypto"
"github.com/Masterminds/squirrel" "github.com/Masterminds/squirrel"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
@@ -37,18 +39,30 @@ type ApiRequestService struct {
featureModel model.FeatureModel featureModel model.FeatureModel
productFeatureModel model.ProductFeatureModel productFeatureModel model.ProductFeatureModel
tianyuanapi *tianyuanapi.Client tianyuanapi *tianyuanapi.Client
authService *AuthorizationService
} }
// NewApiRequestService 是一个构造函数,用于初始化 ApiRequestService // NewApiRequestService 是一个构造函数,用于初始化 ApiRequestService
func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, tianyuanapi *tianyuanapi.Client) *ApiRequestService { func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, tianyuanapi *tianyuanapi.Client, authService *AuthorizationService) *ApiRequestService {
return &ApiRequestService{ return &ApiRequestService{
config: c, config: c,
featureModel: featureModel, featureModel: featureModel,
productFeatureModel: productFeatureModel, productFeatureModel: productFeatureModel,
tianyuanapi: tianyuanapi, tianyuanapi: tianyuanapi,
authService: authService,
} }
} }
// keysOfMap 返回 map 的 key 列表,便于 debug 日志展示参数结构而不是完整明文
func keysOfMap(m map[string]interface{}) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
type APIResponseData struct { type APIResponseData struct {
ApiID string `json:"apiID"` ApiID string `json:"apiID"`
Data json.RawMessage `json:"data"` // 这里用 RawMessage 来存储原始的 data Data json.RawMessage `json:"data"` // 这里用 RawMessage 来存储原始的 data
@@ -58,7 +72,8 @@ type APIResponseData struct {
} }
// ProcessRequests 处理请求 // ProcessRequests 处理请求
func (a *ApiRequestService) ProcessRequests(params []byte, productID int64) ([]APIResponseData, error) { // orderNo: 当前查询对应的订单号用于为异步车辆类接口生成回调地址return_url
func (a *ApiRequestService) ProcessRequests(params []byte, productID int64, orderNo string) ([]APIResponseData, error) {
var ctx, cancel = context.WithCancel(context.Background()) var ctx, cancel = context.WithCancel(context.Background())
defer cancel() defer cancel()
build := a.productFeatureModel.SelectBuilder().Where(squirrel.Eq{ build := a.productFeatureModel.SelectBuilder().Where(squirrel.Eq{
@@ -85,6 +100,27 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID int64) ([]A
if len(featureList) == 0 { if len(featureList) == 0 {
return nil, errors.New("处理请求错误,产品无对应接口功能") return nil, errors.New("处理请求错误,产品无对应接口功能")
} }
// 在原始 params 上附加 order_no供异步车辆类接口自动生成回调地址使用
var baseParams map[string]interface{}
if err := json.Unmarshal(params, &baseParams); err != nil {
logx.Errorf("解析查询参数失败, Params: %s, Error: %v", string(params), err)
return nil, fmt.Errorf("解析查询参数失败: %w", err)
}
// 参数转换:将 mobile 转换为 mobile_no
if mobile, exists := baseParams["mobile"]; exists {
baseParams["mobile_no"] = mobile
}
if orderNo != "" {
baseParams["order_no"] = orderNo
}
paramsWithOrder, err := json.Marshal(baseParams)
if err != nil {
logx.Errorf("序列化查询参数失败, Params: %s, Error: %v", string(params), err)
return nil, fmt.Errorf("序列化查询参数失败: %w", err)
}
var ( var (
wg sync.WaitGroup wg sync.WaitGroup
resultsCh = make(chan APIResponseData, len(featureList)) resultsCh = make(chan APIResponseData, len(featureList))
@@ -118,7 +154,7 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID int64) ([]A
tryCount := 0 tryCount := 0
for { for {
tryCount++ tryCount++
resp, preprocessErr = a.PreprocessRequestApi(params, feature.ApiId) resp, preprocessErr = a.PreprocessRequestApi(paramsWithOrder, feature.ApiId)
if preprocessErr == nil { if preprocessErr == nil {
break break
} }
@@ -129,6 +165,9 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID int64) ([]A
} }
} }
if preprocessErr != nil { if preprocessErr != nil {
// 在这里添加日志记录入参
logx.Errorf("API请求处理器失败, ApiID: %s, Params: %s, Error: %v", feature.ApiId, string(paramsWithOrder), preprocessErr)
result.Timestamp = timestamp result.Timestamp = timestamp
result.Error = preprocessErr.Error() result.Error = preprocessErr.Error()
result.Data = resp result.Data = resp
@@ -175,9 +214,12 @@ var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, err
"BehaviorRiskScan": (*ApiRequestService).ProcessBehaviorRiskScanRequest, "BehaviorRiskScan": (*ApiRequestService).ProcessBehaviorRiskScanRequest,
"YYSYBE08": (*ApiRequestService).ProcessYYSYBE08Request, "YYSYBE08": (*ApiRequestService).ProcessYYSYBE08Request,
"YYSY09CD": (*ApiRequestService).ProcessYYSY09CDRequest, "YYSY09CD": (*ApiRequestService).ProcessYYSY09CDRequest,
"QCXGGB2Q": (*ApiRequestService).ProcessQCXGGB2QRequest,
"QCXGYTS2": (*ApiRequestService).ProcessQCXGYTS2Request,
"QCXG5F3A": (*ApiRequestService).ProcessQCXG5F3ARequest, //内部替换名下
"FLXG0687": (*ApiRequestService).ProcessFLXG0687Request, "FLXG0687": (*ApiRequestService).ProcessFLXG0687Request,
"FLXG3D56": (*ApiRequestService).ProcessFLXG3D56Request, "FLXG3D56": (*ApiRequestService).ProcessFLXG3D56Request,
"FLXG0V4B": (*ApiRequestService).ProcesFLXG0V4BRequest, "FLXG0V4B": (*ApiRequestService).ProcessFLXG0V4BRequest,
"QYGL8271": (*ApiRequestService).ProcessQYGL8271Request, "QYGL8271": (*ApiRequestService).ProcessQYGL8271Request,
"IVYZ5733": (*ApiRequestService).ProcessIVYZ5733Request, "IVYZ5733": (*ApiRequestService).ProcessIVYZ5733Request,
"IVYZ9A2B": (*ApiRequestService).ProcessIVYZ9A2BRequest, "IVYZ9A2B": (*ApiRequestService).ProcessIVYZ9A2BRequest,
@@ -197,6 +239,41 @@ var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, err
"IVYZ3P9M": (*ApiRequestService).ProcessIVYZ3P9MRequest, "IVYZ3P9M": (*ApiRequestService).ProcessIVYZ3P9MRequest,
"FLXG7E8F": (*ApiRequestService).ProcessFLXG7E8FRequest, "FLXG7E8F": (*ApiRequestService).ProcessFLXG7E8FRequest,
"QCXG9P1C": (*ApiRequestService).ProcessQCXG9P1CFRequest, "QCXG9P1C": (*ApiRequestService).ProcessQCXG9P1CFRequest,
// 车辆类接口(传参后续配置,先透传缓存 params
"QCXG4D2E": (*ApiRequestService).ProcessQCXG4D2ERequest,
"QCXG5U0Z": (*ApiRequestService).ProcessQCXG5U0ZRequest,
"QCXG1U4U": (*ApiRequestService).ProcessQCXG1U4URequest,
"QCXGY7F2": (*ApiRequestService).ProcessQCXGY7F2Request,
"QCXG1H7Y": (*ApiRequestService).ProcessQCXG1H7YRequest,
"QCXG4I1Z": (*ApiRequestService).ProcessQCXG4I1ZRequest,
"QCXG3Y6B": (*ApiRequestService).ProcessQCXG3Y6BRequest,
"QCXG3Z3L": (*ApiRequestService).ProcessQCXG3Z3LRequest,
"QCXGP00W": (*ApiRequestService).ProcessQCXGP00WRequest,
"QCXG6B4E": (*ApiRequestService).ProcessQCXG6B4ERequest,
// 核验工具verify feature.md
"IVYZ9K7F": (*ApiRequestService).ProcessIVYZ9K7FRequest,
"IVYZA1B3": (*ApiRequestService).ProcessIVYZA1B3Request,
"IVYZ6M8P": (*ApiRequestService).ProcessIVYZ6M8PRequest,
"JRZQ8B3C": (*ApiRequestService).ProcessJRZQ8B3CRequest,
"YYSY3M8S": (*ApiRequestService).ProcessYYSY3M8SRequest,
"YYSYK9R4": (*ApiRequestService).ProcessYYSYK9R4Request,
"YYSYF2T7": (*ApiRequestService).ProcessYYSYF2T7Request,
"YYSYK8R3": (*ApiRequestService).ProcessYYSYK8R3Request,
"YYSYS9W1": (*ApiRequestService).ProcessYYSYS9W1Request,
"YYSYE7V5": (*ApiRequestService).ProcessYYSYE7V5Request,
"YYSYP0T4": (*ApiRequestService).ProcessYYSYP0T4Request,
"YYSY6F2B": (*ApiRequestService).ProcessYYSY6F2BRequest,
"YYSY9E4A": (*ApiRequestService).ProcessYYSY9E4ARequest,
"QYGL5F6A": (*ApiRequestService).ProcessQYGL5F6ARequest,
"JRZQACAB": (*ApiRequestService).ProcessJRZQACABRequest,
"JRZQ0B6Y": (*ApiRequestService).ProcessJRZQ0B6YRequest,
// 新增司法涉诉类接口
"QYGL66SL": (*ApiRequestService).ProcessQYGL66SLRequest,
"FLXG3A9B": (*ApiRequestService).ProcessFLXG3A9BRequest,
"QYGL2S0W": (*ApiRequestService).ProcessQYGL2S0WRequest,
"FLXGDEA9": (*ApiRequestService).ProcessFLXGDEA9Request,
"IVYZ4Y27": (*ApiRequestService).ProcessIVYZ4Y27Request,
"IVYZ0S0D": (*ApiRequestService).ProcessIVYZ0S0DRequest,
} }
// PreprocessRequestApi 调用指定的请求处理函数 // PreprocessRequestApi 调用指定的请求处理函数
@@ -1104,6 +1181,382 @@ func (a *ApiRequestService) ProcessQYGL6F2DRequest(params []byte) ([]byte, error
return nil, fmt.Errorf("响应code错误%s", code.String()) return nil, fmt.Errorf("响应code错误%s", code.String())
} }
// ProcessQCXGGB2QRequest 人车核验简版
func (a *ApiRequestService) ProcessQCXGGB2QRequest(params []byte) ([]byte, error) {
plateNo := gjson.GetBytes(params, "plate_no")
carplateType := gjson.GetBytes(params, "carplate_type")
name := gjson.GetBytes(params, "name")
if !plateNo.Exists() || !carplateType.Exists() || !name.Exists() {
return nil, errors.New("api请求, QCXGGB2Q, 获取相关参数失败")
}
resp, err := a.tianyuanapi.CallInterface("QCXGGB2Q", map[string]interface{}{
"plate_no": plateNo.String(),
"carplate_type": carplateType.String(),
"name": name.String(),
})
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
// ProcessQCXGYTS2Request 人车核验详版
func (a *ApiRequestService) ProcessQCXGYTS2Request(params []byte) ([]byte, error) {
plateNo := gjson.GetBytes(params, "plate_no")
carplateType := gjson.GetBytes(params, "carplate_type")
name := gjson.GetBytes(params, "name")
if !plateNo.Exists() || !carplateType.Exists() || !name.Exists() {
return nil, errors.New("api请求, QCXGYTS2, 获取相关参数失败")
}
resp, err := a.tianyuanapi.CallInterface("QCXGYTS2", map[string]interface{}{
"plate_no": plateNo.String(),
"carplate_type": carplateType.String(),
"name": name.String(),
})
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
// ProcessQCXG5F3ARequest 名下车辆(车牌)
func (a *ApiRequestService) ProcessQCXG5F3ARequest(params []byte) ([]byte, error) {
idCard := gjson.GetBytes(params, "id_card")
name := gjson.GetBytes(params, "name")
if !idCard.Exists() || !name.Exists() {
return nil, errors.New("api请求, QCXG5F3A, 获取相关参数失败")
}
resp, err := a.tianyuanapi.CallInterface("QCXG5F3A", map[string]interface{}{
"id_card": idCard.String(),
"name": name.String(),
})
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
// processVehicleApiPassThrough 车辆类接口通用透传:将缓存 params 原样传给天远(传参后续按接口文档再细化)
func (a *ApiRequestService) processVehicleApiPassThrough(params []byte, apiID string) ([]byte, error) {
var m map[string]interface{}
if err := json.Unmarshal(params, &m); err != nil {
return nil, fmt.Errorf("api请求, %s, 解析参数失败: %w", apiID, err)
}
logx.Infof("vehicle api passthrough, api_id=%s, params_keys=%v", apiID, keysOfMap(m))
resp, err := a.tianyuanapi.CallInterface(apiID, m)
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
// 车辆 API 按 md 从缓存 params 提取字段并调用天远
func (a *ApiRequestService) ProcessQCXG4D2ERequest(params []byte) ([]byte, error) {
m := map[string]interface{}{}
if err := json.Unmarshal(params, &m); err != nil {
return nil, fmt.Errorf("api请求, QCXG4D2E, 解析参数失败: %w", err)
}
body := map[string]interface{}{}
if v, ok := m["user_type"].(string); ok && v != "" {
body["user_type"] = v
} else {
body["user_type"] = "1"
}
if v, ok := m["id_card"].(string); ok {
body["id_card"] = v
} else {
return nil, errors.New("api请求, QCXG4D2E, 缺少 id_card")
}
resp, err := a.tianyuanapi.CallInterface("QCXG4D2E", body)
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessQCXG5U0ZRequest(params []byte) ([]byte, error) {
vin := gjson.GetBytes(params, "vin_code")
if !vin.Exists() || vin.String() == "" {
return nil, errors.New("api请求, QCXG5U0Z, 缺少 vin_code")
}
resp, err := a.tianyuanapi.CallInterface("QCXG5U0Z", map[string]interface{}{"vin_code": vin.String()})
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessQCXG1U4URequest(params []byte) ([]byte, error) {
body := buildVehicleBody(params, []string{"vin_code", "image_url"}, nil)
orderNo := gjson.GetBytes(params, "order_no").String()
if body["vin_code"] == nil || body["image_url"] == nil || orderNo == "" {
return nil, errors.New("api请求, QCXG1U4U, 缺少必填参数 vin_code/image_url/order_no")
}
logx.Infof("vehicle api request QCXG1U4U, order_no=%s, vin_code=%v, image_url=%v", orderNo, body["vin_code"], body["image_url"])
body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG1U4U")
resp, err := a.tianyuanapi.CallInterface("QCXG1U4U", body)
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessQCXGY7F2Request(params []byte) ([]byte, error) {
body := buildVehicleBody(params, []string{"vin_code", "vehicle_location", "first_registrationdate"}, nil)
if body["vin_code"] == nil || body["vehicle_location"] == nil || body["first_registrationdate"] == nil {
return nil, errors.New("api请求, QCXGY7F2, 缺少必填参数 vin_code/vehicle_location/first_registrationdate")
}
logx.Infof("vehicle api request QCXGY7F2, vin_code=%v, vehicle_location=%v, first_registrationdate=%v", body["vin_code"], body["vehicle_location"], body["first_registrationdate"])
resp, err := a.tianyuanapi.CallInterface("QCXGY7F2", body)
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessQCXG1H7YRequest(params []byte) ([]byte, error) {
vin := gjson.GetBytes(params, "vin_code")
plate := gjson.GetBytes(params, "car_license")
if !vin.Exists() || vin.String() == "" || !plate.Exists() || plate.String() == "" {
return nil, errors.New("api请求, QCXG1H7Y, 缺少 vin_code 或 car_license")
}
// 天远侧字段为 vin_code + plate_no这里将前端 car_license 映射为 plate_no
body := map[string]interface{}{
"vin_code": vin.String(),
"plate_no": plate.String(),
}
logx.Infof("vehicle api request QCXG1H7Y, vin_code=%s, plate_no=%s", vin.String(), plate.String())
resp, err := a.tianyuanapi.CallInterface("QCXG1H7Y", body)
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessQCXG4I1ZRequest(params []byte) ([]byte, error) {
vin := gjson.GetBytes(params, "vin_code")
if !vin.Exists() || vin.String() == "" {
return nil, errors.New("api请求, QCXG4I1Z, 缺少 vin_code")
}
resp, err := a.tianyuanapi.CallInterface("QCXG4I1Z", map[string]interface{}{"vin_code": vin.String()})
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessQCXG3Y6BRequest(params []byte) ([]byte, error) {
body := buildVehicleBody(params, []string{"vin_code"}, nil)
orderNo := gjson.GetBytes(params, "order_no").String()
if body["vin_code"] == nil || orderNo == "" {
return nil, errors.New("api请求, QCXG3Y6B, 缺少必填参数 vin_code/order_no")
}
logx.Infof("vehicle api request QCXG3Y6B, order_no=%s, vin_code=%v", orderNo, body["vin_code"])
body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Y6B")
resp, err := a.tianyuanapi.CallInterface("QCXG3Y6B", body)
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessQCXG3Z3LRequest(params []byte) ([]byte, error) {
body := buildVehicleBody(params, []string{"vin_code"}, nil)
orderNo := gjson.GetBytes(params, "order_no").String()
if body["vin_code"] == nil || orderNo == "" {
return nil, errors.New("api请求, QCXG3Z3L, 缺少必填参数 vin_code/order_no")
}
logx.Infof("vehicle api request QCXG3Z3L, order_no=%s, vin_code=%v", orderNo, body["vin_code"])
body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Z3L")
resp, err := a.tianyuanapi.CallInterface("QCXG3Z3L", body)
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessQCXGP00WRequest(params []byte) ([]byte, error) {
vin := gjson.GetBytes(params, "vin_code")
orderNo := gjson.GetBytes(params, "order_no").String()
vlphoto := gjson.GetBytes(params, "vlphoto_data")
if !vin.Exists() || vin.String() == "" || orderNo == "" || !vlphoto.Exists() || vlphoto.String() == "" {
return nil, errors.New("api请求, QCXGP00W, 缺少必填参数 vin_code/order_no/vlphoto_data")
}
logx.Infof("vehicle api request QCXGP00W, order_no=%s, vin_code=%s, vlphoto_data_len=%d", orderNo, vin.String(), len(vlphoto.String()))
key, err := hex.DecodeString(a.config.Encrypt.SecretKey)
if err != nil {
return nil, fmt.Errorf("api请求, QCXGP00W, 密钥解析失败: %w", err)
}
encData, err := crypto.AesEncrypt([]byte(vlphoto.String()), key)
if err != nil {
return nil, fmt.Errorf("api请求, QCXGP00W, 加密行驶证数据失败: %w", err)
}
resp, err := a.tianyuanapi.CallInterface("QCXGP00W", map[string]interface{}{
"vin_code": vin.String(),
"return_url": a.buildVehicleCallbackURL(orderNo, "QCXGP00W"),
"data": encData,
})
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessQCXG6B4ERequest(params []byte) ([]byte, error) {
vin := gjson.GetBytes(params, "vin_code")
if !vin.Exists() || vin.String() == "" {
return nil, errors.New("api请求, QCXG6B4E, 缺少 vin_code")
}
auth := gjson.GetBytes(params, "authorized").String()
if auth == "" {
auth = "1"
}
// 天远文档字段名为 VINCode、Authorized
resp, err := a.tianyuanapi.CallInterface("QCXG6B4E", map[string]interface{}{
"vin_code": vin.String(),
"authorized": auth,
})
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
// processVerifyPassThrough 核验类接口:缓存 params 已含 mobile_no/id_card/name 等,原样传天远
func (a *ApiRequestService) processVerifyPassThrough(params []byte, apiID string) ([]byte, error) {
var m map[string]interface{}
if err := json.Unmarshal(params, &m); err != nil {
return nil, fmt.Errorf("api请求, %s, 解析参数失败: %w", apiID, err)
}
logx.Infof("verify api passthrough, api_id=%s, params_keys=%v", apiID, keysOfMap(m))
resp, err := a.tianyuanapi.CallInterface(apiID, m)
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
func (a *ApiRequestService) ProcessIVYZ9K7FRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "IVYZ9K7F")
}
func (a *ApiRequestService) ProcessIVYZA1B3Request(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "IVYZA1B3")
}
func (a *ApiRequestService) ProcessIVYZ6M8PRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "IVYZ6M8P")
}
func (a *ApiRequestService) ProcessJRZQ8B3CRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "JRZQ8B3C")
}
func (a *ApiRequestService) ProcessYYSY3M8SRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "YYSY3M8S")
}
func (a *ApiRequestService) ProcessYYSYK9R4Request(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "YYSYK9R4")
}
func (a *ApiRequestService) ProcessYYSYF2T7Request(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "YYSYF2T7")
}
func (a *ApiRequestService) ProcessYYSYK8R3Request(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "YYSYK8R3")
}
func (a *ApiRequestService) ProcessYYSYS9W1Request(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "YYSYS9W1")
}
func (a *ApiRequestService) ProcessYYSYE7V5Request(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "YYSYE7V5")
}
func (a *ApiRequestService) ProcessYYSYP0T4Request(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "YYSYP0T4")
}
func (a *ApiRequestService) ProcessYYSY6F2BRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "YYSY6F2B")
}
func (a *ApiRequestService) ProcessYYSY9E4ARequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "YYSY9E4A")
}
func (a *ApiRequestService) ProcessQYGL5F6ARequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "QYGL5F6A")
}
func (a *ApiRequestService) ProcessJRZQACABRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "JRZQACAB")
}
func (a *ApiRequestService) ProcessJRZQ0B6YRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "JRZQ0B6Y")
}
// QYGL66SL 企业司法涉诉(简版)
func (a *ApiRequestService) ProcessQYGL66SLRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "QYGL66SL")
}
// FLXG3A9B 限高被执行人
func (a *ApiRequestService) ProcessFLXG3A9BRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "FLXG3A9B")
}
// QYGL2S0W 失信被执行人
func (a *ApiRequestService) ProcessQYGL2S0WRequest(params []byte) ([]byte, error) {
return a.processVerifyPassThrough(params, "QYGL2S0W")
}
// FLXGDEA9 本人不良
func (a *ApiRequestService) ProcessFLXGDEA9Request(params []byte) ([]byte, error) {
var m map[string]interface{}
if err := json.Unmarshal(params, &m); err != nil {
return nil, fmt.Errorf("api请求, FLXGDEA9, 解析参数失败: %w", err)
}
// 授权由后端默认传 1前端与查询服务不再感知 authorized
if v, ok := m["authorized"]; !ok || v == "" {
m["authorized"] = "1"
}
resp, err := a.tianyuanapi.CallInterface("FLXGDEA9", m)
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
// buildVehicleBody 从 params 中取 required 与 optional 键,仅非空才写入 body
func buildVehicleBody(params []byte, required, optional []string) map[string]interface{} {
body := make(map[string]interface{})
for _, k := range required {
v := gjson.GetBytes(params, k)
if v.Exists() {
body[k] = v.String()
}
}
for _, k := range optional {
v := gjson.GetBytes(params, k)
if v.Exists() && v.String() != "" {
body[k] = v.String()
}
}
return body
}
// buildVehicleCallbackURL 生成车辆类接口的异步回调地址
// 使用 PublicBaseURL 作为对外域名配置,路径固定为 /api/v1/tianyuan/vehicle/callback
// 并通过查询参数携带 order_no 与 api_id 以便后端识别具体查询与模块。
func (a *ApiRequestService) buildVehicleCallbackURL(orderNo, apiID string) string {
base := strings.TrimRight(a.config.PublicBaseURL, "/")
if base == "" {
// 兜底:如果未配置 URLDomain则使用相对路径交给网关/部署层补全域名
return fmt.Sprintf("/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", orderNo, apiID)
}
return fmt.Sprintf("%s/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", base, orderNo, apiID)
}
// ProcessQCXG7A2BRequest 名下车辆 // ProcessQCXG7A2BRequest 名下车辆
func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) { func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) {
idCard := gjson.GetBytes(params, "id_card") idCard := gjson.GetBytes(params, "id_card")
@@ -1478,3 +1931,54 @@ func (a *ApiRequestService) ProcessQCXG9P1CFRequest(params []byte) ([]byte, erro
return convertTianyuanResponse(resp) return convertTianyuanResponse(resp)
} }
// ProcessIVYZ4Y27Request 学历信息查询需生成专用授权书PDF并Base64编码传入
func (a *ApiRequestService) ProcessIVYZ4Y27Request(params []byte) ([]byte, error) {
name := gjson.GetBytes(params, "name")
idCard := gjson.GetBytes(params, "id_card")
mobile := gjson.GetBytes(params, "mobile")
if !name.Exists() || !idCard.Exists() {
return nil, errors.New("api请求, IVYZ4Y27, 获取相关参数失败")
}
// 生成专用授权书PDF并Base64编码
userInfo := map[string]interface{}{
"name": name.String(),
"id_card": idCard.String(),
"mobile": mobile.String(),
}
authFileBase64, err := a.authService.GenerateIVYZ4Y27AuthorizationBase64(userInfo)
if err != nil {
return nil, fmt.Errorf("生成IVYZ4Y27授权书失败: %v", err)
}
resp, err := a.tianyuanapi.CallInterface("IVYZ4Y27", map[string]interface{}{
"name": name.String(),
"id_card": idCard.String(),
"auth_authorize_file_base64": authFileBase64,
})
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}
// ProcessIVYZ0S0DRequest 劳动仲裁信息查询(个人版)
func (a *ApiRequestService) ProcessIVYZ0S0DRequest(params []byte) ([]byte, error) {
idCard := gjson.GetBytes(params, "id_card")
name := gjson.GetBytes(params, "name")
if !idCard.Exists() || !name.Exists() {
return nil, errors.New("api请求, IVYZ0S0D, 获取相关参数失败")
}
resp, err := a.tianyuanapi.CallInterface("IVYZ0S0D", map[string]interface{}{
"id_card": idCard.String(),
"name": name.String(),
})
if err != nil {
return nil, err
}
return convertTianyuanResponse(resp)
}

View File

@@ -4,11 +4,13 @@ import (
"bytes" "bytes"
"context" "context"
"database/sql" "database/sql"
"encoding/base64"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"text/template"
"time" "time"
"tyc-server/app/main/api/internal/config" "tyc-server/app/main/api/internal/config"
"tyc-server/app/main/model" "tyc-server/app/main/model"
@@ -329,3 +331,116 @@ func getUserInfoString(userInfo map[string]interface{}, key string) string {
} }
return "" return ""
} }
// GenerateIVYZ4Y27AuthorizationBase64 生成IVYZ4Y27专用授权书PDF并返回Base64编码字符串
func (s *AuthorizationService) GenerateIVYZ4Y27AuthorizationBase64(userInfo map[string]interface{}) (string, error) {
name := getUserInfoString(userInfo, "name")
idCard := getUserInfoString(userInfo, "id_card")
if name == "" || idCard == "" {
return "", fmt.Errorf("缺少必要的用户信息name或id_card")
}
// 构建模板变量
data := map[string]string{
"CompanyName": "广西福铭网络科技有限公司",
"Name": name,
"IdCard": idCard,
"Mobile": getUserInfoString(userInfo, "mobile"),
"Date": time.Now().Format("2006年1月2日"),
}
// 从模板生成PDF
pdfBytes, err := s.generatePDFFromTemplate("static/authorization_ivyz4y27.tmpl", data)
if err != nil {
return "", errors.Wrapf(err, "生成IVYZ4Y27授权书PDF失败")
}
// Base64编码
return base64.StdEncoding.EncodeToString(pdfBytes), nil
}
// generatePDFFromTemplate 从模板生成PDF
func (s *AuthorizationService) generatePDFFromTemplate(templatePath string, data interface{}) ([]byte, error) {
// 1. 读取模板文件
tmplContent, err := os.ReadFile(templatePath)
if err != nil {
// 尝试从项目根目录读取
absPath, _ := filepath.Abs(templatePath)
if _, err := os.Stat(absPath); err == nil {
tmplContent, err = os.ReadFile(absPath)
}
if err != nil {
return nil, errors.Wrapf(err, "读取模板文件失败: %s", templatePath)
}
}
// 2. 解析并执行模板
tmpl, err := template.New("auth").Parse(string(tmplContent))
if err != nil {
return nil, errors.Wrapf(err, "解析模板失败")
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return nil, errors.Wrapf(err, "执行模板失败")
}
content := buf.String()
// 3. 创建PDF
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
// 4. 加载中文字体
fontPaths := []string{
"static/SIMHEI.TTF",
"/app/static/SIMHEI.TTF",
"app/main/api/static/SIMHEI.TTF",
"../static/SIMHEI.TTF",
}
fontAdded := false
for _, fontPath := range fontPaths {
if _, err := os.Stat(fontPath); err == nil {
pdf.AddUTF8Font("ChineseFont", "", fontPath)
fontAdded = true
logx.Infof("generatePDFFromTemplate 成功加载字体: %s", fontPath)
break
}
}
if fontAdded {
pdf.SetFont("ChineseFont", "", 12)
} else {
pdf.SetFont("Arial", "", 12)
logx.Errorf("generatePDFFromTemplate 未找到中文字体文件使用默认Arial字体")
}
// 5. 写入内容
// 简单处理:按行分割并使用 MultiCell
lines := strings.Split(content, "\n")
for _, line := range lines {
if strings.TrimSpace(line) == "" {
pdf.Ln(5)
continue
}
// MultiCell 支持自动换行
pdf.MultiCell(0, 6, line, "", "L", false)
}
// 6. 添加水印 (简单实现:在页面中间添加浅灰色文字)
if fontAdded {
pdf.SetFont("ChineseFont", "", 40)
pdf.SetTextColor(220, 220, 220) // 非常浅的灰色
// 将坐标移动到页面中间附近并旋转或倾斜gofpdf 旋转比较复杂,这里简单放几个位置)
pdf.Text(40, 100, "仅供背景调查使用")
pdf.Text(40, 180, "仅供背景调查使用")
pdf.Text(40, 260, "仅供背景调查使用")
}
// 7. 输出PDF
var pdfBuf bytes.Buffer
if err := pdf.Output(&pdfBuf); err != nil {
return nil, errors.Wrapf(err, "输出PDF字节数组失败")
}
return pdfBuf.Bytes(), nil
}

View File

@@ -0,0 +1,150 @@
package tianxingjuhe
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
// Client 天行聚合API客户端
type Client struct {
baseURL string
key string
client *http.Client
}
// Config 客户端配置
type Config struct {
BaseURL string // API基础URL
Key string // API密钥
Timeout int // 超时时间(秒)
}
// Response 通用API响应结构
type Response struct {
Code int `json:"code"` // 状态码200表示成功
Msg string `json:"msg"` // 返回说明
Result interface{} `json:"result"` // 返回结果集,具体内容根据接口而定
}
// NewClient 创建新的客户端实例
func NewClient(config Config) (*Client, error) {
if config.BaseURL == "" {
return nil, fmt.Errorf("baseURL不能为空")
}
if config.Key == "" {
return nil, fmt.Errorf("key不能为空")
}
return &Client{
baseURL: config.BaseURL,
key: config.Key,
client: &http.Client{
Timeout: time.Duration(config.Timeout) * time.Second,
},
}, nil
}
// Get 发送GET请求
func (c *Client) Get(endpoint string, params map[string]interface{}) (*Response, error) {
// 构建完整URL
fullURL := fmt.Sprintf("%s/%s", c.baseURL, endpoint)
// 添加请求参数
queryParams := url.Values{}
for key, value := range params {
queryParams.Set(key, fmt.Sprintf("%v", value))
}
// 添加key参数
queryParams.Set("key", c.key)
// 拼接查询参数
if len(queryParams) > 0 {
fullURL = fmt.Sprintf("%s?%s", fullURL, queryParams.Encode())
}
// 创建HTTP请求
req, err := http.NewRequest("GET", fullURL, nil)
if err != nil {
return nil, fmt.Errorf("创建HTTP请求失败: %v", err)
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Tianxingjuhe-Go-SDK/1.0.0")
// 发送请求
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("发送HTTP请求失败: %v", err)
}
defer resp.Body.Close()
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %v", err)
}
// 解析响应
var apiResp Response
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
}
return &apiResp, nil
}
// Post 发送POST请求
func (c *Client) Post(endpoint string, params map[string]interface{}) (*Response, error) {
// 构建完整URL
fullURL := fmt.Sprintf("%s/%s", c.baseURL, endpoint)
// 添加key参数
if params == nil {
params = make(map[string]interface{})
}
params["key"] = c.key
// 序列化请求体
requestBody, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("序列化请求体失败: %v", err)
}
// 创建HTTP请求
req, err := http.NewRequest("POST", fullURL, bytes.NewBuffer(requestBody))
if err != nil {
return nil, fmt.Errorf("创建HTTP请求失败: %v", err)
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Tianxingjuhe-Go-SDK/1.0.0")
// 发送请求
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("发送HTTP请求失败: %v", err)
}
defer resp.Body.Close()
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %v", err)
}
// 解析响应
var apiResp Response
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
}
return &apiResp, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,11 @@ package service
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
"tyc-server/app/main/api/internal/config" "tyc-server/app/main/api/internal/config"
"tyc-server/app/main/model" "tyc-server/app/main/model"
@@ -24,6 +26,14 @@ import (
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
// wxpayAPIHTTPStatus 安全读取微信支付 API 的 HTTP 状态码;本地签名/随机串失败时 result 可能为 nil。
func wxpayAPIHTTPStatus(result *core.APIResult) int {
if result == nil || result.Response == nil {
return 0
}
return result.Response.StatusCode
}
const ( const (
TradeStateSuccess = "SUCCESS" // 支付成功 TradeStateSuccess = "SUCCESS" // 支付成功
TradeStateRefund = "REFUND" // 转入退款 TradeStateRefund = "REFUND" // 转入退款
@@ -172,14 +182,54 @@ func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount floa
// 发起预支付请求 // 发起预支付请求
resp, result, err := svc.Prepay(ctx, payRequest) resp, result, err := svc.Prepay(ctx, payRequest)
logx.Infof("微信app支付订单resp: %+v, result: %+v, err: %+v", resp, result, err)
if err != nil { if err != nil {
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result))
} }
// 返回预支付交易会话标识 // 返回预支付交易会话标识
return *resp.PrepayId, nil return *resp.PrepayId, nil
} }
// jsapiRequestPaymentToMap 将 JSAPI 调起参数转为 map[string]string便于 JSON 序列化给前端 WeixinJSBridge
func jsapiRequestPaymentToMap(resp *jsapi.PrepayWithRequestPaymentResponse) (map[string]string, error) {
if resp == nil {
return nil, fmt.Errorf("微信下单返回为空")
}
m := map[string]string{}
if resp.Appid != nil {
m["appId"] = *resp.Appid
}
if resp.TimeStamp != nil {
m["timeStamp"] = *resp.TimeStamp
}
if resp.NonceStr != nil {
m["nonceStr"] = *resp.NonceStr
}
if resp.Package != nil {
m["package"] = *resp.Package
}
if resp.SignType != nil && *resp.SignType != "" {
m["signType"] = *resp.SignType
} else {
m["signType"] = "RSA"
}
if resp.PaySign != nil {
m["paySign"] = *resp.PaySign
}
var missing []string
for _, key := range []string{"appId", "timeStamp", "nonceStr", "package", "paySign"} {
if m[key] == "" {
missing = append(missing, key)
}
}
if len(missing) > 0 {
logx.Errorf("[WechatPay] JSAPI 调起参数缺项: missing=%v resp=%s", missing, resp.String())
return nil, fmt.Errorf("微信 JSAPI 调起参数不完整: 缺少或为空 %v", missing)
}
return m, nil
}
// CreateWechatMiniProgramOrder 创建微信小程序支付订单 // CreateWechatMiniProgramOrder 创建微信小程序支付订单
func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) { func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) {
totalAmount := lzUtils.ToWechatAmount(amount) totalAmount := lzUtils.ToWechatAmount(amount)
@@ -203,17 +253,18 @@ func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amo
// 发起预支付请求 // 发起预支付请求
resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest) resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest)
logx.Infof("微信小程序支付订单resp: %+v, result: %+v, err: %+v", resp, result, err)
if err != nil { if err != nil {
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result))
} }
// 返回预支付交易会话标识 return jsapiRequestPaymentToMap(resp)
return resp, nil
} }
// CreateWechatH5Order 创建微信H5支付订单 // CreateWechatH5Order 创建微信H5支付订单
func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) { func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) {
totalAmount := lzUtils.ToWechatAmount(amount) totalAmount := lzUtils.ToWechatAmount(amount)
logx.Infof("微信h5支付订单amount: %+v, description: %+v, outTradeNo: %+v, openid: %+v", amount, description, outTradeNo, openid)
// 构建支付请求参数 // 构建支付请求参数
payRequest := jsapi.PrepayRequest{ payRequest := jsapi.PrepayRequest{
Appid: core.String(w.config.WechatH5.AppID), Appid: core.String(w.config.WechatH5.AppID),
@@ -231,20 +282,31 @@ func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float
// 初始化 AppApiService // 初始化 AppApiService
svc := jsapi.JsapiApiService{Client: w.wechatClient} svc := jsapi.JsapiApiService{Client: w.wechatClient}
logx.Infof("微信h5支付订单payRequest: %+v", payRequest)
// 发起预支付请求 // 发起预支付请求
resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest) resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest)
logx.Infof("微信h5支付订单resp: %+v, result: %+v, err: %+v", resp, result, err) logx.Infof("微信h5支付订单resp: %+v, result: %+v, err: %+v", resp, result, err)
if err != nil { if err != nil {
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) logx.Infof("微信h5支付订单resp: %+v, result: %+v, err: %+v", resp, result, err)
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result))
} }
// 返回预支付交易会话标识 return jsapiRequestPaymentToMap(resp)
return resp, nil
} }
// CreateWechatOrder 创建微信支付订单(集成 APP、H5、小程序 // CreateWechatOrder 创建微信支付订单(集成 APP、H5、小程序
func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string) (interface{}, error) { func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string) (interface{}, error) {
// 根据 ctx 中的 platform 判断平台 platformVal := ctx.Value("platform")
platform := ctx.Value("platform").(string) platform, ok := platformVal.(string)
rawPlatform := platform
platform = strings.TrimSpace(platform)
logx.WithContext(ctx).Infof(
"[WechatPay] CreateWechatOrder platform ctx: value_type=%T assert_ok=%v raw=%q trimmed=%q ref_wxh5=%q ref_wxmini=%q ref_app=%q out_trade_no=%s",
platformVal, ok, rawPlatform, platform, model.PlatformWxH5, model.PlatformWxMini, model.PlatformApp, outTradeNo,
)
if !ok || platform == "" {
logx.WithContext(ctx).Errorf("[WechatPay] CreateWechatOrder 缺少 X-Platform")
return "", fmt.Errorf("缺少 X-Platform 请求头(微信内请传 wxh5")
}
var prepayData interface{} var prepayData interface{}
var err error var err error
@@ -264,15 +326,33 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64
return "", err return "", err
} }
case model.PlatformWxH5: case model.PlatformWxH5:
logx.WithContext(ctx).Infof(
"[WechatPay] CreateWechatOrder branch=wxh5 out_trade_no=%s amount=%.2f desc_len=%d",
outTradeNo, amount, len(description),
)
userID, getUidErr := ctxdata.GetUidFromCtx(ctx) userID, getUidErr := ctxdata.GetUidFromCtx(ctx)
if getUidErr != nil { if getUidErr != nil {
return "", getUidErr return "", getUidErr
} }
userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID) // 微信内置浏览器 JSAPI 必须使用与商户公众号一致的 openidsnsapi_base / snsapi_userinfo 授权后写入 wxh5_openid
if findAuthModelErr != nil { // 不可使用小程序 openid 兜底AppID 与 openid 主体不一致会导致下单失败或调起异常。
return "", findAuthModelErr h5Auth, h5Err := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID)
if h5Err != nil {
if errors.Is(h5Err, model.ErrNotFound) {
logx.WithContext(ctx).Infof(
"[WechatPay] wxh5 缺少 user_auth(wxh5_openid) user_id=%d out_trade_no=%s需先走公众号网页授权(建议 scope=snsapi_base)",
userID, outTradeNo,
)
return "", fmt.Errorf("微信内支付需先完成公众号网页授权以获取 openid建议使用 snsapi_base 静默授权)")
} }
prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, userAuthModel.AuthKey) return "", h5Err
}
if strings.TrimSpace(h5Auth.AuthKey) == "" {
logx.WithContext(ctx).Errorf("[WechatPay] wxh5_openid 记录存在但 auth_key 为空 user_id=%d", userID)
return "", fmt.Errorf("微信内支付 openid 未就绪,请重新完成公众号网页授权")
}
logx.Infof("微信h5支付订单userAuthModel(wxh5): %+v", h5Auth)
prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, strings.TrimSpace(h5Auth.AuthKey))
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -288,6 +368,21 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64
return "", fmt.Errorf("支付订单创建失败: %v", err) return "", fmt.Errorf("支付订单创建失败: %v", err)
} }
if prepayData == nil {
logx.WithContext(ctx).Errorf("[WechatPay] CreateWechatOrder 返回 prepayData 为 nil platform=%q", platform)
return nil, fmt.Errorf("微信支付返回数据为空 platform=%s", platform)
}
if m, isMap := prepayData.(map[string]string); isMap {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
logx.WithContext(ctx).Infof("[WechatPay] CreateWechatOrder return prepay: platform=%q type=map[string]string len=%d keys=%v", platform, len(m), keys)
} else {
logx.WithContext(ctx).Infof("[WechatPay] CreateWechatOrder return prepay: platform=%q type=%T", platform, prepayData)
}
// 返回预支付ID // 返回预支付ID
return prepayData, nil return prepayData, nil
} }
@@ -323,7 +418,7 @@ func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID s
Mchid: core.String(w.config.Wxpay.MchID), Mchid: core.String(w.config.Wxpay.MchID),
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode) return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result))
} }
return resp, nil return resp, nil

View File

@@ -5,6 +5,7 @@ import (
"tyc-server/app/main/api/internal/config" "tyc-server/app/main/api/internal/config"
"tyc-server/app/main/api/internal/middleware" "tyc-server/app/main/api/internal/middleware"
"tyc-server/app/main/api/internal/service" "tyc-server/app/main/api/internal/service"
tianxingjuhe "tyc-server/app/main/api/internal/service/tianxingjuhe_sdk"
tianyuanapi "tyc-server/app/main/api/internal/service/tianyuanapi_sdk" tianyuanapi "tyc-server/app/main/api/internal/service/tianyuanapi_sdk"
"tyc-server/app/main/model" "tyc-server/app/main/model"
@@ -23,6 +24,7 @@ type ServiceContext struct {
// 中间件 // 中间件
AuthInterceptor rest.Middleware AuthInterceptor rest.Middleware
UserAuthInterceptor rest.Middleware UserAuthInterceptor rest.Middleware
UserDisableInterceptor rest.Middleware
AdminAuthInterceptor rest.Middleware AdminAuthInterceptor rest.Middleware
// 用户相关模型 // 用户相关模型
@@ -100,6 +102,8 @@ type ServiceContext struct {
AdminPromotionLinkStatsService *service.AdminPromotionLinkStatsService AdminPromotionLinkStatsService *service.AdminPromotionLinkStatsService
ImageService *service.ImageService ImageService *service.ImageService
AuthorizationService *service.AuthorizationService AuthorizationService *service.AuthorizationService
ToolboxService *service.ToolboxService
TianxingjuheService *tianxingjuhe.Client
} }
// NewServiceContext 创建服务上下文 // NewServiceContext 创建服务上下文
@@ -187,13 +191,22 @@ func NewServiceContext(c config.Config) *ServiceContext {
logx.Errorf("初始化天远API失败: %+v", err) logx.Errorf("初始化天远API失败: %+v", err)
} }
// 初始化天行聚合API客户端
tianxingjuhe, err := tianxingjuhe.NewClient(tianxingjuhe.Config{
BaseURL: c.Tianxingjuhe.URL,
Key: c.Tianxingjuhe.Key,
Timeout: c.Tianxingjuhe.Timeout,
})
if err != nil {
logx.Errorf("初始化天行聚合API失败: %+v", err)
}
// ============================== 业务服务初始化 ============================== // ============================== 业务服务初始化 ==============================
alipayService := service.NewAliPayService(c) alipayService := service.NewAliPayService(c)
// wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey) wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey)
// 为暂时关闭微信支付,将 WechatPayService 置为 nil避免在项目启动时初始化微信支付相关配置
var wechatPayService *service.WechatPayService
applePayService := service.NewApplePayService(c) applePayService := service.NewApplePayService(c)
apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, tianyuanapi) authorizationService := service.NewAuthorizationService(c, authorizationDocumentModel)
apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, tianyuanapi, authorizationService)
verificationService := service.NewVerificationService(c, tianyuanapi, apiRequestService) verificationService := service.NewVerificationService(c, tianyuanapi, apiRequestService)
asynqService := service.NewAsynqService(c) asynqService := service.NewAsynqService(c)
agentService := service.NewAgentService(c, orderModel, agentModel, agentAuditModel, agentClosureModel, agentService := service.NewAgentService(c, orderModel, agentModel, agentAuditModel, agentClosureModel,
@@ -206,7 +219,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
adminPromotionLinkStatsService := service.NewAdminPromotionLinkStatsService(adminPromotionLinkModel, adminPromotionLinkStatsService := service.NewAdminPromotionLinkStatsService(adminPromotionLinkModel,
adminPromotionLinkStatsTotalModel, adminPromotionLinkStatsHistoryModel) adminPromotionLinkStatsTotalModel, adminPromotionLinkStatsHistoryModel)
imageService := service.NewImageService() imageService := service.NewImageService()
authorizationService := service.NewAuthorizationService(c, authorizationDocumentModel) toolboxService := service.NewToolboxService(tianxingjuhe)
tianxingjuheService := tianxingjuhe
// ============================== 异步任务服务 ============================== // ============================== 异步任务服务 ==============================
asynqServer := asynq.NewServer( asynqServer := asynq.NewServer(
@@ -226,6 +240,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
Redis: redisClient, Redis: redisClient,
AuthInterceptor: middleware.NewAuthInterceptorMiddleware(c).Handle, AuthInterceptor: middleware.NewAuthInterceptorMiddleware(c).Handle,
UserAuthInterceptor: middleware.NewUserAuthInterceptorMiddleware().Handle, UserAuthInterceptor: middleware.NewUserAuthInterceptorMiddleware().Handle,
UserDisableInterceptor: middleware.NewUserDisableInterceptorMiddleware(userModel).Handle,
AdminAuthInterceptor: middleware.NewAdminAuthInterceptorMiddleware(c, AdminAuthInterceptor: middleware.NewAdminAuthInterceptorMiddleware(c,
adminUserModel, adminUserRoleModel, adminRoleModel, adminApiModel, adminRoleApiModel).Handle, adminUserModel, adminUserRoleModel, adminRoleModel, adminApiModel, adminRoleApiModel).Handle,
@@ -304,6 +319,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
AdminPromotionLinkStatsService: adminPromotionLinkStatsService, AdminPromotionLinkStatsService: adminPromotionLinkStatsService,
ImageService: imageService, ImageService: imageService,
AuthorizationService: authorizationService, AuthorizationService: authorizationService,
ToolboxService: toolboxService,
TianxingjuheService: tianxingjuheService,
} }
} }

View File

@@ -45,12 +45,10 @@ type PreLoanBackgroundCheckReq struct {
Code string `json:"code" validate:"required"` Code string `json:"code" validate:"required"`
} }
// BackgroundCheck 查询请求结构
type BackgroundCheckReq struct { type BackgroundCheckReq struct {
Name string `json:"name" validate:"required,name"` Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"` IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
} }
type PersonalDataReq struct { type PersonalDataReq struct {
Name string `json:"name" validate:"required,name"` Name string `json:"name" validate:"required,name"`
@@ -64,6 +62,38 @@ type EntLawsuitReq struct {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"` Code string `json:"code" validate:"required"`
} }
// EnterpriseLawsuitSimple 企业司法涉诉简版QYGL66SL仅企业名称授权信息由后端补齐
type EnterpriseLawsuitSimpleReq struct {
EntName string `json:"ent_name" validate:"required"`
}
// LimitHighExecuted 限高被执行人FLXG3A9B
type LimitHighExecutedReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Authorized string `json:"authorized"` // 可选,后端默认 1
}
// DishonestExecuted 失信被执行人QYGL2S0W
type DishonestExecutedReq struct {
Type string `json:"type"` // 可选,后端默认 per
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
}
// PersonalBadRecord 本人不良FLXGDEA9姓名 + 身份证(授权由 ApiRequest 默认传 1
type PersonalBadRecordReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
}
// TocPersonEnterprisePro 人企关系加强版预查询:仅身份证号
type TocPersonEnterpriseProReq struct {
IDCard string `json:"id_card" validate:"required,idCard"`
}
type TocPhoneThreeElements struct { type TocPhoneThreeElements struct {
Name string `json:"name" validate:"required,name"` Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"` IDCard string `json:"id_card" validate:"required,idCard"`
@@ -83,6 +113,16 @@ type TocDualMarriage struct {
NameWoman string `json:"name_woman" validate:"required,name"` NameWoman string `json:"name_woman" validate:"required,name"`
IDCardWoman string `json:"id_card_woman" validate:"required,idCard"` IDCardWoman string `json:"id_card_woman" validate:"required,idCard"`
} }
// TocDualMarriageReq 双人婚姻状态查询(含手机号与验证码)
type TocDualMarriageReq struct {
NameMan string `json:"name_man" validate:"required,name"`
IDCardMan string `json:"id_card_man" validate:"required,idCard"`
NameWoman string `json:"name_woman" validate:"required,name"`
IDCardWoman string `json:"id_card_woman" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
}
type TocPersonVehicleVerification struct { type TocPersonVehicleVerification struct {
Name string `json:"name" validate:"required,name"` Name string `json:"name" validate:"required,name"`
CarType string `json:"car_type" validate:"required"` CarType string `json:"car_type" validate:"required"`
@@ -112,8 +152,170 @@ type AgentQueryData struct {
Mobile string `json:"mobile"` Mobile string `json:"mobile"`
Code string `json:"code"` Code string `json:"code"`
} }
// 名下车辆(数量) QCXG4D2E仅 user_type(1-ETC开户人 2-车辆所有人 3-ETC经办人)+id_card无验证码
type TocVehiclesUnderNameCountReq struct {
UserType string `json:"user_type"` // 必填,默认 1
IDCard string `json:"id_card" validate:"required,idCard"`
}
// 车辆静态信息/过户详版等 仅 vin_code车辆类不要求手机号与验证码
type TocVehicleVinCodeReq struct {
VinCode string `json:"vin_code" validate:"required"`
Mobile string `json:"mobile"`
Code string `json:"code"`
}
// 车辆里程记录(混合) QCXG1U4U
type TocVehicleMileageMixedReq struct {
VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no"`
// 回调地址由后端在调用第三方接口时自动生成,不再由前端透传
ReturnURL string `json:"return_url"`
ImageURL string `json:"image_url" validate:"required"`
RegURL string `json:"reg_url"`
EngineNumber string `json:"engine_number"`
Mobile string `json:"mobile"`
Code string `json:"code"`
}
// 二手车VIN估值 QCXGY7F2
type TocVehicleVinValuationReq struct {
VinCode string `json:"vin_code" validate:"required"`
VehicleName string `json:"vehicle_name"`
VehicleLocation string `json:"vehicle_location" validate:"required"`
FirstRegistrationDate string `json:"first_registrationdate" validate:"required"` // yyyy-MM
Color string `json:"color"`
Mobile string `json:"mobile"`
Code string `json:"code"`
}
// 车辆过户简版 QCXG1H7Y
type TocVehicleTransferSimpleReq struct {
VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no"`
Mobile string `json:"mobile"`
Code string `json:"code"`
}
// 车辆维保简版 QCXG3Y6B
type TocVehicleMaintenanceSimpleReq struct {
VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no"`
// 回调地址由后端在调用第三方接口时自动生成,不再由前端透传
ReturnURL string `json:"return_url"`
ImageURL string `json:"image_url"`
RegURL string `json:"reg_url"`
EngineNumber string `json:"engine_number"`
Mobile string `json:"mobile"`
Code string `json:"code"`
}
// 车辆维保详细版 QCXG3Z3L
type TocVehicleMaintenanceDetailReq struct {
VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no"`
// 回调地址由后端在调用第三方接口时自动生成,不再由前端透传
ReturnURL string `json:"return_url"`
ImageURL string `json:"image_url"`
EngineNumber string `json:"engine_number"`
Mobile string `json:"mobile"`
Code string `json:"code"`
}
// 车辆出险详版 QCXGP00Wvlphoto_data 加密后以 data 字段提交
type TocVehicleClaimDetailReq struct {
VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no"`
// 回调地址由后端在调用第三方接口时自动生成,不再由前端透传
ReturnURL string `json:"return_url"`
VlphotoData string `json:"vlphoto_data" validate:"required"` // 行驶证图片 base64加密后传 API 的 data
Mobile string `json:"mobile"`
Code string `json:"code"`
}
// 车辆出险记录核验 QCXG6B4EVINCodeAuthorized 由后端默认传 1
type TocVehicleClaimVerifyReq struct {
VINCode string `json:"vin_code" validate:"required"`
Authorized string `json:"authorized"` // 可选,后端默认 1
Mobile string `json:"mobile"`
Code string `json:"code"`
}
type AgentIdentifier struct { type AgentIdentifier struct {
Product string `json:"product"` Product string `json:"product"`
AgentID int64 `json:"agent_id"` AgentID int64 `json:"agent_id"`
Price string `json:"price"` Price string `json:"price"`
} }
// --------------- 核验工具verify feature.md---------------
// 公安二要素 IVYZ9K7F请求用 mobile缓存/API 用 mobile_no
type TocVerifyAuthTwoReq struct {
MobileNo string `json:"mobile" validate:"required,mobile"` // 前端传 mobile
IDCard string `json:"id_card" validate:"required,idCard"`
Name string `json:"name" validate:"required,name"`
}
// 公安三要素 IVYZA1B3photo_data, id_card, name
type TocVerifyAuthThreeReq struct {
PhotoData string `json:"photo_data" validate:"required"` // 人像 base64
IDCard string `json:"id_card" validate:"required,idCard"`
Name string `json:"name" validate:"required,name"`
}
// 职业资格证书 IVYZ6M8Pid_card, name
type TocVerifyCertReq struct {
IDCard string `json:"id_card" validate:"required,idCard"`
Name string `json:"name" validate:"required,name"`
}
// 个人消费能力 JRZQ8B3C
type TocVerifyConsumptionReq struct {
MobileNo string `json:"mobile" validate:"required,mobile"`
IDCard string `json:"id_card" validate:"required,idCard"`
Name string `json:"name" validate:"required,name"`
}
// 运营商二要素 YYSY3M8S
type TocVerifyYysTwoReq struct {
MobileNo string `json:"mobile" validate:"required,mobile"`
Name string `json:"name" validate:"required,name"`
}
// 全网手机三要素 YYSYK9R4
type TocVerifyYysThreeReq struct {
MobileNo string `json:"mobile" validate:"required,mobile"`
IDCard string `json:"id_card" validate:"required,idCard"`
Name string `json:"name" validate:"required,name"`
}
// 仅手机号YYSYF2T7/YYSYK8R3/YYSYS9W1/YYSYE7V5/YYSYP0T4/YYSY9E4A
type TocVerifyMobileOnlyReq struct {
MobileNo string `json:"mobile" validate:"required,mobile"`
}
// 手机消费区间 YYSY6F2B
type TocVerifyYysConsumptionReq struct {
MobileNo string `json:"mobile" validate:"required,mobile"`
Authorized string `json:"authorized" validate:"required"` // 0/1
}
// 名下企业关联 QYGL5F6Aid_card 选填
type TocVerifyEntRelationReq struct {
IDCard string `json:"id_card"` // 可选
}
// 银行卡四要素 JRZQACAB
type TocVerifyBankFourReq struct {
MobileNo string `json:"mobile" validate:"required,mobile"`
IDCard string `json:"id_card" validate:"required,idCard"`
BankCard string `json:"bank_card" validate:"required"`
Name string `json:"name" validate:"required,name"`
}
// 银行卡黑名单 JRZQ0B6Y
type TocVerifyBankBlackReq struct {
MobileNo string `json:"mobile" validate:"required,mobile"`
IDCard string `json:"id_card" validate:"required,idCard"`
Name string `json:"name" validate:"required,name"`
BankCard string `json:"bank_card" validate:"required"`
}

View File

@@ -533,7 +533,7 @@ type AdminGetOrderListReq struct {
RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始 RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始
RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束 RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束
SalesCost float64 `form:"sales_cost,optional"` // 成本价 SalesCost float64 `form:"sales_cost,optional"` // 成本价
QueriedName string `form:"queried_name,optional"` // 被查询人姓名(明文) QueriedName string `form:"queried_name,optional"` // 被查询人姓名(明文,后端加密后匹配 query_user_record
QueriedIdCard string `form:"queried_id_card,optional"` // 被查询人身份证(明文) QueriedIdCard string `form:"queried_id_card,optional"` // 被查询人身份证(明文)
QueriedMobile string `form:"queried_mobile,optional"` // 被查询人手机号(明文) QueriedMobile string `form:"queried_mobile,optional"` // 被查询人手机号(明文)
} }
@@ -568,6 +568,7 @@ type AdminGetPlatformUserDetailResp struct {
Nickname string `json:"nickname"` // 昵称 Nickname string `json:"nickname"` // 昵称
Info string `json:"info"` // 备注信息 Info string `json:"info"` // 备注信息
Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否
Disable int64 `json:"disable"` // 是否封禁 0-可用 1-禁用
CreateTime string `json:"create_time"` // 创建时间 CreateTime string `json:"create_time"` // 创建时间
UpdateTime string `json:"update_time"` // 更新时间 UpdateTime string `json:"update_time"` // 更新时间
} }
@@ -674,7 +675,12 @@ type AdminGetQueryDetailByOrderIdResp struct {
Id int64 `json:"id"` // 主键ID Id int64 `json:"id"` // 主键ID
OrderId int64 `json:"order_id"` // 订单ID OrderId int64 `json:"order_id"` // 订单ID
UserId int64 `json:"user_id"` // 用户ID UserId int64 `json:"user_id"` // 用户ID
ProductName string `json:"product_name"` // 产品ID ProductName string `json:"product_name"` // 产品名称
OrderNo string `json:"order_no"` // 商户订单号
PlatformOrderId string `json:"platform_order_id"` // 支付订单号
PaymentStatus string `json:"payment_status"` // 支付状态
PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间(有退款时)
QueryParams map[string]interface{} `json:"query_params"` QueryParams map[string]interface{} `json:"query_params"`
QueryData []AdminQueryItem `json:"query_data"` QueryData []AdminQueryItem `json:"query_data"`
CreateTime string `json:"create_time"` // 创建时间 CreateTime string `json:"create_time"` // 创建时间
@@ -811,6 +817,7 @@ type AdminReviewBankCardWithdrawalReq struct {
WithdrawalId int64 `json:"withdrawal_id"` // 提现记录ID WithdrawalId int64 `json:"withdrawal_id"` // 提现记录ID
Action int64 `json:"action"` // 操作:1-确认,2-拒绝 Action int64 `json:"action"` // 操作:1-确认,2-拒绝
Remark string `json:"remark"` // 备注(拒绝时必填) Remark string `json:"remark"` // 备注(拒绝时必填)
TaxRate *float64 `json:"tax_rate,optional"` // 扣税比例,如 0.06 表示 6%,不传则默认 6%
} }
type AdminReviewBankCardWithdrawalResp struct { type AdminReviewBankCardWithdrawalResp struct {
@@ -949,6 +956,7 @@ type AdminUpdatePlatformUserReq struct {
Nickname *string `json:"nickname,optional"` // 昵称 Nickname *string `json:"nickname,optional"` // 昵称
Info *string `json:"info,optional"` // 备注信息 Info *string `json:"info,optional"` // 备注信息
Inside *int64 `json:"inside,optional"` // 是否内部用户 1-是 0-否 Inside *int64 `json:"inside,optional"` // 是否内部用户 1-是 0-否
Disable *int64 `json:"disable,optional"` // 是否封禁 0-可用 1-禁用
} }
type AdminUpdatePlatformUserResp struct { type AdminUpdatePlatformUserResp struct {
@@ -1300,6 +1308,7 @@ type AgentWithdrawalListItem struct {
Amount float64 `json:"amount"` // 金额 Amount float64 `json:"amount"` // 金额
ActualAmount float64 `json:"actual_amount"` // 实际到账金额(扣税后) ActualAmount float64 `json:"actual_amount"` // 实际到账金额(扣税后)
TaxAmount float64 `json:"tax_amount"` // 扣税金额 TaxAmount float64 `json:"tax_amount"` // 扣税金额
TaxRate float64 `json:"tax_rate"` // 扣税比例,如 0.06 表示 6%
Status int64 `json:"status"` // 状态 Status int64 `json:"status"` // 状态
PayeeAccount string `json:"payee_account"` // 收款账户 PayeeAccount string `json:"payee_account"` // 收款账户
Remark string `json:"remark"` // 备注 Remark string `json:"remark"` // 备注
@@ -1538,6 +1547,10 @@ type GetCommissionResp struct {
List []Commission `json:"list"` // 查询列表 List []Commission `json:"list"` // 查询列表
} }
type GetEncryptedSceneIdResp struct {
EncryptedSceneId string `json:"encryptedSceneId"`
}
type GetLinkDataReq struct { type GetLinkDataReq struct {
LinkIdentifier string `form:"link_identifier"` LinkIdentifier string `form:"link_identifier"`
} }
@@ -1838,7 +1851,7 @@ type PaymentCheckResp struct {
type PaymentReq struct { type PaymentReq struct {
Id string `json:"id"` Id string `json:"id"`
PayMethod string `json:"pay_method"` PayMethod string `json:"pay_method" validate:"required,oneof=wechat alipay appleiap test"`
PayType string `json:"pay_type" validate:"required,oneof=query agent_vip"` PayType string `json:"pay_type" validate:"required,oneof=query agent_vip"`
} }
@@ -1854,6 +1867,7 @@ type PlatformUserListItem struct {
Nickname string `json:"nickname"` // 昵称 Nickname string `json:"nickname"` // 昵称
Info string `json:"info"` // 备注信息 Info string `json:"info"` // 备注信息
Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否
Disable int64 `json:"disable"` // 是否封禁 0-可用 1-禁用
CreateTime string `json:"create_time"` // 创建时间 CreateTime string `json:"create_time"` // 创建时间
UpdateTime string `json:"update_time"` // 更新时间 UpdateTime string `json:"update_time"` // 更新时间
} }
@@ -2005,6 +2019,7 @@ type QueryItem struct {
type QueryListReq struct { type QueryListReq struct {
Page int64 `form:"page"` // 页码 Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数据量 PageSize int64 `form:"page_size"` // 每页数据量
Source string `form:"source,optional"` // 来源: miniapp 小程序过滤非车辆产品
} }
type QueryListResp struct { type QueryListResp struct {
@@ -2044,6 +2059,7 @@ type QueryServiceReq struct {
Data string `json:"data" validate:"required"` Data string `json:"data" validate:"required"`
AgentIdentifier string `json:"agent_identifier,optional"` AgentIdentifier string `json:"agent_identifier,optional"`
App bool `json:"app,optional"` App bool `json:"app,optional"`
CaptchaVerifyParam string `json:"captchaVerifyParam,optional"`
} }
type QueryServiceResp struct { type QueryServiceResp struct {
@@ -2105,11 +2121,40 @@ type SaveAgentMembershipUserConfigReq struct {
PriceRatio float64 `json:"price_ratio"` PriceRatio float64 `json:"price_ratio"`
} }
type ServeUploadedFileReq struct {
FileName string `path:"fileName"` // 文件名,如 uuid.jpg
}
type ServeUploadedFileResp struct {
FilePath string `json:"-"` // 内部:本地文件路径
ContentType string `json:"-"` // 内部Content-Type
}
type TimeRangeReport struct { type TimeRangeReport struct {
Commission float64 `json:"commission"` // 佣金 Commission float64 `json:"commission"` // 佣金
Report int `json:"report"` // 报告量 Report int `json:"report"` // 报告量
} }
type ToolboxQueryReq struct {
ToolKey string `json:"tool_key" validate:"required"`
Params map[string]interface{} `json:"params"`
}
type ToolboxQueryResp struct {
ToolKey string `json:"tool_key"`
Result map[string]interface{} `json:"result"`
}
type ToolInfo struct {
Key string `json:"key"`
Name string `json:"name"`
Desc string `json:"desc"`
}
type ToolboxListResp struct {
Tools []ToolInfo `json:"tools"`
}
type UpdateMenuReq struct { type UpdateMenuReq struct {
Id int64 `path:"id"` // 菜单ID Id int64 `path:"id"` // 菜单ID
Pid int64 `json:"pid,optional"` // 父菜单ID Pid int64 `json:"pid,optional"` // 父菜单ID
@@ -2160,6 +2205,14 @@ type UpdateRoleResp struct {
Success bool `json:"success"` // 是否成功 Success bool `json:"success"` // 是否成功
} }
type UploadImageReq struct {
ImageBase64 string `json:"image_base64" validate:"required"` // 图片 base64不含 data URL 前缀)
}
type UploadImageResp struct {
Url string `json:"url"` // 可公网访问的图片 URL
}
type User struct { type User struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Mobile string `json:"mobile"` Mobile string `json:"mobile"`
@@ -2198,6 +2251,7 @@ type Withdrawal struct {
Remark string `json:"remark"` Remark string `json:"remark"`
PayeeAccount string `json:"payee_account"` PayeeAccount string `json:"payee_account"`
CreateTime string `json:"create_time"` CreateTime string `json:"create_time"`
TaxRate float64 `json:"tax_rate"` // 扣税比例,如 0.06 表示 6%
} }
type WithdrawalReq struct { type WithdrawalReq struct {
@@ -2219,4 +2273,5 @@ type GetAppVersionResp struct {
type SendSmsReq struct { type SendSmsReq struct {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply realName bindMobile"` ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply realName bindMobile"`
CaptchaVerifyParam string `json:"captchaVerifyParam"`
} }

View File

@@ -0,0 +1,85 @@
【重要提示】为了保障您的合法权益,您在签署本授权书前,应当确保您为具有完全民事权利能力和民事行为能力的自然人,并审慎阅读、充分理解本授权书所有条款(特别是加粗字体的条款)。您在操作页面上的确认、勾选等行为或以其他方式接受即表示您已阅读并同意本授权书,本授权书随即在法律上生效并在您和{{.CompanyName}}之间产生法律约束力。
个人信息处理授权书
致:{{.CompanyName}}
本人("授权人")在此确认、同意并授权{{.CompanyName}}"贵司")及贵司授权的第三方,为本文件所列之目的,处理(指收集、整理、存储、使用、加工、分析和比对、核验、传输、提供等)本人的个人信息。
一、 授权处理的个人信息范围
1.本人同意并授权贵司处理以下类型的个人信息(无论以电子或非电子形式存在):
身份信息:包括但不限于姓名、身份证号码。
教育背景信息:包括但不限于各教育阶段的入学与毕业时间、毕业院校、专业、学位类型、学历学位证书信息、学信网验证信息等。
婚姻家庭信息:包括但不限于婚姻状况(未婚、已婚、离异等)。
工作履历信息:包括但不限于过往及当前工作单位、任职时间、职位、职责、证明人及其联系方式等。
其他与背景调查相关的信息:贵司为完成背景调查认为必要的其他信息。
2.本人知悉且同意,贵司拟处理的数据信息可能包含身份证件信息、生物识别信息等敏感信息,本人明确知悉对本人权益的影响且同意贵司处理该等信息。
二、 信息来源与查询方式
本人知晓并同意,贵司为完成背景调查,将通过以下方式查询并核实本授权书第一条所列的个人信息:
1. 通过本人或委托贵司开展背景调查的拟入职机构提供的联系方式和证明材料进行直接核实。
2. 通过贵司链接的权威数据源进行查询或验证、核实,包括但不限于政府部门或事业单位、社会团体、公用事业服务单位、第三方数据库或数据平台(如从中国高等教育学生信息网-学信网)等。本人授权贵司通过地方信用服务平台向前述数据源单位收集、处理、加工和分析本人的婚姻家庭信息、教育背景信息。
3. 通过向本人前任及现任雇主、同事、证明人等进行访谈核实。
4. 在法律允许的范围内,通过其他公开、合法的渠道进行核实。
三、 信息处理的目的与使用
本人同意贵司将本授权书项下收集的个人信息用于以下目的:
1. 为{{.CompanyName}}"委托方")对本人进行的员工招聘/录用前背景调查/持续的尽职调查提供信息核实与报告服务。
2. 基于核实的信息,生成关于本人的《背景调查报告》或《尽职调查报告》("报告"),并提供给委托方,以供其评估本人的任职资格与工作能力。
3. 贵司为内部质量控制和合规目的而对相关信息进行留存、处理与管理。
四、 信息存储与保护
贵司承诺将采取必要的技术和管理措施,保护本人个人信息的安全性与机密性,防止信息丢失、泄露、篡改或毁损。
除非法律法规另有规定或为履行本授权书目的之必要贵司处理及存储本人个人信息的时间将在完成本次背景调查目的后12个月内或直至委托方与本人的劳动关系确立或明确终止之时。具体留存期限届满后贵司将依法对相关信息进行删除或匿名化处理。删除个人信息从技术上难以实现的贵司将停止除存储和采取必要的安全保护措施之外的处理。
五、 授权转移与共享
(一)本人知晓并同意,为实现本授权书之目的,贵司可能将本人的个人信息提供给以下接收方:
1. 委托方(即本次背景调查的发起企业/拟入职机构)。
2. 为完成特定核实工作而必需的第三方合作伙伴,如数据源提供方(包括但不限于地方融资信用服务平台、政府部门或事业单位)等,但贵司应确保该等第三方受到与本授权书同等严格的保密义务约束。
六、 权利告知与行使
1.本人知悉并理解,根据相关法律法规,本人有权:
1查阅、复制及要求更正本人的个人信息
2在满足法定条件时要求删除个人信息或撤回本授权同意。
本人确认,撤回本授权同意将不影响撤回前基于本授权已进行的个人信息处理活动的效力。但若撤回授权,可能导致委托方无法完整评估本人的任职资格,从而可能影响本次招聘/录用结果,本人将自行承担相应后果。
2. 本人知悉:如本人对贵司或数据源单位个人信息处理活动有任何疑问、意见建议或需要依法行使权利,可通过以下联系方式进行咨询、反映或行使法定权利:
贵司:【联系方式:{{.Mobile}}】
七、 全部协议与授权效力
本授权书自本人同意之日起生效,并在上述业务办理及存续期间持续有效,至本授权书所述的所有业务终结之日止。
本人已仔细阅读并完全理解本授权书的全部内容,特别是加粗字体部分。本人的签署是基于本人的真实意愿,本人知悉且理解由此产生的法律效力及相应信息披露产生的不利后果(包括不限于第三方通过非法手段或方式获取、使用该信息,可能会给本人造成人身财产的损害,或是造成本人预期利益减少、损失扩大,或是其他不良或不利影响等),自愿作出上述授权。
授权人签署:{{.Name}}
身份证号码:{{.IdCard}}
签署日期:{{.Date}}

View File

@@ -10,8 +10,6 @@ import (
"time" "time"
"tyc-server/common/globalkey"
"github.com/Masterminds/squirrel" "github.com/Masterminds/squirrel"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/builder" "github.com/zeromicro/go-zero/core/stores/builder"
@@ -19,6 +17,7 @@ import (
"github.com/zeromicro/go-zero/core/stores/sqlc" "github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
"tyc-server/common/globalkey"
) )
var ( var (
@@ -65,6 +64,7 @@ type (
LevelName string `db:"level_name"` // 会员级别,如 VIPSVIPnormal LevelName string `db:"level_name"` // 会员级别,如 VIPSVIPnormal
Amount float64 `db:"amount"` // 充值金额 Amount float64 `db:"amount"` // 充值金额
PaymentMethod string `db:"payment_method"` // 支付方式:支付宝,微信,苹果支付,其他 PaymentMethod string `db:"payment_method"` // 支付方式:支付宝,微信,苹果支付,其他
PaymentMerchant string `db:"payment_merchant"` // 支付商户标识,例如 one/two
OrderNo string `db:"order_no"` // 交易号 OrderNo string `db:"order_no"` // 交易号
PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号 PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号
Status string `db:"status"` Status string `db:"status"`
@@ -89,11 +89,11 @@ func (m *defaultAgentMembershipRechargeOrderModel) Insert(ctx context.Context, s
tycAgentMembershipRechargeOrderOrderNoKey := fmt.Sprintf("%s%v", cacheTycAgentMembershipRechargeOrderOrderNoPrefix, data.OrderNo) tycAgentMembershipRechargeOrderOrderNoKey := fmt.Sprintf("%s%v", cacheTycAgentMembershipRechargeOrderOrderNoPrefix, data.OrderNo)
tycAgentMembershipRechargeOrderPlatformOrderIdKey := fmt.Sprintf("%s%v", cacheTycAgentMembershipRechargeOrderPlatformOrderIdPrefix, data.PlatformOrderId) tycAgentMembershipRechargeOrderPlatformOrderIdKey := fmt.Sprintf("%s%v", cacheTycAgentMembershipRechargeOrderPlatformOrderIdPrefix, data.PlatformOrderId)
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, agentMembershipRechargeOrderRowsExpectAutoSet) query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, agentMembershipRechargeOrderRowsExpectAutoSet)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, data.UserId, data.AgentId, data.LevelName, data.Amount, data.PaymentMethod, data.OrderNo, data.PlatformOrderId, data.Status, data.DeleteTime, data.DelState, data.Version) return session.ExecCtx(ctx, query, data.UserId, data.AgentId, data.LevelName, data.Amount, data.PaymentMethod, data.PaymentMerchant, data.OrderNo, data.PlatformOrderId, data.Status, data.DeleteTime, data.DelState, data.Version)
} }
return conn.ExecCtx(ctx, query, data.UserId, data.AgentId, data.LevelName, data.Amount, data.PaymentMethod, data.OrderNo, data.PlatformOrderId, data.Status, data.DeleteTime, data.DelState, data.Version) return conn.ExecCtx(ctx, query, data.UserId, data.AgentId, data.LevelName, data.Amount, data.PaymentMethod, data.PaymentMerchant, data.OrderNo, data.PlatformOrderId, data.Status, data.DeleteTime, data.DelState, data.Version)
}, tycAgentMembershipRechargeOrderIdKey, tycAgentMembershipRechargeOrderOrderNoKey, tycAgentMembershipRechargeOrderPlatformOrderIdKey) }, tycAgentMembershipRechargeOrderIdKey, tycAgentMembershipRechargeOrderOrderNoKey, tycAgentMembershipRechargeOrderPlatformOrderIdKey)
} }
@@ -165,9 +165,9 @@ func (m *defaultAgentMembershipRechargeOrderModel) Update(ctx context.Context, s
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, agentMembershipRechargeOrderRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, agentMembershipRechargeOrderRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) return session.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.PaymentMerchant, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id)
} }
return conn.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) return conn.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.PaymentMerchant, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id)
}, tycAgentMembershipRechargeOrderIdKey, tycAgentMembershipRechargeOrderOrderNoKey, tycAgentMembershipRechargeOrderPlatformOrderIdKey) }, tycAgentMembershipRechargeOrderIdKey, tycAgentMembershipRechargeOrderOrderNoKey, tycAgentMembershipRechargeOrderPlatformOrderIdKey)
} }
@@ -189,9 +189,9 @@ func (m *defaultAgentMembershipRechargeOrderModel) UpdateWithVersion(ctx context
sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, agentMembershipRechargeOrderRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, agentMembershipRechargeOrderRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) return session.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.PaymentMerchant, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion)
} }
return conn.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) return conn.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.PaymentMerchant, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion)
}, tycAgentMembershipRechargeOrderIdKey, tycAgentMembershipRechargeOrderOrderNoKey, tycAgentMembershipRechargeOrderPlatformOrderIdKey) }, tycAgentMembershipRechargeOrderIdKey, tycAgentMembershipRechargeOrderOrderNoKey, tycAgentMembershipRechargeOrderPlatformOrderIdKey)
if err != nil { if err != nil {
return err return err

View File

@@ -10,8 +10,6 @@ import (
"time" "time"
"tyc-server/common/globalkey"
"github.com/Masterminds/squirrel" "github.com/Masterminds/squirrel"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/builder" "github.com/zeromicro/go-zero/core/stores/builder"
@@ -19,6 +17,7 @@ import (
"github.com/zeromicro/go-zero/core/stores/sqlc" "github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
"tyc-server/common/globalkey"
) )
var ( var (
@@ -63,6 +62,7 @@ type (
ProductId int64 `db:"product_id"` // 产品ID软关联到产品表 ProductId int64 `db:"product_id"` // 产品ID软关联到产品表
PaymentPlatform string `db:"payment_platform"` // 支付平台(支付宝、微信、苹果内购、其他) PaymentPlatform string `db:"payment_platform"` // 支付平台(支付宝、微信、苹果内购、其他)
PaymentScene string `db:"payment_scene"` // 支付场景App、H5、微信小程序、公众号 PaymentScene string `db:"payment_scene"` // 支付场景App、H5、微信小程序、公众号
PaymentMerchant string `db:"payment_merchant"` // 支付商户标识,例如 one/two
PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号 PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号
Amount float64 `db:"amount"` // 支付金额 Amount float64 `db:"amount"` // 支付金额
Status string `db:"status"` // 支付状态 Status string `db:"status"` // 支付状态
@@ -90,11 +90,11 @@ func (m *defaultOrderModel) Insert(ctx context.Context, session sqlx.Session, da
tycOrderIdKey := fmt.Sprintf("%s%v", cacheTycOrderIdPrefix, data.Id) tycOrderIdKey := fmt.Sprintf("%s%v", cacheTycOrderIdPrefix, data.Id)
tycOrderOrderNoKey := fmt.Sprintf("%s%v", cacheTycOrderOrderNoPrefix, data.OrderNo) tycOrderOrderNoKey := fmt.Sprintf("%s%v", cacheTycOrderOrderNoPrefix, data.OrderNo)
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, orderRowsExpectAutoSet) query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, orderRowsExpectAutoSet)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, data.OrderNo, data.UserId, data.ProductId, data.PaymentPlatform, data.PaymentScene, data.PlatformOrderId, data.Amount, data.Status, data.DelState, data.Version, data.PayTime, data.RefundTime, data.CloseTime, data.DeleteTime, data.SalesCost) return session.ExecCtx(ctx, query, data.OrderNo, data.UserId, data.ProductId, data.PaymentPlatform, data.PaymentScene, data.PaymentMerchant, data.PlatformOrderId, data.Amount, data.Status, data.DelState, data.Version, data.PayTime, data.RefundTime, data.CloseTime, data.DeleteTime, data.SalesCost)
} }
return conn.ExecCtx(ctx, query, data.OrderNo, data.UserId, data.ProductId, data.PaymentPlatform, data.PaymentScene, data.PlatformOrderId, data.Amount, data.Status, data.DelState, data.Version, data.PayTime, data.RefundTime, data.CloseTime, data.DeleteTime, data.SalesCost) return conn.ExecCtx(ctx, query, data.OrderNo, data.UserId, data.ProductId, data.PaymentPlatform, data.PaymentScene, data.PaymentMerchant, data.PlatformOrderId, data.Amount, data.Status, data.DelState, data.Version, data.PayTime, data.RefundTime, data.CloseTime, data.DeleteTime, data.SalesCost)
}, tycOrderIdKey, tycOrderOrderNoKey) }, tycOrderIdKey, tycOrderOrderNoKey)
} }
@@ -145,9 +145,9 @@ func (m *defaultOrderModel) Update(ctx context.Context, session sqlx.Session, ne
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, orderRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, orderRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id) return session.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PaymentMerchant, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id)
} }
return conn.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id) return conn.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PaymentMerchant, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id)
}, tycOrderIdKey, tycOrderOrderNoKey) }, tycOrderIdKey, tycOrderOrderNoKey)
} }
@@ -168,9 +168,9 @@ func (m *defaultOrderModel) UpdateWithVersion(ctx context.Context, session sqlx.
sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, orderRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, orderRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id, oldVersion) return session.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PaymentMerchant, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id, oldVersion)
} }
return conn.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id, oldVersion) return conn.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PaymentMerchant, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id, oldVersion)
}, tycOrderIdKey, tycOrderOrderNoKey) }, tycOrderIdKey, tycOrderOrderNoKey)
if err != nil { if err != nil {
return err return err

View File

@@ -10,8 +10,6 @@ import (
"time" "time"
"tyc-server/common/globalkey"
"github.com/Masterminds/squirrel" "github.com/Masterminds/squirrel"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/builder" "github.com/zeromicro/go-zero/core/stores/builder"
@@ -19,6 +17,7 @@ import (
"github.com/zeromicro/go-zero/core/stores/sqlc" "github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx" "github.com/zeromicro/go-zero/core/stringx"
"tyc-server/common/globalkey"
) )
var ( var (
@@ -64,6 +63,7 @@ type (
OrderId int64 `db:"order_id"` // 关联的订单ID OrderId int64 `db:"order_id"` // 关联的订单ID
UserId int64 `db:"user_id"` // 用户ID UserId int64 `db:"user_id"` // 用户ID
ProductId int64 `db:"product_id"` // 产品ID ProductId int64 `db:"product_id"` // 产品ID
PaymentMerchant string `db:"payment_merchant"` // 退款对应的支付商户标识,例如 one/two
PlatformRefundId sql.NullString `db:"platform_refund_id"` // 支付平台退款单号 PlatformRefundId sql.NullString `db:"platform_refund_id"` // 支付平台退款单号
RefundAmount float64 `db:"refund_amount"` // 退款金额 RefundAmount float64 `db:"refund_amount"` // 退款金额
RefundReason sql.NullString `db:"refund_reason"` // 退款原因 RefundReason sql.NullString `db:"refund_reason"` // 退款原因
@@ -91,11 +91,11 @@ func (m *defaultOrderRefundModel) Insert(ctx context.Context, session sqlx.Sessi
tycOrderRefundPlatformRefundIdKey := fmt.Sprintf("%s%v", cacheTycOrderRefundPlatformRefundIdPrefix, data.PlatformRefundId) tycOrderRefundPlatformRefundIdKey := fmt.Sprintf("%s%v", cacheTycOrderRefundPlatformRefundIdPrefix, data.PlatformRefundId)
tycOrderRefundRefundNoKey := fmt.Sprintf("%s%v", cacheTycOrderRefundRefundNoPrefix, data.RefundNo) tycOrderRefundRefundNoKey := fmt.Sprintf("%s%v", cacheTycOrderRefundRefundNoPrefix, data.RefundNo)
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, orderRefundRowsExpectAutoSet) query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, orderRefundRowsExpectAutoSet)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, data.RefundNo, data.OrderId, data.UserId, data.ProductId, data.PlatformRefundId, data.RefundAmount, data.RefundReason, data.Status, data.DelState, data.Version, data.RefundTime, data.CloseTime, data.DeleteTime) return session.ExecCtx(ctx, query, data.RefundNo, data.OrderId, data.UserId, data.ProductId, data.PaymentMerchant, data.PlatformRefundId, data.RefundAmount, data.RefundReason, data.Status, data.DelState, data.Version, data.RefundTime, data.CloseTime, data.DeleteTime)
} }
return conn.ExecCtx(ctx, query, data.RefundNo, data.OrderId, data.UserId, data.ProductId, data.PlatformRefundId, data.RefundAmount, data.RefundReason, data.Status, data.DelState, data.Version, data.RefundTime, data.CloseTime, data.DeleteTime) return conn.ExecCtx(ctx, query, data.RefundNo, data.OrderId, data.UserId, data.ProductId, data.PaymentMerchant, data.PlatformRefundId, data.RefundAmount, data.RefundReason, data.Status, data.DelState, data.Version, data.RefundTime, data.CloseTime, data.DeleteTime)
}, tycOrderRefundIdKey, tycOrderRefundPlatformRefundIdKey, tycOrderRefundRefundNoKey) }, tycOrderRefundIdKey, tycOrderRefundPlatformRefundIdKey, tycOrderRefundRefundNoKey)
} }
@@ -167,9 +167,9 @@ func (m *defaultOrderRefundModel) Update(ctx context.Context, session sqlx.Sessi
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, orderRefundRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, orderRefundRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id) return session.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PaymentMerchant, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id)
} }
return conn.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id) return conn.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PaymentMerchant, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id)
}, tycOrderRefundIdKey, tycOrderRefundPlatformRefundIdKey, tycOrderRefundRefundNoKey) }, tycOrderRefundIdKey, tycOrderRefundPlatformRefundIdKey, tycOrderRefundRefundNoKey)
} }
@@ -191,9 +191,9 @@ func (m *defaultOrderRefundModel) UpdateWithVersion(ctx context.Context, session
sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, orderRefundRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, orderRefundRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id, oldVersion) return session.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PaymentMerchant, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id, oldVersion)
} }
return conn.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id, oldVersion) return conn.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PaymentMerchant, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id, oldVersion)
}, tycOrderRefundIdKey, tycOrderRefundPlatformRefundIdKey, tycOrderRefundRefundNoKey) }, tycOrderRefundIdKey, tycOrderRefundPlatformRefundIdKey, tycOrderRefundRefundNoKey)
if err != nil { if err != nil {
return err return err

View File

@@ -68,6 +68,7 @@ type (
Nickname sql.NullString `db:"nickname"` Nickname sql.NullString `db:"nickname"`
Info string `db:"info"` Info string `db:"info"`
Inside int64 `db:"inside"` Inside int64 `db:"inside"`
Disable int64 `db:"disable"` // 0可用 1禁用
} }
) )
@@ -83,11 +84,11 @@ func (m *defaultUserModel) Insert(ctx context.Context, session sqlx.Session, dat
tycUserIdKey := fmt.Sprintf("%s%v", cacheTycUserIdPrefix, data.Id) tycUserIdKey := fmt.Sprintf("%s%v", cacheTycUserIdPrefix, data.Id)
tycUserMobileKey := fmt.Sprintf("%s%v", cacheTycUserMobilePrefix, data.Mobile) tycUserMobileKey := fmt.Sprintf("%s%v", cacheTycUserMobilePrefix, data.Mobile)
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?)", m.table, userRowsExpectAutoSet) query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, userRowsExpectAutoSet)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside) return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable)
} }
return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside) return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable)
}, tycUserIdKey, tycUserMobileKey) }, tycUserIdKey, tycUserMobileKey)
} }
@@ -138,9 +139,9 @@ func (m *defaultUserModel) Update(ctx context.Context, session sqlx.Session, new
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Id) return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id)
} }
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Id) return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id)
}, tycUserIdKey, tycUserMobileKey) }, tycUserIdKey, tycUserMobileKey)
} }
@@ -161,9 +162,9 @@ func (m *defaultUserModel) UpdateWithVersion(ctx context.Context, session sqlx.S
sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, userRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, userRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Id, oldVersion) return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id, oldVersion)
} }
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Id, oldVersion) return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id, oldVersion)
}, tycUserIdKey, tycUserMobileKey) }, tycUserIdKey, tycUserMobileKey)
if err != nil { if err != nil {
return err return err

View File

@@ -90,7 +90,7 @@ func GetPlatformFromCtx(ctx context.Context) (string, error) {
} }
switch platform { switch platform {
case model.PlatformWxMini: case model.PlatformWxMini, "mp-weixin": // 兼容旧客户端误传的 uni 平台名
return model.PlatformWxMini, nil return model.PlatformWxMini, nil
case model.PlatformWxH5: case model.PlatformWxH5:
return model.PlatformWxH5, nil return model.PlatformWxH5, nil
@@ -99,6 +99,6 @@ func GetPlatformFromCtx(ctx context.Context) (string, error) {
case model.PlatformH5: case model.PlatformH5:
return model.PlatformH5, nil return model.PlatformH5, nil
default: default:
return "", fmt.Errorf("不支持的支付平台: %s", platform) return "", fmt.Errorf("不支持的客户端平台: %s", platform)
} }
} }

View File

@@ -16,6 +16,7 @@ const PARAM_VERIFICATION_ERROR uint32 = 100007
const CUSTOM_ERROR uint32 = 100008 const CUSTOM_ERROR uint32 = 100008
const USER_NOT_FOUND uint32 = 100009 const USER_NOT_FOUND uint32 = 100009
const USER_NEED_BIND_MOBILE uint32 = 100010 const USER_NEED_BIND_MOBILE uint32 = 100010
const USER_DISABLED uint32 = 100011 // 用户已被封禁
const LOGIN_FAILED uint32 = 200001 const LOGIN_FAILED uint32 = 200001
const LOGIC_QUERY_WAIT uint32 = 200002 const LOGIC_QUERY_WAIT uint32 = 200002

View File

@@ -11,6 +11,7 @@ func init() {
message[TOKEN_GENERATE_ERROR] = "生成token失败" message[TOKEN_GENERATE_ERROR] = "生成token失败"
message[DB_ERROR] = "系统维护升级中,请稍后再试" message[DB_ERROR] = "系统维护升级中,请稍后再试"
message[DB_UPDATE_AFFECTED_ZERO_ERROR] = "更新数据影响行数为0" message[DB_UPDATE_AFFECTED_ZERO_ERROR] = "更新数据影响行数为0"
message[USER_DISABLED] = "您的账号已被封禁,如有疑问请联系客服"
} }
func MapErrMsg(errcode uint32) string { func MapErrMsg(errcode uint32) string {

View File

@@ -5,7 +5,7 @@ $DB_URL = "tyc:5vg67b3UNHu8@tcp(127.0.0.1:22001)/tyc"
$OUTPUT_DIR = "./model" $OUTPUT_DIR = "./model"
$TEMPLATE_DIR = "../template" $TEMPLATE_DIR = "../template"
# 表名列表 # 表名列表(按需开启)
$tables = @( $tables = @(
# "agent" # "agent"
# "agent_active_stat", # "agent_active_stat",
@@ -16,7 +16,7 @@ $tables = @(
# "agent_commission_deduction" # "agent_commission_deduction"
# "agent_link", # "agent_link",
# "agent_membership_config", # "agent_membership_config",
# "agent_membership_recharge_order" "agent_membership_recharge_order"
# "agent_membership_user_config", # "agent_membership_user_config",
# "agent_order", # "agent_order",
# "agent_platform_deduction" # "agent_platform_deduction"
@@ -29,8 +29,8 @@ $tables = @(
# "agent_withdrawal_tax_exemption" # "agent_withdrawal_tax_exemption"
# "feature" # "feature"
# "global_notifications" # "global_notifications"
# "order" "order"
# "order_refund" "order_refund"
# "product", # "product",
# "product_feature" # "product_feature"
# "query", # "query",
@@ -54,8 +54,7 @@ $tables = @(
# "admin_promotion_link_stats_total" # "admin_promotion_link_stats_total"
# "admin_promotion_link_stats_history" # "admin_promotion_link_stats_history"
# "admin_promotion_order" # "admin_promotion_order"
"query_user_record" # "query_user_record"
) )
# 为每个表生成模型 # 为每个表生成模型

90
deploy/script/m.sql Normal file
View File

@@ -0,0 +1,90 @@
-- =========================
-- 1. 表结构变更:新增 payment_merchant
-- =========================
-- 1.1 order 表:增加支付商户标识
ALTER TABLE `order`
ADD COLUMN `payment_merchant` varchar(64) NOT NULL DEFAULT '' COMMENT '支付商户标识,例如 one/two' AFTER `payment_scene`;
-- 1.2 order_refund 表:增加支付商户标识
ALTER TABLE `order_refund`
ADD COLUMN `payment_merchant` varchar(64) NOT NULL DEFAULT '' COMMENT '退款对应的支付商户标识,例如 one/two' AFTER `product_id`;
-- 1.3 agent_membership_recharge_order 表:增加支付商户标识
ALTER TABLE `agent_membership_recharge_order`
ADD COLUMN `payment_merchant` varchar(64) NOT NULL DEFAULT '' COMMENT '支付商户标识,例如 one/two' AFTER `payment_method`;
-- =========================
-- 2. 历史数据初始化one / two
-- 约定:
-- - one当前主支付宝商户
-- - two当前 bak 支付宝商户
-- 时间区间:
-- [2026-01-25 16:38:17, 2026-02-02 18:26:00)
-- =========================
-- 2.1 order 表:按时间区间映射 one / two
-- 仅处理支付宝订单payment_platform='alipay'
-- 区间内用 two其余用 one
-- 时间优先用 pay_timepay_time 为空则用 create_time
-- 2.1.1 全部支付宝订单默认标记为 one
UPDATE `order`
SET
payment_merchant = 'one'
WHERE
payment_platform = 'alipay'
AND del_state = 0;
-- 2.1.2 区间内的支付宝订单标记为 two
UPDATE `order`
SET
payment_merchant = 'two'
WHERE
payment_platform = 'alipay'
AND del_state = 0
AND (
(
pay_time IS NOT NULL
AND pay_time >= '2026-01-25 16:38:17'
AND pay_time < '2026-02-02 18:26:00'
)
OR (
pay_time IS NULL
AND create_time >= '2026-01-25 16:38:17'
AND create_time < '2026-02-02 18:26:00'
)
);
-- 2.2 agent_membership_recharge_order 表:按创建时间映射 one / two
-- 仅处理支付宝支付payment_method='alipay'
-- 区间内创建的订单标记为 two其余为 one
-- 2.2.1 所有支付宝代理会员充值订单默认标记为 one
UPDATE `agent_membership_recharge_order`
SET
payment_merchant = 'one'
WHERE
payment_method = 'alipay'
AND del_state = 0;
-- 2.2.2 区间内的支付宝代理会员订单标记为 two
UPDATE `agent_membership_recharge_order`
SET
payment_merchant = 'two'
WHERE
payment_method = 'alipay'
AND del_state = 0
AND create_time >= '2026-01-25 16:38:17'
AND create_time < '2026-02-02 18:26:00';
-- 2.3 order_refund 表:跟随对应订单的 payment_merchant
-- 直接复制 order.payment_merchant避免逻辑重复
UPDATE `order_refund` r
JOIN `order` o ON r.order_id = o.id
SET
r.payment_merchant = o.payment_merchant
WHERE
r.del_state = 0
AND o.del_state = 0;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
-- 司法涉诉、婚姻状况等二级分类小项产品(与前端 inquireCategories 配置对应)
-- 执行前请确认 id 不与现有 product 表冲突;若已有 31+ 可改为更大起始 id 或去掉 id 让其自增
INSERT INTO `product` (`id`, `create_time`, `update_time`, `delete_time`, `del_state`, `version`, `product_name`, `product_en`, `description`, `notes`, `cost_price`, `sell_price`) VALUES
(31, NOW(), NOW(), NULL, 0, 0, '企业司法涉诉', 'toc_EnterpriseLawsuit', '<p>企业司法涉诉服务,可查询企业涉诉与执行信息,帮助您评估企业法律风险。</p>', NULL, 0.50, 28.80),
(32, NOW(), NOW(), NULL, 0, 0, '被执行人', 'toc_ExecutedPerson', '<p>被执行人信息查询,帮助您了解个人或企业是否被列为被执行人及其执行案件情况。</p>', NULL, 0.36, 18.80),
(33, NOW(), NOW(), NULL, 0, 0, '限制高消费', 'toc_LimitHigh', '<p>限制高消费信息查询,可核查个人或企业是否被采取限制高消费措施。</p>', NULL, 0.36, 18.80),
(34, NOW(), NOW(), NULL, 0, 0, '个人婚姻状态', 'toc_PersonalMarriageStatus', '<p>个人婚姻登记状态查询,帮助您了解个人的婚姻状态(未登记/结婚/离婚)。</p>', NULL, 1.00, 39.90),
(35, NOW(), NOW(), NULL, 0, 0, '婚姻状态查询(登记时间版)', 'toc_MarriageStatusRegisterTime', '<p>按登记时间查询婚姻状态,提供婚姻登记时间维度的核查服务。</p>', NULL, 1.00, 39.90),
(36, NOW(), NOW(), NULL, 0, 0, '婚姻状态查询(补证版)', 'toc_MarriageStatusSupplement', '<p>补证版婚姻状态查询,适用于补领婚姻登记证明等场景的婚姻状态核查。</p>', NULL, 1.00, 39.90),
(37, NOW(), NOW(), NULL, 0, 0, '婚姻状态核验', 'toc_MarriageStatusVerify', '<p>婚姻状态核验服务,对指定人员的婚姻状态进行快速核验。</p>', NULL, 1.00, 39.90),
(38, NOW(), NOW(), NULL, 0, 0, '双人婚姻状态(登记时间版)', 'toc_DualMarriageStatusRegisterTime', '<p>双人婚姻状态按登记时间查询,可同时核查双方婚姻登记状态及登记时间信息。</p>', NULL, 1.50, 59.90);
COMMIT;

View File

@@ -0,0 +1,285 @@
-- =============================================================================
-- 修复订单 Q_176967208700018143d9f6支付宝已退款成功但库内未完成
-- =============================================================================
--
-- 【原因简述】
-- 后台发起支付宝退款后,支付宝侧已退款成功,但创建退款记录时因 unique_refund_no
-- 冲突Duplicate entry 'refund-Q_176967208700018143d9f6')导致 createRefundRecordAndUpdateOrder
-- 失败,后续流程未执行:订单未置为 refunded、未写退款记录、未扣代理佣金与钱包。
--
-- 【本单实际退款金额】支付宝已退 99.5 元(非整单金额)
--
-- 【与 adminrefundorderlogic 退款链路对照】
-- 代码路径AdminRefundOrder -> handleAlipayRefund -> createRefundRecordAndUpdateOrder + HandleCommissionAndWalletDeduction
--
-- handleAlipayRefund 成功分支:
-- 1) createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, ...)
-- -> 事务内Insert order_refundrefund_no, platform_refund_id=TradeNo, order_id, user_id, product_id, refund_amount, status=success, refund_time=NOW()
-- -> 事务内Update order仅 code 中赋了 status=refunded未显式设 refund_time/version
-- 2) HandleCommissionAndWalletDeduction(ctx, svcCtx, nil, order, req.RefundAmount)
-- -> 该订单下 agent_commission按 refundAmount 比例冲减refunded_amount 增加,满额则 status=2UpdateWithVersion
-- -> 按代理汇总扣减额agent_wallet 先扣冻结再扣可用UpdateWithVersion
-- -> agent_wallet_transaction 插入 type=refund、金额为负的流水
--
-- 本 SQL 对应关系:
-- Step 2 = createRefundRecordAndUpdateOrder 的 Insert order_refundplatform_refund_id 修复时无支付宝 TradeNo 填 NULL可从支付宝补
-- Step 3 = createRefundRecordAndUpdateOrder 的 Update order并显式补全 refund_time、version+1
-- Step 4 = HandleCommissionAndWalletDeduction 对 agent_commission 的冲减(按 99.5 比例)
-- Step 5 = HandleCommissionAndWalletDeduction 对 agent_wallet 扣减 + agent_wallet_transaction 插入
--
-- 【执行前请确认】
-- 1该订单在支付宝侧已退款成功2已备份相关表或先在测试环境执行。
-- Step 25 已包在事务中:任一步报错请执行 ROLLBACK若 step 1 未查到订单_order_not_found=1勿执行后续。
-- =============================================================================
-- 与表字段 collation 一致,避免 #1267 Illegal mix of collations若表为 utf8mb4_general_ci 则改为 COLLATE utf8mb4_general_ci
SET
@order_no = CONVERT(
'Q_176967208700018143d9f6' USING utf8mb4
) COLLATE utf8mb4_general_ci;
-- 本单实际退款金额(元)
SET @repair_refund_amount = 99.5;
-- 1) 取订单信息
SELECT
id,
user_id,
product_id,
amount,
version INTO @order_id,
@user_id,
@product_id,
@order_amount,
@order_version
FROM `order`
WHERE
order_no = @order_no
AND del_state = 0
LIMIT 1;
SET @refund_amount = @repair_refund_amount;
-- 若无记录则说明订单号错误,终止
SELECT IF(@order_id IS NULL, 1, 0) AS _order_not_found;
-- 若 _order_not_found=1 请勿继续执行后续语句
-- ---------- 以下为事务:任一步报错请执行 ROLLBACK ----------
START TRANSACTION;
-- 2) 补写退款记录(若该订单尚无成功状态的退款记录)
-- 代码中 platform_refund_id 来自支付宝 refundResp.TradeNo修复时无则填 NULL如有支付宝退款单号可事后 UPDATE 补上
INSERT INTO
order_refund (
refund_no,
order_id,
user_id,
product_id,
platform_refund_id,
refund_amount,
refund_reason,
status,
del_state,
version,
refund_time,
close_time,
delete_time
)
SELECT
CONCAT(
'refund-',
@order_no,
'-repair'
),
@order_id,
@user_id,
@product_id,
NULL,
@refund_amount,
NULL,
'success',
0,
0,
NOW(),
NULL,
NULL
FROM (
SELECT 1
) _one
WHERE
NOT EXISTS (
SELECT 1
FROM order_refund
WHERE
order_id = @order_id
AND status = 'success'
AND del_state = 0
);
-- 3) 订单状态改为已退款
UPDATE `order`
SET
status = 'refunded',
refund_time = NOW(),
version = version + 1,
update_time = NOW()
WHERE
id = @order_id
AND del_state = 0
AND status = 'paid';
-- 4) 代理佣金:按本次退款 99.5 元在该订单的佣金上比例冲减refunded_amount 增加,满额则 status=2
SET
@total_available = (
SELECT COALESCE(
SUM(amount - refunded_amount), 0
)
FROM agent_commission
WHERE
order_id = @order_id
AND del_state = 0
);
UPDATE agent_commission
SET
refunded_amount = LEAST(
amount,
refunded_amount + @repair_refund_amount * (amount - refunded_amount) / NULLIF(@total_available, 0)
),
status = CASE
WHEN LEAST(
amount,
refunded_amount + @repair_refund_amount * (amount - refunded_amount) / NULLIF(@total_available, 0)
) >= amount THEN 2
ELSE status
END,
version = version + 1,
update_time = NOW()
WHERE
order_id = @order_id
AND del_state = 0
AND @total_available > 0;
-- 5) 代理钱包扣减 + 流水(仅对尚未存在本单退款流水的代理扣减,避免重复执行导致重复扣款)
DROP TEMPORARY TABLE IF EXISTS _repair_wallet_snapshot;
CREATE TEMPORARY TABLE _repair_wallet_snapshot (
agent_id BIGINT NOT NULL,
balance_before DECIMAL(20, 4) NOT NULL,
frozen_before DECIMAL(20, 4) NOT NULL,
total_deduct DECIMAL(20, 4) NOT NULL,
PRIMARY KEY (agent_id)
);
-- 按 99.5 元在该订单各代理间按“可退佣金”比例分配扣减额(与 step 4 一致),仅扣减额>0 的代理
INSERT INTO
_repair_wallet_snapshot (
agent_id,
balance_before,
frozen_before,
total_deduct
)
SELECT
agent_id,
balance_before,
frozen_before,
total_deduct
FROM (
SELECT
c.agent_id, w.balance AS balance_before, w.frozen_balance AS frozen_before, @repair_refund_amount * COALESCE(
SUM(c.amount - c.refunded_amount), 0
) / NULLIF(@total_available, 0) AS total_deduct
FROM
agent_commission c
JOIN agent_wallet w ON w.agent_id = c.agent_id
AND w.del_state = 0
WHERE
c.order_id = @order_id
AND c.del_state = 0
AND @total_available > 0
AND NOT EXISTS (
SELECT 1
FROM agent_wallet_transaction t
WHERE
t.agent_id = c.agent_id
AND t.transaction_id = @order_no
AND t.transaction_type = 'refund'
AND t.del_state = 0
)
GROUP BY
c.agent_id, w.balance, w.frozen_balance
) _agent_deduct
WHERE
total_deduct > 0;
-- 从钱包扣减:优先扣冻结余额,不足再扣可用余额
UPDATE agent_wallet w
INNER JOIN _repair_wallet_snapshot r ON w.agent_id = r.agent_id
SET
w.frozen_balance = w.frozen_balance - LEAST(
r.total_deduct,
w.frozen_balance
),
w.balance = w.balance - (
r.total_deduct - LEAST(
r.total_deduct,
w.frozen_balance
)
),
w.version = w.version + 1,
w.update_time = NOW()
WHERE
w.del_state = 0;
-- 插入退款流水(金额为负数)
INSERT INTO
agent_wallet_transaction (
delete_time,
del_state,
version,
agent_id,
transaction_type,
amount,
balance_before,
balance_after,
frozen_balance_before,
frozen_balance_after,
transaction_id,
related_user_id,
remark
)
SELECT
NULL,
0,
0,
r.agent_id,
'refund',
- r.total_deduct,
r.balance_before,
r.balance_before - (
r.total_deduct - LEAST(
r.total_deduct,
r.frozen_before
)
),
r.frozen_before,
r.frozen_before - LEAST(
r.total_deduct,
r.frozen_before
),
@order_no,
NULL,
'订单退款修复(支付宝已退,库内补单)'
FROM _repair_wallet_snapshot r;
DROP TEMPORARY TABLE IF EXISTS _repair_wallet_snapshot;
COMMIT;
-- ---------- 事务结束 ----------
-- 6) 校验(可选)
-- 订单应为 refunded
-- SELECT id, order_no, status, refund_time FROM `order` WHERE id = @order_id;
-- 应有 success 退款记录refund_amount = 99.5
-- SELECT * FROM order_refund WHERE order_id = @order_id AND del_state = 0;
-- 佣金 refunded_amount 按 99.5 比例增加,满额则 status=2
-- SELECT id, agent_id, order_id, amount, refunded_amount, status FROM agent_commission WHERE order_id = @order_id;

View File

@@ -0,0 +1,2 @@
-- 为用户表添加 disable 字段0 可用1 禁用,默认 0
ALTER TABLE `user` ADD COLUMN `disable` tinyint NOT NULL DEFAULT 0 COMMENT '0可用 1禁用' AFTER `inside`;

View File

@@ -0,0 +1,228 @@
# 天远查车询功能实现说明
## 功能概述
实现了一个完整的工具查询系统包含11个实用工具用户可以通过首页点击工具进行查询。
## 后端实现
### 1. 配置和类型定义
#### 配置文件更新
- `config/config.go`: 添加了 `TianxingjuheConfig` 结构体和 `Tianxingjuhe` 字段
- `etc/main.yaml``etc/main.dev.yaml`: 添加了天行聚合API配置
#### 类型定义
- `types/types.go`: 添加了工具相关类型
- `ToolInfo`: 工具信息结构
- `ToolboxListResp`: 工具列表响应
- `ToolboxQueryReq`: 工具查询请求
- `ToolboxQueryResp`: 工具查询响应
### 2. 天行聚合API客户端
#### 文件结构
- `service/tianxingjuhe_sdk/client.go`: 天行聚合API客户端实现
#### 功能特性
- 支持GET和POST请求
- 自动添加API密钥
- 统一的响应处理
- 错误处理机制
### 3. 工具箱服务
#### 文件结构
- `service/toolboxService.go`: 工具箱服务核心实现
#### 工具列表11个工具
1. **IP地址查询** (ip-location)
- 查询IP地址归属地、运营商等信息
- 支持ipv4和ipv6
- 内网IP自动识别
2. **身份证归属地** (idcard-info)
- 查询身份证归属地、性别、出生日期等信息
- 校验身份证有效性
- 计算年龄
3. **手机号归属地** (phone-location)
- 查询手机号码归属地、运营商等信息
4. **北京时间查询** (beijing-time)
- 获取当前北京时间
- 不需要输入参数
5. **银行卡识别** (bank-card)
- 识别银行卡发卡行与卡种
6. **车牌号解析** (plate-parse)
- 解析车牌类型与归属地
7. **金额大写转换** (money-to-chinese)
- 阿拉伯数字转中文大写金额
8. **密码强度检测** (password-strength)
- 检测密码安全性并给出建议
9. **日期间隔计算** (days-between-dates)
- 计算两个日期相差的天数
10. **文件大小格式化** (file-size-format)
- 字节数转为 KB / MB / GB
11. **文本字数统计** (text-stats)
- 统计字符数、单词数、行数
### 4. API接口
#### 路由配置
- `handler/routes.go`: 添加了工具箱相关路由
- `GET /api/v1/toolbox/list`: 获取工具列表
- `POST /api/v1/toolbox/query`: 执行工具查询
#### Handler实现
- `handler/toolbox/toolboxlisthandler.go`: 工具列表接口
- `handler/toolbox/toolboxqueryhandler.go`: 工具查询接口
#### Logic实现
- `logic/toolbox/toolboxlistlogic.go`: 工具列表业务逻辑
- `logic/toolbox/toolboxquerylogic.go`: 工具查询业务逻辑
### 5. 服务上下文注册
#### 文件更新
- `svc/servicecontext.go`:
- 导入天行聚合SDK包
- 添加 `TianxingjuheService` 字段
- 初始化天行聚合客户端实例
- 注册到服务上下文
## 前端实现
### 1. 工具配置
#### 文件结构
- `config/toolboxRegistry.js`: 工具配置注册表
#### 配置内容
每个工具配置包含:
- `key`: 工具唯一标识
- `name`: 工具名称
- `desc`: 工具描述
- `icon`: 图标类名使用carbon图标库
- `fields`: 输入字段配置
- `validate`: 表单验证函数
- `validateMsg`: 验证失败提示信息
- `resultLabels`: 结果展示字段映射
### 2. 首页集成
#### 文件更新
- `pages/index.vue`:
- 添加了"天远查车询"卡片
- 显示所有11个工具
- 点击工具跳转到查询页面
- 添加了自定义图标样式
### 3. 工具查询页面
#### 文件结构
- `pages/toolbox/query.vue`: 工具查询页面
#### 功能特性
- 动态表单生成支持text、digit、date、textarea等类型
- 表单验证
- 自动查询(无需输入参数的工具)
- 结果展示
- 错误处理
- 加载状态
#### 支持的字段类型
- `text`: 普通文本输入
- `digit`: 数字输入
- `idcard`: 身份证号输入
- `date`: 日期选择器
- `textarea`: 多行文本输入
### 4. API调用
#### 文件结构
- `api/toolbox.js`: 工具箱API封装
#### 接口函数
- `postToolboxQuery(toolKey, params)`: 执行工具查询
## 使用流程
### 用户使用流程
1. 打开首页
2. 看到"天远查车询"卡片
3. 点击任意工具图标
4. 跳转到工具查询页面
5. 填写表单(如需)
6. 点击"立即查询"按钮
7. 查看查询结果
### 数据流程
1. 前端调用 `/api/v1/toolbox/list` 获取工具列表
2. 用户选择工具后调用 `/api/v1/toolbox/query` 执行查询
3. 后端根据 toolKey 分发到对应的处理函数
4. 处理函数执行查询逻辑可能调用第三方API
5. 返回查询结果
6. 前端展示结果
## 技术特点
### 后端特点
- 统一的工具接口设计
- 工具处理器注册表模式
- 灵活的参数传递map[string]interface{}
- 完善的错误处理
- 支持第三方API集成天行聚合
### 前端特点
- 动态表单生成
- 类型安全的TypeScript
- 响应式UI设计
- 统一的错误处理
- 良好的用户体验
## 扩展指南
### 添加新工具
#### 后端步骤
1.`toolboxService.go` 中添加处理函数
2.`toolProcessors` 映射中注册新工具
3.`ListTools()` 方法中添加工具信息
#### 前端步骤
1.`toolboxRegistry.js` 中添加工具配置
2. 定义字段配置和验证规则
3. 定义结果展示字段映射
## 注意事项
1. **API密钥管理**: 天行聚合API的密钥已配置请在生产环境中使用真实的API密钥
2. **错误处理**: 所有工具都有完善的错误处理机制
3. **表单验证**: 前端和后端都有验证,确保数据安全
4. **性能优化**: 工具查询响应时间已优化建议在3秒内完成
5. **用户体验**: 无需输入参数的工具会自动查询,提升用户体验
## 测试建议
1. 测试每个工具的输入验证
2. 测试错误处理(如格式错误的输入)
3. 测试网络异常情况
4. 测试边界情况(如最大长度、特殊字符等)
5. 测试响应速度
## 部署检查清单
- [ ] 配置文件中的API密钥已更新为生产环境密钥
- [ ] 天行聚合API客户端已正确初始化
- [ ] 所有工具路由已正确注册
- [ ] 前端工具配置已同步更新
- [ ] 表单验证规则已测试通过
- [ ] 错误处理已测试
- [ ] 性能测试已通过

9
go.mod
View File

@@ -6,9 +6,9 @@ toolchain go1.23.4
require ( require (
github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/squirrel v1.5.4
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13
github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6 github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6
github.com/alibabacloud-go/tea v1.2.2 github.com/alibabacloud-go/tea v1.3.13
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 github.com/alibabacloud-go/tea-utils/v2 v2.0.7
github.com/bytedance/sonic v1.13.0 github.com/bytedance/sonic v1.13.0
github.com/cenkalti/backoff/v4 v4.3.0 github.com/cenkalti/backoff/v4 v4.3.0
@@ -37,16 +37,17 @@ require (
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/captcha-20230305 v1.1.3 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
github.com/alibabacloud-go/tea-utils v1.3.1 // indirect github.com/alibabacloud-go/tea-utils v1.3.1 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.3.10 // indirect github.com/aliyun/credentials-go v1.4.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic/loader v0.2.2 // indirect github.com/bytedance/sonic/loader v0.2.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.5.5 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect

33
go.sum
View File

@@ -13,6 +13,8 @@ github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do2
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
github.com/alibabacloud-go/captcha-20230305 v1.1.3 h1:0Aobw12m3x28aeDMPjwjXsfF8MuLvRjlQ4Hhoy5hFOY=
github.com/alibabacloud-go/captcha-20230305 v1.1.3/go.mod h1:ydzBIN2OiM7eeQPpAFyBrv1H5TY1MtUP2rQig44C4UQ=
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
@@ -22,6 +24,8 @@ github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+M
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
@@ -46,6 +50,8 @@ github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU= github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94=
github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I= github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
@@ -64,6 +70,8 @@ github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTs
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA= github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA=
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk=
github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
@@ -83,6 +91,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
@@ -326,10 +336,13 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -342,6 +355,9 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20180826012351-8a410e7b638d/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -356,10 +372,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -370,6 +389,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/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-20220722155255-886fb9371eb4/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -384,20 +406,27 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -406,6 +435,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
@@ -420,6 +451,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

82
pkg/captcha/aliyun.go Normal file
View File

@@ -0,0 +1,82 @@
package captcha
import (
"os"
"strings"
"tyc-server/common/xerr"
captcha20230305 "github.com/alibabacloud-go/captcha-20230305/client"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/tea"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
// Config 阿里云验证码配置(与 api internal config 解耦,供 pkg 使用)
type Config struct {
AccessKeyID string
AccessKeySecret string
EndpointURL string
SceneID string
}
// isWeChatUserAgent 判断 User-Agent 是否为微信内置浏览器(含 MicroMessenger
func isWeChatUserAgent(ua string) bool {
return strings.Contains(ua, "MicroMessenger")
}
// VerifyWithUserAgent 根据 User-Agent 与 captchaVerifyParam 校验。微信请求直接通过。
func VerifyWithUserAgent(cfg Config, captchaVerifyParam string, userAgent string) error {
if isWeChatUserAgent(userAgent) {
return nil
}
return Verify(cfg, captchaVerifyParam)
}
// Verify 校验前端传入的 captchaVerifyParam。异常时视为通过以保证业务可用。
func Verify(cfg Config, captchaVerifyParam string) error {
if os.Getenv("ENV") == "development" {
return nil
}
if captchaVerifyParam == "" {
return errors.Wrapf(xerr.NewErrMsg("图形验证码校验失败"), "empty captchaVerifyParam")
}
clientCfg := &openapi.Config{
AccessKeyId: tea.String(cfg.AccessKeyID),
AccessKeySecret: tea.String(cfg.AccessKeySecret),
}
clientCfg.Endpoint = tea.String(cfg.EndpointURL)
clientCfg.ConnectTimeout = tea.Int(5000)
clientCfg.ReadTimeout = tea.Int(5000)
client, err := captcha20230305.NewClient(clientCfg)
if err != nil {
logx.Errorf("init aliyun captcha client error: %+v", err)
return nil
}
req := &captcha20230305.VerifyIntelligentCaptchaRequest{
SceneId: tea.String(cfg.SceneID),
CaptchaVerifyParam: tea.String(captchaVerifyParam),
}
resp, err := client.VerifyIntelligentCaptcha(req)
if err != nil {
logx.Errorf("verify aliyun captcha error: %+v", err)
return nil
}
if resp.Body == nil || resp.Body.Result == nil {
logx.Errorf("verify aliyun captcha empty result, resp: %+v", resp)
return nil
}
if tea.BoolValue(resp.Body.Result.VerifyResult) {
return nil
}
verifyCode := tea.StringValue(resp.Body.Result.VerifyCode)
logx.Errorf("verify aliyun captcha failed, code: %s", verifyCode)
return errors.Wrapf(xerr.NewErrMsg("图形验证码校验失败"), "aliyun captcha verify fail, code: %s", verifyCode)
}

View File

@@ -0,0 +1,32 @@
package captcha
import (
"encoding/base64"
"fmt"
"time"
lzcrypto "tyc-server/pkg/lzkit/crypto"
)
// GenerateEncryptedSceneID 按阿里云文档生成 EncryptedSceneId仅适用于 V3 架构加密模式)。
// 明文格式: sceneId&timestamp&expireTime
// 加密: AES-256-CBC + PKCS7Padding结果为 Base64( IV(16字节) + ciphertext )
func GenerateEncryptedSceneID(sceneId, ekey string, expireSeconds int) (string, error) {
if expireSeconds <= 0 || expireSeconds > 86400 {
expireSeconds = 3600
}
ts := time.Now().Unix() // 秒级时间戳
plaintext := fmt.Sprintf("%s&%d&%d", sceneId, ts, expireSeconds)
keyBytes, err := base64.StdEncoding.DecodeString(ekey)
if err != nil {
return "", fmt.Errorf("decode ekey error: %w", err)
}
if len(keyBytes) != 32 {
return "", fmt.Errorf("invalid ekey length, need 32 bytes after base64 decode, got %d", len(keyBytes))
}
// 复用已有的 AES-CBC + PKCS7 实现,输出即为 Base64(IV + ciphertext)
return lzcrypto.AesEncrypt([]byte(plaintext), keyBytes)
}

View File

@@ -9,7 +9,7 @@ import (
func TestAesEcbMobileEncryption(t *testing.T) { func TestAesEcbMobileEncryption(t *testing.T) {
// 测试手机号加密 // 测试手机号加密
mobile := "18653052547" mobile := "15029295957"
key := []byte("ff83609b2b24fc73196aac3d3dfb874f") // 16字节AES-128密钥 key := []byte("ff83609b2b24fc73196aac3d3dfb874f") // 16字节AES-128密钥
keyStr := hex.EncodeToString(key) keyStr := hex.EncodeToString(key)
@@ -19,7 +19,8 @@ func TestAesEcbMobileEncryption(t *testing.T) {
t.Fatalf("手机号加密失败: %v", err) t.Fatalf("手机号加密失败: %v", err)
} }
fmt.Printf("encrypted: %s\n", encrypted) fmt.Printf("encrypted: %s\n", encrypted)
jmStr := "m9EEeW9ZBBJmi1hx1k1uIQ=="
jmStr := "Am8/KpmBnsbXZoZOZq/oVQ=="
// 测试解密 // 测试解密
decrypted, err := DecryptMobile(jmStr, keyStr) decrypted, err := DecryptMobile(jmStr, keyStr)
if err != nil { if err != nil {