Compare commits

..

38 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
56 changed files with 10364 additions and 243 deletions

9
.gitignore vendored
View File

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

View File

@@ -46,7 +46,7 @@ service main {
type (
PaymentReq {
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"`
}
PaymentResp {

View File

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

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

@@ -143,6 +143,24 @@ type (
sendSmsReq {
Mobile string `json:"mobile" validate:"required,mobile"`
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,9 +14,15 @@ VerifyCode:
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
EndpointURL: "dysmsapi.aliyuncs.com"
SignName: "天远查"
SignName: "海南海宇大数据"
TemplateCode: "SMS_302641455"
ValidTime: 300
Captcha:
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
EndpointURL: "captcha.cn-shanghai.aliyuncs.com"
SceneID: "wynt39to"
EKey: "xdhf5JbWVmFXx+2K+6kBk2aH++GtQBEI8Gmzdeen90o="
Encrypt:
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
Alipay:
@@ -26,7 +32,6 @@ Alipay:
AppCertPath: "etc/merchant/appCertPublicKey_2021006121698606.crt"
AlipayCertPath: "etc/merchant/alipayCertPublicKey_RSA2.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: ""
@@ -37,16 +42,17 @@ Alipay:
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/alipay/callback"
ReturnURL: "https://www.tianyuancha.cn/payment/result"
Wxpay:
AppID: "wxa581992dc74d860e"
MchID: "1682635136"
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61"
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f"
AppID: "wxd391e40295bd9dfb"
MchID: "1105276690"
MchCertificateSerialNumber: "4F1738D21CAEB7F76193A770CDBA6D7002ED1CFD"
MchApiv3Key: "K2d8F5gJ1sP7zQ3bN9mR4xV6c0hL5tU2"
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
MchPublicKeyID: "PUB_KEY_ID_0116826351362025060900382267001601"
MchPublicKeyID: "PUB_KEY_ID_0111052766902026050900112134001605"
MchPublicKeyPath: "etc/merchant/pub_key.pem"
MchPlatformRAS: "1FFEC3F62E31885FAB4C91ADCB8D7557E9488781"
MchPlatformRAS: "PUB_KEY_ID_0111052766902026050900112134001605"
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://www.tianyuancha.cn/api/v1/wechat/refund_callback"
Applepay:
ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt"
SandboxVerifyURL: "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/receipt"
@@ -61,13 +67,14 @@ SystemConfig:
ThreeVerify: false
CommissionSafeMode: false # 佣金安全防御模式true-冻结模式false-直接结算模式
WechatH5:
AppID: "wxe323cdc072127c27"
AppSecret: "e68035927b054a20b8bfdb7f0caffebf"
AppID: "wxd391e40295bd9dfb"
AppSecret: "f0fa74f7ed8c3c9953677465d44a4c0c"
WechatMini:
AppID: "wx781abb66b3368963" # 小程序的AppID
AppSecret: "c7d02cdb0fc23c35c93187af9243b00d" # 小程序的AppSecret
TycAppID: "wxe74617f3dd56c196"
TycAppSecret: "c8207e54aef5689b2a7c1f91ed7ae8a0"
# AppID: "wx5bacc94add2da981" # 小程序的AppID
# AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret
# TYC
AppID: "wxe74617f3dd56c196"
AppSecret: "c8207e54aef5689b2a7c1f91ed7ae8a0"
Query:
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
AdminConfig:
@@ -91,3 +98,9 @@ Upload:
TempFileMaxAgeH: 24 # 临时文件保留时长小时超时自动删除0 表示默认 24
PublicBaseURL: "https://www.tianyuancha.cn"
ExtensionTime: 24 # 佣金解冻延迟时间单位24小时
tianxingjuhe:
url: "https://apis.tianapi.com"
key: "4ceffb1ffb95b83230b9a9c9df2467e1"
timeout: 30

View File

@@ -16,9 +16,19 @@ VerifyCode:
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
EndpointURL: "dysmsapi.aliyuncs.com"
SignName: "天远查"
SignName: "海南海宇大数据"
TemplateCode: "SMS_302641455"
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:
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
WestConfig:
@@ -48,14 +58,14 @@ Alipay:
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/alipay/callback"
ReturnURL: "https://www.tianyuancha.cn/payment/result"
Wxpay:
AppID: "wxa581992dc74d860e"
MchID: "1682635136"
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61"
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f"
AppID: "wxd391e40295bd9dfb"
MchID: "1105276690"
MchCertificateSerialNumber: "4F1738D21CAEB7F76193A770CDBA6D7002ED1CFD"
MchApiv3Key: "K2d8F5gJ1sP7zQ3bN9mR4xV6c0hL5tU2"
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
MchPublicKeyID: "PUB_KEY_ID_0116826351362025060900382267001601"
MchPublicKeyID: "PUB_KEY_ID_0111052766902026050900112134001605"
MchPublicKeyPath: "etc/merchant/pub_key.pem"
MchPlatformRAS: "1FFEC3F62E31885FAB4C91ADCB8D7557E9488781"
MchPlatformRAS: "PUB_KEY_ID_0111052766902026050900112134001605"
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://www.tianyuancha.cn/api/v1/wechat/refund_callback"
Applepay:
@@ -72,11 +82,14 @@ SystemConfig:
ThreeVerify: true
CommissionSafeMode: false # 佣金安全防御模式true-冻结模式false-直接结算模式
WechatH5:
AppID: "wxa581992dc74d860e"
AppSecret: "4de1fbf521712247542d49907fcd5dbf"
AppID: "wxd391e40295bd9dfb"
AppSecret: "f0fa74f7ed8c3c9953677465d44a4c0c"
WechatMini:
AppID: "wx781abb66b3368963" # 小程序的AppID
AppSecret: "c7d02cdb0fc23c35c93187af9243b00d" # 小程序的AppSecret
# AppID: "wx5bacc94add2da981" # 小程序的AppID
# AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret
# TYC
AppID: "wxe74617f3dd56c196"
AppSecret: "c8207e54aef5689b2a7c1f91ed7ae8a0"
Query:
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
AdminConfig:
@@ -93,9 +106,15 @@ Tianyuanapi:
Key: "74902aff197d72d1caa5593560cb281e"
BaseURL: "https://api.tianyuanapi.com"
Timeout: 60
tianxingjuhe:
url: "https://apis.tianapi.com"
key: "4ceffb1ffb95b83230b9a9c9df2467e1"
timeout: 30
Authorization:
FileBaseURL: "https://www.tianyuancha.cn/api/v1/auth-docs" # 授权书文件访问基础URL
Upload:
FileBaseURL: "https://www.tianyuancha.cn/api/v1/upload/file" # 上传图片访问基础 URL行驶证等
FileBaseURL: "https://www.tianyuanfcha.cn/api/v1/upload/file" # 上传图片访问基础 URL行驶证等
TempFileMaxAgeH: 24
PublicBaseURL: "https://www.tianyuancha.cn"
ExtensionTime: 24 # 佣金解冻延迟时间单位24小时

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

View File

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

View File

@@ -11,12 +11,14 @@ type Config struct {
CacheRedis cache.CacheConf
JwtAuth JwtAuth // JWT 鉴权相关配置
VerifyCode VerifyCode
Captcha CaptchaConfig
Encrypt Encrypt
Alipay AlipayConfig
Wxpay WxpayConfig
Applepay ApplepayConfig
Ali AliConfig
Tianyuanapi TianyuanapiConfig
Tianxingjuhe TianxingjuheConfig
SystemConfig SystemConfig
WechatH5 WechatH5Config
Authorization AuthorizationConfig // 授权书配置
@@ -45,6 +47,13 @@ type VerifyCode struct {
TemplateCode string
ValidTime int
}
type CaptchaConfig struct {
AccessKeyID string
AccessKeySecret string
EndpointURL string
SceneID string
EKey string
}
type Encrypt struct {
SecretKey string
}
@@ -142,6 +151,13 @@ type AuthorizationConfig struct {
// UploadConfig 图片上传(行驶证等)配置,临时存储,按 hash 去重
type UploadConfig struct {
FileBaseURL string `json:",optional"` // 上传文件访问基础 URL如 https://xxx/api/v1/upload/file
TempFileMaxAgeH int `json:",optional"` // 临时文件保留时长小时超时删除0 表示默认 24 小时
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
import (
"context"
"net/http"
"tyc-server/app/main/api/internal/logic/auth"
@@ -23,7 +24,8 @@ func SendSmsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
result.ParamValidateErrorResult(r, w, err)
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)
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,11 +22,13 @@ import (
app "tyc-server/app/main/api/internal/handler/app"
auth "tyc-server/app/main/api/internal/handler/auth"
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"
pay "tyc-server/app/main/api/internal/handler/pay"
product "tyc-server/app/main/api/internal/handler/product"
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"
"tyc-server/app/main/api/internal/svc"
@@ -954,6 +956,18 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
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(
[]rest.Route{
{
@@ -1162,6 +1176,24 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
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{
{

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

@@ -1,11 +1,14 @@
package user
import (
"context"
"net/http"
"strings"
"tyc-server/app/main/api/internal/logic/user"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
jwtx "tyc-server/common/jwt"
"tyc-server/common/result"
"tyc-server/pkg/lzkit/validator"
@@ -23,7 +26,14 @@ func WxH5AuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
result.ParamValidateErrorResult(r, w, err)
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)
result.HttpResult(r, w, resp, err)
}

View File

@@ -124,7 +124,7 @@ func (l *AdminReviewBankCardWithdrawalLogic) approveWithdrawal(ctx context.Conte
return l.approveAlipayWithdrawal(ctx, session, record)
} else {
// 银行卡提现:直接更新状态为成功(线下转账)
return l.approveBankCardWithdrawal(ctx, session, record)
return l.approveBankCardWithdrawal(ctx, session, record, taxRate)
}
}
@@ -186,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.Remark = sql.NullString{String: "管理员确认提现", Valid: true}
@@ -233,10 +233,13 @@ func (l *AdminReviewBankCardWithdrawalLogic) approveBankCardWithdrawal(ctx conte
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
// 注意:由于在 applyReviewTaxRate 中已经更新过 tax 记录version 已经递增)
// 这里只需要更新 tax_status 和 tax_time不需要再次调用 UpdateWithVersion
// 使用普通 Update 方法避免乐观锁冲突
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功
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)
}
}
@@ -304,10 +307,12 @@ func (l *AdminReviewBankCardWithdrawalLogic) rejectWithdrawal(ctx context.Contex
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
// 注意:由于在 applyReviewTaxRate 中已经更新过 tax 记录version 已经递增)
// 这里只需要更新 tax_status 和 tax_time使用普通 Update 方法避免乐观锁冲突
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败
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)
}
}
@@ -364,10 +369,12 @@ func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalSuccess(ctx conte
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
// 注意:由于在 applyReviewTaxRate 中已经更新过 tax 记录version 已经递增)
// 这里只需要更新 tax_status 和 tax_time使用普通 Update 方法避免乐观锁冲突
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功
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)
}
}
@@ -435,10 +442,12 @@ func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalFailure(ctx conte
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err)
}
// 注意:由于在 applyReviewTaxRate 中已经更新过 tax 记录version 已经递增)
// 这里只需要更新 tax_status 和 tax_time使用普通 Update 方法避免乐观锁冲突
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败
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)
}
}

View File

@@ -82,7 +82,8 @@ func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *type
orderPayTime = &order.PayTime.Time
}
refundNo := l.generateRefundNo(order.OrderNo)
refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.OrderNo, req.RefundAmount, orderPayTime, refundNo)
// 按订单记录的商户号 payment_merchant 选择支付宝商户;老订单未写入时由 AliRefund 内部按时间区间兜底。
refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.PaymentMerchant, order.OrderNo, req.RefundAmount, orderPayTime, refundNo)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 支付宝退款失败 err: %v", err)
}
@@ -146,6 +147,7 @@ func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Or
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
PaymentMerchant: order.PaymentMerchant,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus, // 使用传入的状态,不再硬编码
@@ -174,6 +176,7 @@ func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
PaymentMerchant: order.PaymentMerchant,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus,

View File

@@ -6,6 +6,7 @@ import (
"math/rand"
"time"
"tyc-server/common/xerr"
"tyc-server/pkg/captcha"
"tyc-server/pkg/lzkit/crypto"
"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 {
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
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
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 {
notification, err := l.svcCtx.AlipayService.HandleAliPaymentNotification(r)
if err != nil {
logx.Errorf("支付宝支付回调,%v", err)
// 先解析表单,拿到 out_trade_no 用于查找订单和对应商户号
if err := r.ParseForm(); err != nil {
logx.Errorf("支付宝支付回调,解析请求表单失败: %v", err)
return nil
}
// 根据订单号前缀判断订单类型
orderNo := notification.OutTradeNo
orderNo := r.FormValue("out_trade_no")
if orderNo == "" {
logx.Errorf("支付宝支付回调,缺少 out_trade_no")
return nil
}
// 根据订单号前缀判断订单类型,并查出对应商户标识
var merchant string
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_") {
// 代理会员订单处理
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 {
// 兼容旧订单,假设没有前缀的是查询订单
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)
}
}
@@ -218,9 +254,10 @@ func (l *AlipayCallbackLogic) handleRefund(order *model.AgentMembershipRechargeO
return refundErr
}
} else {
// 代理会员订单以创建时间为准,仅 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单走 bak 商户
// 支付宝退款按订单记录的商户号 payment_merchant 走对应商户
// 老订单若未写入商户号,则在 AliRefund 内按时间区间兜底。
orderPayTime := order.CreateTime
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount, &orderPayTime, "")
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.PaymentMerchant, order.OrderNo, order.Amount, &orderPayTime, "")
if refundErr != nil {
return refundErr
}

View File

@@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
@@ -32,6 +33,22 @@ type PaymentTypeResp struct {
outTradeNo string
description string
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 {
@@ -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) {
// 暂时关闭微信支付
if req.PayMethod == "wechat" {
return nil, errors.Wrapf(xerr.NewErrMsg("微信支付暂时关闭"), "微信支付暂时关闭")
}
req.PayMethod = strings.TrimSpace(req.PayMethod)
req.PayType = strings.TrimSpace(req.PayType)
req.Id = strings.TrimSpace(req.Id)
var paymentTypeResp *PaymentTypeResp
var prepayData interface{}
@@ -79,19 +95,53 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
if req.PayMethod == "wechat" {
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
} 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" {
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 {
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
})
if err != nil {
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
isDevTestPayment := os.Getenv("ENV") == "development" && req.PayMethod == "test"
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)
}
// 代理推广链接订单仅支持支付宝,拒绝微信支付(与个人查询页选项一致)
if req.PayMethod == "wechat" && data.AgentIdentifier != "" {
return nil, errors.Wrapf(xerr.NewErrMsg("代理推广订单暂不支持微信支付"), "代理推广订单暂不支持微信支付")
}
product, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, data.Product)
if err != nil {
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
}
var orderID int64
// 默认支付宝商户号为 one若为代理推广订单存在 AgentIdentifier则使用 two。
paymentMerchant := ""
if req.PayMethod == "alipay" {
if data.AgentIdentifier != "" {
paymentMerchant = "two"
} else {
paymentMerchant = "one"
}
}
order := model.Order{
OrderNo: outTradeNo,
UserId: userID,
ProductId: product.Id,
PaymentPlatform: req.PayMethod,
PaymentScene: "app",
PaymentMerchant: paymentMerchant,
Amount: amount,
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 &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) {
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
@@ -274,12 +347,19 @@ func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.
if user.Inside == 1 {
amount = 0.01
}
paymentMerchant := ""
if req.PayMethod == "alipay" {
paymentMerchant = "one"
}
agentMembershipRechargeOrder := model.AgentMembershipRechargeOrder{
OrderNo: req.Id,
UserId: userID,
AgentId: agentModel.Id,
Amount: amount,
PaymentMethod: req.PayMethod,
PaymentMerchant: paymentMerchant,
LevelName: agentVipCache.Type,
Status: "pending",
}
@@ -287,7 +367,12 @@ func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.
if err != nil {
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) {
key, decodeErr := hex.DecodeString("8e3e7a2f60edb49221e953b9c029ed10")

View File

@@ -221,9 +221,10 @@ func (l *WechatPayCallbackLogic) handleRefund(order *model.AgentMembershipRechar
return refundErr
}
} else {
// 代理会员订单以创建时间为准,仅 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单走 bak 商户
// 支付宝退款按订单记录的商户号 payment_merchant 走对应商户
// 老订单若未写入商户号,则在 AliRefund 内按时间区间兜底。
orderPayTime := order.CreateTime
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount, &orderPayTime, "")
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.PaymentMerchant, order.OrderNo, order.Amount, &orderPayTime, "")
if refundErr != nil {
return refundErr
}

View File

@@ -2,6 +2,7 @@ package query
import (
"context"
"strings"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"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) {
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
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表的条件
build := l.svcCtx.QueryModel.SelectBuilder().Where(squirrel.Eq{
"user_id": userID,
})
// 直接从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 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告列表查询, 查找报告列表错误, %+v", err)
}
var list []types.Query
if len(queryList) > 0 {
for _, queryModel := range queryList {
var query types.Query
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)
}
// 小程序端过滤非车辆类产品H5 端 source 为空,不做过滤)
if isMiniapp && !isVehicleProduct(product.ProductEn) {
continue
}
// 检查订单状态,如果订单已退款,则设置查询状态为已退款
order, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, queryModel.OrderId)
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
list = append(list, query)
}
// H5 端返回数据库原始 total小程序端返回过滤后的实际数量
respTotal := total
if isMiniapp {
respTotal = int64(len(list))
}
return &types.QueryListResp{
Total: total,
Total: respTotal,
List: list,
}, nil
}

View File

@@ -13,6 +13,7 @@ import (
"tyc-server/common/ctxdata"
"tyc-server/common/globalkey"
"tyc-server/common/xerr"
"tyc-server/pkg/captcha"
"tyc-server/pkg/lzkit/crypto"
"tyc-server/pkg/lzkit/validator"
@@ -59,8 +60,9 @@ var productHandlers = map[string]queryHandlerFunc{
"companyinfo": runMarriageReq,
"rentalinfo": runMarriageReq,
"preloanbackgroundcheck": runMarriageReq,
"backgroundcheck": runMarriageReq,
"backgroundcheck": runBackgroundCheckReq,
"personalData": runMarriageReq,
"toc_PersonalBadRecord": runPersonalBadRecordReq,
"toc_PersonalLawsuit": runMarriageReq,
"toc_EnterpriseLawsuit": runEntLawsuitReq,
// 人企关系加强版:仅身份证号
@@ -109,7 +111,58 @@ var productHandlers = map[string]queryHandlerFunc{
"toc_BankcardBlacklist": runVerifyBankBlackReq,
}
// productHasSmsCode 表示该 product 解密后的请求结构体中是否包含必填短信验证码 Code。
// 有 Code 的产品在「获取验证码」时已经做了滑块,这里不再强制要求 CaptchaVerifyParam。
// 其他产品(无 Code在查询时必须传并校验 CaptchaVerifyParam防止跳过图形验证
// 微信小程序X-Platform: wxmini见 ctxdata.GetPlatformFromCtx不嵌入 H5 滑块,由 PreprocessLogic 跳过图形校验。
func productHasSmsCode(product string) bool {
switch product {
case "marriage",
"homeservice",
"riskassessment",
"companyinfo",
"rentalinfo",
"preloanbackgroundcheck",
"personalData",
"toc_PersonalLawsuit",
"toc_EnterpriseLawsuit",
"toc_Marriage",
"toc_PersonalMarriageStatus",
"toc_MarriageStatusRegisterTime",
"toc_MarriageStatusSupplement",
"toc_MarriageStatusVerify",
"toc_DualMarriageStatusRegisterTime",
"toc_VehiclesUnderName",
"toc_VehiclesUnderNamePlate":
return true
default:
return false
}
}
func (l *QueryServiceLogic) PreprocessLogic(req *types.QueryServiceReq, product string) (*types.QueryServiceResp, error) {
// 无短信验证码 Code 的 product查询前必须传并校验滑块微信小程序端跳过依赖登录态与 X-Platform
requireCaptcha := !productHasSmsCode(product)
if requireCaptcha {
if plat, platErr := ctxdata.GetPlatformFromCtx(l.ctx); platErr == nil && plat == model.PlatformWxMini {
requireCaptcha = false
}
}
if requireCaptcha {
if req.CaptchaVerifyParam == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("请完成图形验证"), "product %s requires captcha", product)
}
cfg := l.svcCtx.Config.Captcha
if err := captcha.Verify(captcha.Config{
AccessKeyID: cfg.AccessKeyID,
AccessKeySecret: cfg.AccessKeySecret,
EndpointURL: cfg.EndpointURL,
SceneID: cfg.SceneID,
}, req.CaptchaVerifyParam); err != nil {
return nil, err
}
}
decryptData, err := l.DecryptData(req.Data)
if err != nil {
return nil, err
@@ -170,6 +223,24 @@ func runMarriageReq(l *QueryServiceLogic, decryptData []byte, product string) (m
}, nil
}
func runBackgroundCheckReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.BackgroundCheckReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
if verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile); verifyErr != nil {
return nil, verifyErr
}
return map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}, nil
}
// runEntLawsuitReq 企业司法涉诉:企业名称+统一社会信用代码+手机+验证码
func runEntLawsuitReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.EntLawsuitReq
@@ -230,6 +301,21 @@ func runLimitHighExecutedReq(l *QueryServiceLogic, decryptData []byte, product s
}, nil
}
// 本人不良 FLXGDEA9姓名 + 身份证(授权由 ApiRequest 默认传 1
func runPersonalBadRecordReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.PersonalBadRecordReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
}
return map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
}, nil
}
// 失信被执行人 QYGL2S0W
func runDishonestExecutedReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
var data types.DishonestExecutedReq

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

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

View File

@@ -6,8 +6,10 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"
"tyc-server/app/main/model"
"tyc-server/common/ctxdata"
"tyc-server/common/xerr"
"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)
}
// 已登录正式用户 + 静默授权 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: 查找用户授权信息
userAuth, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid)
if findErr != nil && !errors.Is(findErr, model.ErrNotFound) {
@@ -94,6 +101,53 @@ func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthRe
}, 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 {
AccessToken string `json:"access_token"`
Openid string `json:"openid"`

View File

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

View File

@@ -322,12 +322,13 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error
logx.Infof("已发起微信退款申请, orderID: %d, amount: %f", order.Id, order.Amount)
return asynq.SkipRetry
} else {
// 支付宝退款为同步结果,仅 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内订单走 bak 商户
// 支付宝退款为同步结果,优先按订单记录的 payment_merchant 选择商户
// 老订单若未写入商户号,则在 AliRefund 内按时间区间兜底。
orderPayTime := &order.CreateTime
if order.PayTime.Valid {
orderPayTime = &order.PayTime.Time
}
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount, orderPayTime, "")
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.PaymentMerchant, order.OrderNo, order.Amount, orderPayTime, "")
if refundErr != nil {
logx.Error(refundErr)
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"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"strconv"
"sync/atomic"
"time"
@@ -28,6 +28,28 @@ type AliPayService struct {
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
func NewAliPayService(c config.Config) *AliPayService {
client, err := alipay.New(c.Alipay.AppID, c.Alipay.PrivateKey, c.Alipay.IsProduction)
@@ -77,8 +99,8 @@ func NewAliPayService(c config.Config) *AliPayService {
return svc
}
func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, outTradeNo string) (string, error) {
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)
// 构造移动支付请求
p := alipay.TradeAppPay{
@@ -101,8 +123,8 @@ func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, out
}
// CreateAlipayH5Order 创建支付宝H5支付订单
func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outTradeNo string) (string, error) {
client := a.AlipayClient
func (a *AliPayService) CreateAlipayH5Order(merchant string, amount float64, subject string, outTradeNo string) (string, error) {
client := a.clientForMerchant(merchant, nil)
totalAmount := lzUtils.ToAlipayAmount(amount)
// 构造H5支付请求
p := alipay.TradeWapPay{
@@ -124,8 +146,9 @@ func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outT
return payUrl.String(), nil
}
// CreateAlipayOrder 根据平台类型创建支付宝支付订单
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, subject string, outTradeNo string) (string, error) {
// CreateAlipayOrder 根据平台类型和商户标识创建支付宝支付订单
// merchant: 商户标识,目前约定 "one"=主商户, "two"=备商户
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, merchant string, amount float64, subject string, outTradeNo string) (string, error) {
// 根据 ctx 中的 platform 判断平台
platform, platformOk := ctx.Value("platform").(string)
if !platformOk {
@@ -134,23 +157,21 @@ func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, s
switch platform {
case model.PlatformApp:
// 调用App支付的创建方法
return a.CreateAlipayAppOrder(amount, subject, outTradeNo)
return a.CreateAlipayAppOrder(merchant, amount, subject, outTradeNo)
case model.PlatformH5:
// 调用H5支付的创建方法并传入 returnUrl
return a.CreateAlipayH5Order(amount, subject, outTradeNo)
return a.CreateAlipayH5Order(merchant, amount, subject, outTradeNo)
default:
return "", fmt.Errorf("不支持的支付平台: %s", platform)
}
}
// AliRefund 发起支付宝退款。orderPayTime 为订单支付时间(或创建时间);仅当落在 [2026-01-25 16:38:17, 2026-02-02 18:26) 区间内时使用 bak 商户号,否则使用正式商户号;传 nil 则使用正式商户号。
// AliRefund 发起支付宝退款。
// merchant: 支付商户标识one/two。为空时按老逻辑仅在备份时间区间内使用 Bak。
// orderPayTime 为订单支付时间(或创建时间);用于老订单按时间区间选择商户;传 nil 则忽略时间区间。
// outRequestNo 为商户退款请求号,同一笔退款需唯一;传空则使用 "refund-"+outTradeNo重试时建议传入唯一号避免支付宝报重复。
func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refundAmount float64, orderPayTime *time.Time, outRequestNo string) (*alipay.TradeRefundRsp, error) {
client := a.AlipayClient
if orderPayTime != nil && a.AlipayClientBak != nil &&
!orderPayTime.Before(AlipayBakRefundStart) && orderPayTime.Before(AlipayBakRefundEnd) {
client = a.AlipayClientBak
}
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)
@@ -168,27 +189,26 @@ func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refund
return refundResp, nil
}
// HandleAliPaymentNotification 支付宝支付回调
func (a *AliPayService) HandleAliPaymentNotification(r *http.Request) (*alipay.Notification, error) {
// 解析表单
err := r.ParseForm()
if err != nil {
return nil, fmt.Errorf("解析请求表单失败:%v", err)
}
// HandleAliPaymentNotification 支付宝支付回调验签。
// 由上层根据 out_trade_no 查出订单并传入对应商户标识 merchant。
func (a *AliPayService) HandleAliPaymentNotification(merchant string, form url.Values) (*alipay.Notification, error) {
client := a.clientForMerchant(merchant, nil)
// 解析并验证通知DecodeNotification 会自动验证签名
notification, err := a.AlipayClient.DecodeNotification(r.Form)
notification, err := client.DecodeNotification(form)
if err != nil {
return nil, fmt.Errorf("验证签名失败: %v", err)
}
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{
OutTradeNo: outTradeNo,
}
// 发起查询请求
resp, err := a.AlipayClient.TradeQuery(ctx, queryRequest)
resp, err := client.TradeQuery(ctx, queryRequest)
if err != nil {
return nil, fmt.Errorf("查询支付宝订单失败: %v", err)
}

View File

@@ -39,15 +39,17 @@ type ApiRequestService struct {
featureModel model.FeatureModel
productFeatureModel model.ProductFeatureModel
tianyuanapi *tianyuanapi.Client
authService *AuthorizationService
}
// 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{
config: c,
featureModel: featureModel,
productFeatureModel: productFeatureModel,
tianyuanapi: tianyuanapi,
authService: authService,
}
}
@@ -214,10 +216,10 @@ var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, err
"YYSY09CD": (*ApiRequestService).ProcessYYSY09CDRequest,
"QCXGGB2Q": (*ApiRequestService).ProcessQCXGGB2QRequest,
"QCXGYTS2": (*ApiRequestService).ProcessQCXGYTS2Request,
"QCXG5F3A": (*ApiRequestService).ProcessQCXG5F3ARequest,
"QCXG5F3A": (*ApiRequestService).ProcessQCXG5F3ARequest, //内部替换名下
"FLXG0687": (*ApiRequestService).ProcessFLXG0687Request,
"FLXG3D56": (*ApiRequestService).ProcessFLXG3D56Request,
"FLXG0V4B": (*ApiRequestService).ProcesFLXG0V4BRequest,
"FLXG0V4B": (*ApiRequestService).ProcessFLXG0V4BRequest,
"QYGL8271": (*ApiRequestService).ProcessQYGL8271Request,
"IVYZ5733": (*ApiRequestService).ProcessIVYZ5733Request,
"IVYZ9A2B": (*ApiRequestService).ProcessIVYZ9A2BRequest,
@@ -269,6 +271,9 @@ var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, err
"QYGL66SL": (*ApiRequestService).ProcessQYGL66SLRequest,
"FLXG3A9B": (*ApiRequestService).ProcessFLXG3A9BRequest,
"QYGL2S0W": (*ApiRequestService).ProcessQYGL2S0WRequest,
"FLXGDEA9": (*ApiRequestService).ProcessFLXGDEA9Request,
"IVYZ4Y27": (*ApiRequestService).ProcessIVYZ4Y27Request,
"IVYZ0S0D": (*ApiRequestService).ProcessIVYZ0S0DRequest,
}
// PreprocessRequestApi 调用指定的请求处理函数
@@ -1505,6 +1510,23 @@ 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{})
@@ -1909,3 +1931,54 @@ func (a *ApiRequestService) ProcessQCXG9P1CFRequest(params []byte) ([]byte, erro
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"
"context"
"database/sql"
"encoding/base64"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"text/template"
"time"
"tyc-server/app/main/api/internal/config"
"tyc-server/app/main/model"
@@ -329,3 +331,116 @@ func getUserInfoString(userInfo map[string]interface{}, key string) string {
}
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 (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"tyc-server/app/main/api/internal/config"
"tyc-server/app/main/model"
@@ -24,6 +26,14 @@ import (
"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 (
TradeStateSuccess = "SUCCESS" // 支付成功
TradeStateRefund = "REFUND" // 转入退款
@@ -172,14 +182,54 @@ func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount floa
// 发起预支付请求
resp, result, err := svc.Prepay(ctx, payRequest)
logx.Infof("微信app支付订单resp: %+v, result: %+v, err: %+v", resp, result, err)
if err != nil {
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result))
}
// 返回预支付交易会话标识
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 创建微信小程序支付订单
func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) {
totalAmount := lzUtils.ToWechatAmount(amount)
@@ -203,17 +253,18 @@ func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amo
// 发起预支付请求
resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest)
logx.Infof("微信小程序支付订单resp: %+v, result: %+v, err: %+v", resp, result, err)
if err != nil {
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result))
}
// 返回预支付交易会话标识
return resp, nil
return jsapiRequestPaymentToMap(resp)
}
// CreateWechatH5Order 创建微信H5支付订单
func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) {
totalAmount := lzUtils.ToWechatAmount(amount)
logx.Infof("微信h5支付订单amount: %+v, description: %+v, outTradeNo: %+v, openid: %+v", amount, description, outTradeNo, openid)
// 构建支付请求参数
payRequest := jsapi.PrepayRequest{
Appid: core.String(w.config.WechatH5.AppID),
@@ -231,20 +282,31 @@ func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float
// 初始化 AppApiService
svc := jsapi.JsapiApiService{Client: w.wechatClient}
logx.Infof("微信h5支付订单payRequest: %+v", payRequest)
// 发起预支付请求
resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest)
logx.Infof("微信h5支付订单resp: %+v, result: %+v, err: %+v", resp, result, err)
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 resp, nil
return jsapiRequestPaymentToMap(resp)
}
// CreateWechatOrder 创建微信支付订单(集成 APP、H5、小程序
func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string) (interface{}, error) {
// 根据 ctx 中的 platform 判断平台
platform := ctx.Value("platform").(string)
platformVal := ctx.Value("platform")
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 err error
@@ -264,15 +326,33 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64
return "", err
}
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)
if getUidErr != nil {
return "", getUidErr
}
userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID)
if findAuthModelErr != nil {
return "", findAuthModelErr
// 微信内置浏览器 JSAPI 必须使用与商户公众号一致的 openidsnsapi_base / snsapi_userinfo 授权后写入 wxh5_openid
// 不可使用小程序 openid 兜底AppID 与 openid 主体不一致会导致下单失败或调起异常。
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 {
return "", err
}
@@ -288,6 +368,21 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64
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
return prepayData, nil
}
@@ -323,7 +418,7 @@ func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID s
Mchid: core.String(w.config.Wxpay.MchID),
})
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

View File

@@ -5,6 +5,7 @@ import (
"tyc-server/app/main/api/internal/config"
"tyc-server/app/main/api/internal/middleware"
"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"
"tyc-server/app/main/model"
@@ -101,6 +102,8 @@ type ServiceContext struct {
AdminPromotionLinkStatsService *service.AdminPromotionLinkStatsService
ImageService *service.ImageService
AuthorizationService *service.AuthorizationService
ToolboxService *service.ToolboxService
TianxingjuheService *tianxingjuhe.Client
}
// NewServiceContext 创建服务上下文
@@ -188,13 +191,22 @@ func NewServiceContext(c config.Config) *ServiceContext {
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)
// wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey)
// 为暂时关闭微信支付,将 WechatPayService 置为 nil避免在项目启动时初始化微信支付相关配置
var wechatPayService *service.WechatPayService
wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey)
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)
asynqService := service.NewAsynqService(c)
agentService := service.NewAgentService(c, orderModel, agentModel, agentAuditModel, agentClosureModel,
@@ -207,7 +219,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
adminPromotionLinkStatsService := service.NewAdminPromotionLinkStatsService(adminPromotionLinkModel,
adminPromotionLinkStatsTotalModel, adminPromotionLinkStatsHistoryModel)
imageService := service.NewImageService()
authorizationService := service.NewAuthorizationService(c, authorizationDocumentModel)
toolboxService := service.NewToolboxService(tianxingjuhe)
tianxingjuheService := tianxingjuhe
// ============================== 异步任务服务 ==============================
asynqServer := asynq.NewServer(
@@ -306,6 +319,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
AdminPromotionLinkStatsService: adminPromotionLinkStatsService,
ImageService: imageService,
AuthorizationService: authorizationService,
ToolboxService: toolboxService,
TianxingjuheService: tianxingjuheService,
}
}

View File

@@ -45,12 +45,10 @@ type PreLoanBackgroundCheckReq struct {
Code string `json:"code" validate:"required"`
}
// BackgroundCheck 查询请求结构
type BackgroundCheckReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
}
type PersonalDataReq struct {
Name string `json:"name" validate:"required,name"`
@@ -85,6 +83,12 @@ type DishonestExecutedReq struct {
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"`

View File

@@ -1547,6 +1547,10 @@ type GetCommissionResp struct {
List []Commission `json:"list"` // 查询列表
}
type GetEncryptedSceneIdResp struct {
EncryptedSceneId string `json:"encryptedSceneId"`
}
type GetLinkDataReq struct {
LinkIdentifier string `form:"link_identifier"`
}
@@ -1847,7 +1851,7 @@ type PaymentCheckResp struct {
type PaymentReq struct {
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"`
}
@@ -2015,6 +2019,7 @@ type QueryItem struct {
type QueryListReq struct {
Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数据量
Source string `form:"source,optional"` // 来源: miniapp 小程序过滤非车辆产品
}
type QueryListResp struct {
@@ -2054,6 +2059,7 @@ type QueryServiceReq struct {
Data string `json:"data" validate:"required"`
AgentIdentifier string `json:"agent_identifier,optional"`
App bool `json:"app,optional"`
CaptchaVerifyParam string `json:"captchaVerifyParam,optional"`
}
type QueryServiceResp struct {
@@ -2129,6 +2135,26 @@ type TimeRangeReport struct {
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 {
Id int64 `path:"id"` // 菜单ID
Pid int64 `json:"pid,optional"` // 父菜单ID
@@ -2247,4 +2273,5 @@ type GetAppVersionResp struct {
type SendSmsReq struct {
Mobile string `json:"mobile" validate:"required,mobile"`
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"
"tyc-server/common/globalkey"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"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/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
"tyc-server/common/globalkey"
)
var (
@@ -65,6 +64,7 @@ type (
LevelName string `db:"level_name"` // 会员级别,如 VIPSVIPnormal
Amount float64 `db:"amount"` // 充值金额
PaymentMethod string `db:"payment_method"` // 支付方式:支付宝,微信,苹果支付,其他
PaymentMerchant string `db:"payment_merchant"` // 支付商户标识,例如 one/two
OrderNo string `db:"order_no"` // 交易号
PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号
Status string `db:"status"`
@@ -89,11 +89,11 @@ func (m *defaultAgentMembershipRechargeOrderModel) Insert(ctx context.Context, s
tycAgentMembershipRechargeOrderOrderNoKey := fmt.Sprintf("%s%v", cacheTycAgentMembershipRechargeOrderOrderNoPrefix, data.OrderNo)
tycAgentMembershipRechargeOrderPlatformOrderIdKey := fmt.Sprintf("%s%v", cacheTycAgentMembershipRechargeOrderPlatformOrderIdPrefix, data.PlatformOrderId)
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 {
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)
}
@@ -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) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, agentMembershipRechargeOrderRowsWithPlaceHolder)
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)
}
@@ -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) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, agentMembershipRechargeOrderRowsWithPlaceHolder)
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)
if err != nil {
return err

View File

@@ -10,8 +10,6 @@ import (
"time"
"tyc-server/common/globalkey"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"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/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
"tyc-server/common/globalkey"
)
var (
@@ -63,6 +62,7 @@ type (
ProductId int64 `db:"product_id"` // 产品ID软关联到产品表
PaymentPlatform string `db:"payment_platform"` // 支付平台(支付宝、微信、苹果内购、其他)
PaymentScene string `db:"payment_scene"` // 支付场景App、H5、微信小程序、公众号
PaymentMerchant string `db:"payment_merchant"` // 支付商户标识,例如 one/two
PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号
Amount float64 `db:"amount"` // 支付金额
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)
tycOrderOrderNoKey := fmt.Sprintf("%s%v", cacheTycOrderOrderNoPrefix, data.OrderNo)
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 {
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)
}
@@ -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) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, orderRowsWithPlaceHolder)
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)
}
@@ -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) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, orderRowsWithPlaceHolder)
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)
if err != nil {
return err

View File

@@ -10,8 +10,6 @@ import (
"time"
"tyc-server/common/globalkey"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"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/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
"tyc-server/common/globalkey"
)
var (
@@ -64,6 +63,7 @@ type (
OrderId int64 `db:"order_id"` // 关联的订单ID
UserId int64 `db:"user_id"` // 用户ID
ProductId int64 `db:"product_id"` // 产品ID
PaymentMerchant string `db:"payment_merchant"` // 退款对应的支付商户标识,例如 one/two
PlatformRefundId sql.NullString `db:"platform_refund_id"` // 支付平台退款单号
RefundAmount float64 `db:"refund_amount"` // 退款金额
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)
tycOrderRefundRefundNoKey := fmt.Sprintf("%s%v", cacheTycOrderRefundRefundNoPrefix, data.RefundNo)
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 {
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)
}
@@ -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) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, orderRefundRowsWithPlaceHolder)
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)
}
@@ -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) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, orderRefundRowsWithPlaceHolder)
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)
if err != nil {
return err

View File

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

View File

@@ -5,7 +5,7 @@ $DB_URL = "tyc:5vg67b3UNHu8@tcp(127.0.0.1:22001)/tyc"
$OUTPUT_DIR = "./model"
$TEMPLATE_DIR = "../template"
# 表名列表
# 表名列表(按需开启)
$tables = @(
# "agent"
# "agent_active_stat",
@@ -16,7 +16,7 @@ $tables = @(
# "agent_commission_deduction"
# "agent_link",
# "agent_membership_config",
# "agent_membership_recharge_order"
"agent_membership_recharge_order"
# "agent_membership_user_config",
# "agent_order",
# "agent_platform_deduction"
@@ -29,15 +29,15 @@ $tables = @(
# "agent_withdrawal_tax_exemption"
# "feature"
# "global_notifications"
# "order"
# "order_refund"
"order"
"order_refund"
# "product",
# "product_feature"
# "query",
# "query_cleanup_log"
# "query_cleanup_detail"
# "query_cleanup_config"
"user"
# "user"
# "user_auth"
# "user_temp"
# "example"
@@ -55,7 +55,6 @@ $tables = @(
# "admin_promotion_link_stats_history"
# "admin_promotion_order"
# "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;

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 (
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/tea v1.2.2
github.com/alibabacloud-go/tea v1.3.13
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
github.com/bytedance/sonic v1.13.0
github.com/cenkalti/backoff/v4 v4.3.0
@@ -37,16 +37,17 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // 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/endpoint-util v1.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-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/bytedance/sonic/loader v0.2.2 // 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/davecgh/go-spew v1.1.1 // 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.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
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/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
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.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.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/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
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.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
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/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
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.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA=
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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
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/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.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/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
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-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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
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.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.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/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-20180826012351-8a410e7b638d/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.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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
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/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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.6.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.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.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/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-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.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.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.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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.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.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/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
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-20191011141410-1b5146add898/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) {
// 测试手机号加密
mobile := "18653052547"
mobile := "15029295957"
key := []byte("ff83609b2b24fc73196aac3d3dfb874f") // 16字节AES-128密钥
keyStr := hex.EncodeToString(key)
@@ -19,7 +19,8 @@ func TestAesEcbMobileEncryption(t *testing.T) {
t.Fatalf("手机号加密失败: %v", err)
}
fmt.Printf("encrypted: %s\n", encrypted)
jmStr := "m9EEeW9ZBBJmi1hx1k1uIQ=="
jmStr := "Am8/KpmBnsbXZoZOZq/oVQ=="
// 测试解密
decrypted, err := DecryptMobile(jmStr, keyStr)
if err != nil {