Compare commits
178 Commits
report-rou
...
019e47896d
| Author | SHA1 | Date | |
|---|---|---|---|
| 019e47896d | |||
| c0898e6829 | |||
| 4ee6e891cd | |||
| 44b5f6b145 | |||
| 677b7362cf | |||
| 02dbc02fe8 | |||
| 374143995e | |||
| 7a957a6b87 | |||
| c885d562ee | |||
| 9f36cd8b63 | |||
| 4122f874fc | |||
| 9a32387b21 | |||
| 7bf9150cfc | |||
| fecd5a38fd | |||
| 2636d9dff6 | |||
| 927b08b871 | |||
| dedd4a60a4 | |||
| a54a19e439 | |||
| 6dd392f673 | |||
| 8d5da9d88e | |||
| bc6dce21ee | |||
| 5630d93de6 | |||
| d12529307b | |||
| f17e22f4c8 | |||
| 8c0c16006e | |||
| 532503ffe3 | |||
| af37f8620c | |||
| fb495bc0ca | |||
| 2c57ac0dab | |||
| 4b437ecf56 | |||
| abf6284482 | |||
| e6ab833099 | |||
| 47cbc5b3a5 | |||
| f400052f95 | |||
| a38c58c357 | |||
| ede446fb90 | |||
| e23897a13f | |||
| debd0a973f | |||
| b424082f01 | |||
| 4afbd7dec7 | |||
| 070310cfa7 | |||
| 62e220a7b8 | |||
| c37cf2b54a | |||
| 25a3b4a761 | |||
| 1c83102e06 | |||
| d55520e69c | |||
| 4ea7cf4ecb | |||
| 3eae08a576 | |||
| c9126dd780 | |||
| f48289b32b | |||
| 0091e01574 | |||
| d2b806eda0 | |||
| ff86cb6fb9 | |||
| a6f858dbd3 | |||
| 6a1a59de8d | |||
| dbcecde4e0 | |||
| 3158bf8c04 | |||
| 860756b767 | |||
| 3c90144d51 | |||
| 29c49d6a00 | |||
| 5bff33547c | |||
| f50e11a052 | |||
| d1e06984ac | |||
| 2325a110b6 | |||
| 167dd63a7a | |||
| 360fed3907 | |||
| 88787c6145 | |||
| bfa8bbcfcb | |||
| 2fea046981 | |||
| 4e359060e6 | |||
| 7e5a69ffaa | |||
| 482644a914 | |||
| b2f0b47896 | |||
| b1f573c230 | |||
| 2363a51a6a | |||
| 847d48d276 | |||
| 43e4daa45b | |||
| 2005f09248 | |||
| eb8886d961 | |||
| 411aeb8e25 | |||
| f8806eb71c | |||
| 3ef7b7d1fb | |||
| 32336e4ba0 | |||
| 168a7c7f5f | |||
| ebaff673c7 | |||
| 1cfd4bf0d0 | |||
| 21d312f143 | |||
| 37ce65d6f7 | |||
| 4664c06c03 | |||
| ea59a8ddff | |||
| b171288361 | |||
| 426e6f537c | |||
| bce7ba9ef4 | |||
| da1eef618e | |||
| 791147e520 | |||
| 82959f74a8 | |||
| 6fccd4b223 | |||
| e48efb4566 | |||
| e6e38013b8 | |||
| 9b223c996d | |||
| 78035ca1ab | |||
| 38ca033e31 | |||
| b05c459b81 | |||
| 6104bfb84f | |||
| d15d2f2499 | |||
| 7e45a45309 | |||
| 473468e680 | |||
| 7785d3b6ef | |||
| 6607129083 | |||
| 0036180979 | |||
| 0d3a006c46 | |||
| 17f2433fee | |||
| 8a222a0b7f | |||
| 03cfddee93 | |||
| abc7d655ce | |||
| bdb8395615 | |||
| 6905936cf3 | |||
| 85bd011fd3 | |||
| 2d5c2120fb | |||
| 3e5016b439 | |||
| 96d530a67d | |||
| 96dfa3d758 | |||
| 1e2687522b | |||
| f6e7d46067 | |||
| 1e4e2f4b6d | |||
| 21fae5c486 | |||
| 6b902f68f1 | |||
| 745fbc05d5 | |||
| 6618e35869 | |||
| 7fdfa02189 | |||
| d60dc70798 | |||
| 7a589a9c13 | |||
| afc2ab9f4d | |||
| 8b3a80b93f | |||
| a36d188701 | |||
| ef2d73a9ec | |||
| c29c1bceff | |||
| 46a181d027 | |||
| ff8a946d13 | |||
| 68a9e32131 | |||
| ead5f17b7c | |||
| bd76520d22 | |||
| ee55b068a6 | |||
| 45397891b8 | |||
| ae482b7888 | |||
| ddbae6f82a | |||
| 90f16911e9 | |||
| 7746c5c8e3 | |||
| 8098c13de3 | |||
| e61f03a2dd | |||
| 4fcf370dcd | |||
| b0ed5b04ee | |||
| 269ff38604 | |||
| 23909c44f1 | |||
| aabe34b7d5 | |||
| c262894372 | |||
| 39f799bc41 | |||
| 1d4411a940 | |||
| fe44b452e3 | |||
| f1ec9bfe7f | |||
| a70e736cdd | |||
| 53d2c75a9c | |||
| 0bfa7b4f50 | |||
| e2e729eec8 | |||
| 5f7fb43804 | |||
| 89c5c0f9ad | |||
| 6c949a4a1c | |||
| 8556e7331d | |||
| 311d7a9b01 | |||
| ce45ce3ed0 | |||
| 34e2c1bc41 | |||
| 2618105140 | |||
| 6b41f3833a | |||
| 446a5c7661 | |||
| 7f8554fa12 | |||
| 65a61d0336 | |||
| 8dd6f71baf | |||
| e96653751d |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -40,8 +40,13 @@ internal/shared/pdf/fonts/*.ttf
|
||||
internal/shared/pdf/fonts/*.ttc
|
||||
internal/shared/pdf/fonts/*.otf
|
||||
|
||||
# Pure Component 目录(用于持久化存储,不进行版本控制)
|
||||
resources/Pure_Component/
|
||||
|
||||
# 其他
|
||||
*.exe
|
||||
*.exe*
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
cmd/api/__debug_bin*
|
||||
@@ -54,7 +54,8 @@ COPY config.yaml .
|
||||
COPY configs/ ./configs/
|
||||
|
||||
# 复制资源文件(直接从构建上下文复制,与配置文件一致)
|
||||
COPY resources ./resources
|
||||
COPY resources/etc ./resources/etc
|
||||
COPY resources/pdf ./resources/pdf
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8080
|
||||
|
||||
163
config.yaml
163
config.yaml
@@ -124,11 +124,19 @@ sms:
|
||||
access_key_id: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
||||
access_key_secret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
|
||||
endpoint_url: "dysmsapi.aliyuncs.com"
|
||||
sign_name: "天远查"
|
||||
sign_name: "海南海宇大数据"
|
||||
template_code: "SMS_302641455"
|
||||
code_length: 6
|
||||
expire_time: 5m
|
||||
mock_enabled: false
|
||||
# 签名验证配置(用于防止接口被刷)
|
||||
signature_enabled: true # 是否启用签名验证
|
||||
signature_secret: "TyApi2024SMSSecretKey!@#$%^&*()_+QWERTYUIOP" # 签名密钥(请修改为复杂密钥)
|
||||
# 滑块验证码配置
|
||||
captcha_enabled: true # 是否启用滑块验证码
|
||||
captcha_secret: "" # 阿里云验证码密钥(加密模式时需要,可选)EKEY
|
||||
captcha_endpoint: "captcha.cn-shanghai.aliyuncs.com" # 阿里云验证码服务Endpoint
|
||||
scene_id: "wynt39to" # 阿里云验证码场景ID
|
||||
rate_limit:
|
||||
daily_limit: 10
|
||||
hourly_limit: 5
|
||||
@@ -158,16 +166,16 @@ ocr:
|
||||
secret_key: "your-baidu-secret-key"
|
||||
|
||||
ratelimit:
|
||||
requests: 5000
|
||||
window: 60s
|
||||
requests: 7500
|
||||
window: 70s
|
||||
|
||||
# 每日请求限制配置
|
||||
daily_ratelimit:
|
||||
max_requests_per_day: 200 # 每日最大请求次数
|
||||
max_requests_per_ip: 10 # 每个IP每日最大请求次数
|
||||
max_requests_per_day: 300 # 每日最大请求次数
|
||||
max_requests_per_ip: 15 # 每个IP每日最大请求次数
|
||||
key_prefix: "daily_limit" # Redis键前缀
|
||||
ttl: 24h # 键过期时间
|
||||
max_concurrent: 5 # 最大并发请求数
|
||||
max_concurrent: 8 # 最大并发请求数
|
||||
|
||||
# 安全配置
|
||||
enable_ip_whitelist: false # 是否启用IP白名单
|
||||
@@ -192,6 +200,7 @@ daily_ratelimit:
|
||||
- "python" # Python脚本
|
||||
- "java" # Java脚本
|
||||
- "go-http-client" # Go HTTP客户端
|
||||
- "LangShen"
|
||||
|
||||
enable_referer: true # 是否检查Referer
|
||||
allowed_referers: # 允许的Referer
|
||||
@@ -233,7 +242,7 @@ development:
|
||||
|
||||
# 企业微信配置
|
||||
wechat_work:
|
||||
webhook_url: ""
|
||||
webhook_url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=649bf737-28ca-4f30-ad5f-cfb65b2af113"
|
||||
secret: ""
|
||||
|
||||
# ===========================================
|
||||
@@ -267,6 +276,8 @@ wallet:
|
||||
default_credit_limit: 50.00
|
||||
min_amount: "100.00" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
recharge_bonus_enabled: true # 是否启用充值赠送,设为 false 时仅展示商务洽谈提示
|
||||
api_store_recharge_tip: "" # 关闭赠送时展示的提示文案,为空则使用默认文案
|
||||
# 支付宝充值赠送配置
|
||||
alipay_recharge_bonus:
|
||||
- recharge_amount: 1000.00 # 充值1000元
|
||||
@@ -437,7 +448,7 @@ zhicha:
|
||||
# 🌐 木子数据配置
|
||||
# ===========================================
|
||||
muzi:
|
||||
url: "https://carv.m0101.com/magic/carv/pubin/service/academic"
|
||||
url: "https://carv.m0101.com/magic/carv/pubin/service"
|
||||
app_id: "713014138179585"
|
||||
app_secret: "bd4090ac652c404c80e90ebbdcd6ba1d"
|
||||
timeout: 60s
|
||||
@@ -494,3 +505,139 @@ xingwei:
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
# ===========================================
|
||||
# ✨ 极光配置
|
||||
# ===========================================
|
||||
jiguang:
|
||||
url: "http://api.jiguangcloud.com/jg-open-api-gateway/api"
|
||||
app_id: "66ZA28w5" # 请替换为实际的 appId
|
||||
app_secret: "e5261d0f6f003ae7b9fc1b0255b21761bb618d56" # 请替换为实际的 appSecret
|
||||
sign_method: "hmac" # 签名方法:md5 或 hmac,默认 hmac
|
||||
timeout: 60s # 请求超时时间,默认 60 秒
|
||||
|
||||
# 极光日志配置
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "logs/external_services"
|
||||
service_name: "jiguang"
|
||||
use_daily: true
|
||||
enable_level_separation: true
|
||||
|
||||
# 各级别配置
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
error:
|
||||
max_size: 200
|
||||
max_backups: 10
|
||||
max_age: 90
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
# ===========================================
|
||||
# 📄 PDF生成服务配置
|
||||
# ===========================================
|
||||
pdfgen:
|
||||
# 服务地址配置
|
||||
development_url: "http://pdfg.tianyuanapi.com" # 开发环境服务地址
|
||||
production_url: "http://1.117.67.95:15990" # 生产环境服务地址
|
||||
|
||||
# API路径配置
|
||||
api_path: "/api/v1/generate/guangzhou" # PDF生成API路径
|
||||
|
||||
# 超时配置
|
||||
timeout: 120s # 请求超时时间(120秒)
|
||||
|
||||
# 缓存配置
|
||||
cache:
|
||||
ttl: 24h # 缓存过期时间(24小时)
|
||||
cache_dir: "" # 缓存目录(空则使用默认目录)
|
||||
max_size: 0 # 最大缓存大小(0表示不限制,单位:字节)
|
||||
|
||||
# ===========================================
|
||||
# ✨ 数脉配置走实时接口
|
||||
# ===========================================
|
||||
shumai:
|
||||
url: "https://api.shumaidata.com"
|
||||
app_id: "pIfqx8MsoTOjhbB762qi5BfkjJ4D7w0O"
|
||||
app_secret: "BnJWo61hUgNEa5fqBCueiT1IZ1e0DxPU"
|
||||
# ===========================================
|
||||
# ✨ 数脉子账号配置走政务
|
||||
# ===========================================
|
||||
# 走政务接口使用这个
|
||||
app_id2: "AwZZRzWkArtFDO2lDcT2jHfuoo9n35Tq"
|
||||
app_secret2: "nCXN6fKLImjfvzI12hj8O1CMl1gJeaWh"
|
||||
|
||||
sign_method: "md5" # 签名方法:md5 或 hmac,默认 hmac
|
||||
timeout: 60s # 请求超时时间,默认 60 秒
|
||||
|
||||
# 数脉日志配置
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "logs/external_services"
|
||||
service_name: "shumai"
|
||||
use_daily: true
|
||||
enable_level_separation: true
|
||||
|
||||
# 各级别配置
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
error:
|
||||
max_size: 200
|
||||
max_backups: 10
|
||||
max_age: 90
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
|
||||
# ===========================================
|
||||
# ✨ 数据宝配置走实时接口
|
||||
# ===========================================
|
||||
shujubao:
|
||||
url: "https://api.chinadatapay.com"
|
||||
app_secret: "iOk0ALBX0BSdTSTf"
|
||||
sign_method: "md5" # 签名方法:md5 或 hmac,默认 hmac
|
||||
timeout: 60s # 请求超时时间,默认 60 秒
|
||||
# 数据宝日志配置
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "logs/external_services"
|
||||
service_name: "shujubao"
|
||||
use_daily: true
|
||||
enable_level_separation: true
|
||||
# 各级别配置
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
error:
|
||||
max_size: 200
|
||||
max_backups: 10
|
||||
max_age: 90
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -108,6 +108,8 @@ wallet:
|
||||
default_credit_limit: 0.01
|
||||
min_amount: "0.01" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
recharge_bonus_enabled: false # 开发环境可设为 true 测试赠送
|
||||
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||
# 支付宝充值赠送配置
|
||||
alipay_recharge_bonus:
|
||||
- recharge_amount: 0.01 # 充值1000元
|
||||
@@ -174,3 +176,13 @@ daily_ratelimit:
|
||||
enable_user_agent: false # 开发环境禁用User-Agent检查
|
||||
enable_referer: false # 开发环境禁用Referer检查
|
||||
enable_proxy_check: false # 开发环境禁用代理检查
|
||||
|
||||
# ===========================================
|
||||
# 📱 短信服务配置
|
||||
# ===========================================
|
||||
sms:
|
||||
# 滑块验证码配置
|
||||
captcha_enabled: true # 是否启用滑块验证码
|
||||
captcha_secret: "" # 阿里云验证码密钥(可选)
|
||||
scene_id: "wynt39to" # 阿里云验证码场景ID
|
||||
|
||||
@@ -109,7 +109,9 @@ wallet:
|
||||
default_credit_limit: 50.00
|
||||
min_amount: "100.00" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
# 支付宝充值赠送配置
|
||||
recharge_bonus_enabled: false # 暂不赠送,展示商务洽谈提示
|
||||
api_store_recharge_tip: "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||
# 支付宝充值赠送配置(recharge_bonus_enabled 为 true 时生效)
|
||||
alipay_recharge_bonus:
|
||||
- recharge_amount: 1000.00 # 充值1000元
|
||||
bonus_amount: 50.00 # 赠送50元
|
||||
@@ -122,38 +124,48 @@ wallet:
|
||||
# 🚦 频率限制配置 - 生产环境
|
||||
# ===========================================
|
||||
daily_ratelimit:
|
||||
max_requests_per_day: 50000 # 生产环境每日最大请求次数
|
||||
max_requests_per_ip: 5000 # 生产环境每个IP每日最大请求次数
|
||||
max_concurrent: 200 # 生产环境最大并发请求数
|
||||
|
||||
max_requests_per_day: 50000 # 生产环境每日最大请求次数
|
||||
max_requests_per_ip: 5000 # 生产环境每个IP每日最大请求次数
|
||||
max_concurrent: 200 # 生产环境最大并发请求数
|
||||
|
||||
# 排除频率限制的路径
|
||||
exclude_paths:
|
||||
- "/health" # 健康检查接口
|
||||
- "/metrics" # 监控指标接口
|
||||
|
||||
- "/health" # 健康检查接口
|
||||
- "/metrics" # 监控指标接口
|
||||
|
||||
# 排除频率限制的域名
|
||||
exclude_domains:
|
||||
- "api.*" # API二级域名不受频率限制
|
||||
- "*.api.*" # 支持多级API域名
|
||||
|
||||
- "api.*" # API二级域名不受频率限制
|
||||
- "*.api.*" # 支持多级API域名
|
||||
|
||||
# 生产环境安全配置(严格限制)
|
||||
enable_ip_whitelist: false # 生产环境不启用IP白名单
|
||||
enable_ip_blacklist: true # 启用IP黑名单
|
||||
ip_blacklist: # 生产环境IP黑名单
|
||||
- "192.168.1.100" # 示例:被禁止的IP
|
||||
- "10.0.0.50" # 示例:被禁止的IP
|
||||
|
||||
enable_user_agent: true # 启用User-Agent检查
|
||||
blocked_user_agents: # 被阻止的User-Agent
|
||||
- "curl" # 阻止curl请求
|
||||
- "wget" # 阻止wget请求
|
||||
- "python-requests" # 阻止Python requests
|
||||
|
||||
enable_referer: true # 启用Referer检查
|
||||
allowed_referers: # 允许的Referer
|
||||
enable_ip_whitelist: false # 生产环境不启用IP白名单
|
||||
enable_ip_blacklist: true # 启用IP黑名单
|
||||
ip_blacklist: # 生产环境IP黑名单
|
||||
- "192.168.1.100" # 示例:被禁止的IP
|
||||
- "10.0.0.50" # 示例:被禁止的IP
|
||||
|
||||
enable_user_agent: true # 启用User-Agent检查
|
||||
blocked_user_agents: # 被阻止的User-Agent
|
||||
- "curl" # 阻止curl请求
|
||||
- "wget" # 阻止wget请求
|
||||
- "python-requests" # 阻止Python requests
|
||||
- "LangShen" # 阻止LangShen请求
|
||||
|
||||
enable_referer: true # 启用Referer检查
|
||||
allowed_referers: # 允许的Referer
|
||||
- "https://console.tianyuanapi.com"
|
||||
- "https://consoletest.tianyuanapi.com"
|
||||
|
||||
enable_geo_block: false # 生产环境暂时不启用地理位置阻止
|
||||
enable_proxy_check: true # 启用代理检查
|
||||
|
||||
enable_geo_block: false # 生产环境暂时不启用地理位置阻止
|
||||
enable_proxy_check: true # 启用代理检查
|
||||
|
||||
# ===========================================
|
||||
# 📱 短信服务配置
|
||||
# ===========================================
|
||||
sms:
|
||||
# 滑块验证码配置
|
||||
captcha_enabled: true # 是否启用滑块验证码
|
||||
captcha_secret: "" # 阿里云验证码密钥(可选)
|
||||
scene_id: "wynt39to" # 阿里云验证码场景ID
|
||||
|
||||
|
||||
@@ -37,3 +37,12 @@ logger:
|
||||
# ===========================================
|
||||
jwt:
|
||||
secret: test-jwt-secret-key-for-testing-only
|
||||
|
||||
# ===========================================
|
||||
# 📱 短信服务配置
|
||||
# ===========================================
|
||||
sms:
|
||||
# 滑块验证码配置
|
||||
captcha_enabled: true # 是否启用滑块验证码
|
||||
captcha_secret: "" # 阿里云验证码密钥(可选)
|
||||
scene_id: "wynt39to" # 阿里云验证码场景ID
|
||||
|
||||
@@ -20,7 +20,8 @@ services:
|
||||
networks:
|
||||
- tyapi-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U tyapi_user -d tyapi -h localhost"]
|
||||
test:
|
||||
["CMD-SHELL", "pg_isready -U tyapi_user -d tyapi -h localhost"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
@@ -88,6 +89,9 @@ services:
|
||||
- "25000:8080"
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
- ./resources/Pure_Component:/app/resources/Pure_Component
|
||||
# 持久化PDF缓存目录,确保生成的PDF在容器重启后仍然存在
|
||||
- ./storage/pdfg-cache:/app/storage/pdfg-cache
|
||||
# user: "1001:1001" # 注释掉,使用root权限运行
|
||||
networks:
|
||||
- tyapi-network
|
||||
@@ -103,14 +107,6 @@ services:
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
cpus: "1.0"
|
||||
reservations:
|
||||
memory: 256M
|
||||
cpus: "0.3"
|
||||
|
||||
# TYAPI Worker 服务
|
||||
tyapi-worker:
|
||||
@@ -145,14 +141,6 @@ services:
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: "0.5"
|
||||
reservations:
|
||||
memory: 128M
|
||||
cpus: "0.1"
|
||||
|
||||
# Asynq 任务监控 (生产环境)
|
||||
asynq-monitor:
|
||||
@@ -169,7 +157,15 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:8080/health",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
@@ -189,6 +185,8 @@ volumes:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
pure_component:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
tyapi-network:
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
在产品详情页面添加"下载示例报告"功能,允许用户下载与产品对应的前端组件报告。报告文件位于 `resources/Pure Component/src/ui` 目录下,通过产品编号(product_code)匹配对应的文件夹或文件。
|
||||
在产品详情页面添加"下载示例报告"功能,允许用户下载与产品对应的前端组件报告。报告文件位于 `resources/Pure_Component/src/ui` 目录下,通过产品编号(product_code)匹配对应的文件夹或文件。
|
||||
|
||||
## 二、核心需求
|
||||
|
||||
### 2.1 基本功能
|
||||
|
||||
1. **报告匹配**:根据子产品的 `product_code` 模糊匹配 `resources/Pure Component/src/ui` 下的文件夹或文件
|
||||
1. **报告匹配**:根据子产品的 `product_code` 模糊匹配 `resources/Pure_Component/src/ui` 下的文件夹或文件
|
||||
- 支持前缀匹配(如产品编号为 `DWBG6A2C`,文件夹可能是 `DWBG6A2C` 或 `多cDWBG6A2C`)
|
||||
- 匹配规则:文件夹名称包含产品编号,或产品编号包含文件夹名称的核心部分
|
||||
|
||||
@@ -97,8 +97,6 @@ CREATE TABLE component_report_downloads (
|
||||
sub_product_ids TEXT, -- JSON数组,存储子产品ID列表(组合包使用)
|
||||
sub_product_codes TEXT, -- JSON数组,存储子产品编号列表
|
||||
download_price DECIMAL(10,2) NOT NULL, -- 实际支付价格
|
||||
original_price DECIMAL(10,2) NOT NULL, -- 原始总价
|
||||
discount_amount DECIMAL(10,2) DEFAULT 0, -- 减免金额
|
||||
payment_order_id VARCHAR(64), -- 支付订单号(关联充值记录)
|
||||
payment_type VARCHAR(20), -- 支付类型:alipay, wechat
|
||||
payment_status VARCHAR(20) DEFAULT 'pending', -- pending, success, failed
|
||||
@@ -537,7 +535,7 @@ func (s *ComponentReportServiceImpl) MatchProductCodeToPath(ctx context.Context,
|
||||
}
|
||||
|
||||
// 2. 扫描目录
|
||||
basePath := "resources/Pure Component/src/ui"
|
||||
basePath := "resources/Pure_Component/src/ui"
|
||||
entries, err := os.ReadDir(basePath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@@ -807,7 +805,7 @@ func (s *ComponentReportServiceImpl) GenerateZipFile(ctx context.Context, produc
|
||||
defer zipWriter.Close()
|
||||
|
||||
// 3. 遍历子产品,添加UI组件文件到ZIP
|
||||
basePath := "resources/Pure Component/src/ui"
|
||||
basePath := "resources/Pure_Component/src/ui"
|
||||
for _, productCode := range subProductCodes {
|
||||
path, fileType, err := s.MatchProductCodeToPath(ctx, productCode)
|
||||
if err != nil {
|
||||
@@ -847,7 +845,7 @@ func (s *ComponentReportServiceImpl) GenerateZipFile(ctx context.Context, produc
|
||||
|
||||
// 5. 添加其他必要的文件(如果需要)
|
||||
// 例如:复制 public 目录下的其他文件(如果有)
|
||||
publicBasePath := "resources/Pure Component/public"
|
||||
publicBasePath := "resources/Pure_Component/public"
|
||||
publicFiles, err := os.ReadDir(publicBasePath)
|
||||
if err == nil {
|
||||
for _, file := range publicFiles {
|
||||
|
||||
347
docs/短信接口签名验证使用指南.md
Normal file
347
docs/短信接口签名验证使用指南.md
Normal file
@@ -0,0 +1,347 @@
|
||||
# 短信接口签名验证使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
为了防止短信发送接口被恶意刷取,系统实现了基于HMAC-SHA256的签名验证机制。所有发送短信的请求必须包含有效的签名,否则请求将被拒绝。
|
||||
|
||||
## 工作原理
|
||||
|
||||
1. **前端生成签名**:使用密钥对请求参数进行HMAC-SHA256签名
|
||||
2. **后端验证签名**:后端使用相同密钥重新计算签名并比对
|
||||
3. **时间戳验证**:防止重放攻击,时间戳必须在5分钟内有效
|
||||
4. **随机数验证**:每次请求必须包含唯一的随机字符串(nonce)
|
||||
5. **参数编码传输**(推荐):将所有参数(包括签名)编码成Base64字符串后传输,隐藏参数结构,增加安全性
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 后端配置
|
||||
|
||||
在 `config.yaml` 中配置签名相关参数:
|
||||
|
||||
```yaml
|
||||
sms:
|
||||
# ... 其他配置 ...
|
||||
# 签名验证配置
|
||||
signature_enabled: true # 是否启用签名验证
|
||||
signature_secret: "TyApi2024SMSSecretKey!@#$%^&*()_+QWERTYUIOP" # 签名密钥(请修改为复杂密钥)
|
||||
```
|
||||
|
||||
**重要提示**:
|
||||
- 生产环境必须修改 `signature_secret` 为复杂的随机字符串
|
||||
- 密钥长度建议至少32个字符
|
||||
- 密钥应包含大小写字母、数字和特殊字符
|
||||
|
||||
## 签名算法
|
||||
|
||||
### 1. 构建待签名字符串
|
||||
|
||||
将请求参数(排除`signature`字段)按key排序,拼接成以下格式:
|
||||
|
||||
```
|
||||
key1=value1&key2=value2×tamp=1234567890&nonce=random_string
|
||||
```
|
||||
|
||||
### 2. 计算HMAC-SHA256签名
|
||||
|
||||
使用配置的密钥对待签名字符串进行HMAC-SHA256计算,结果转换为hex编码。
|
||||
|
||||
### 3. 请求参数
|
||||
|
||||
系统支持两种请求方式:
|
||||
|
||||
#### 方式1:直接传递参数
|
||||
|
||||
发送请求时直接传递所有字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"phone": "13800138000",
|
||||
"scene": "register",
|
||||
"timestamp": 1704067200,
|
||||
"nonce": "a1b2c3d4e5f6g7h8",
|
||||
"signature": "abc123def456..."
|
||||
}
|
||||
```
|
||||
|
||||
#### 方式2:编码后传输(推荐,更安全)
|
||||
|
||||
将所有参数(包括签名)编码成Base64字符串后传输,只传递一个`data`字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": "eyJwaG9uZSI6IjEzODAwMTM4MDAwIiwic2NlbmUiOiJyZWdpc3RlciIsInRpbWVzdGFtcCI6MTcwNDA2NzIwMCwibm9uY2UiOiJhMWIyYzNkNGE1ZjYiLCJzaWduYXR1cmUiOiJhYmMxMjNkZWY0NTYifQ=="
|
||||
}
|
||||
```
|
||||
|
||||
**编码传输的优势**:
|
||||
- 隐藏参数结构,增加破解难度
|
||||
- 参数不可见,防止参数被直接修改
|
||||
- 增加一层编码保护
|
||||
|
||||
## 前端实现
|
||||
|
||||
### Node.js 示例
|
||||
|
||||
参考文件:`tyapi-frontend/public/examples/nodejs/sms_signature_demo.js`
|
||||
|
||||
#### 方式1:直接传递参数
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
|
||||
function generateSignature(params, secretKey, timestamp, nonce) {
|
||||
// 1. 构建待签名字符串
|
||||
const keys = Object.keys(params)
|
||||
.filter(k => k !== 'signature')
|
||||
.sort();
|
||||
|
||||
const parts = keys.map(k => `${k}=${params[k]}`);
|
||||
parts.push(`timestamp=${timestamp}`);
|
||||
parts.push(`nonce=${nonce}`);
|
||||
|
||||
const signString = parts.join('&');
|
||||
|
||||
// 2. 计算HMAC-SHA256签名
|
||||
const signature = crypto
|
||||
.createHmac('sha256', secretKey)
|
||||
.update(signString)
|
||||
.digest('hex');
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const params = { phone: '13800138000', scene: 'register' };
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = generateRandomString(16);
|
||||
const secretKey = 'your_secret_key';
|
||||
const signature = generateSignature(params, secretKey, timestamp, nonce);
|
||||
|
||||
// 发送请求
|
||||
const requestBody = {
|
||||
phone: '13800138000',
|
||||
scene: 'register',
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
};
|
||||
```
|
||||
|
||||
#### 方式2:编码后传输(推荐)
|
||||
|
||||
```javascript
|
||||
// 1. 生成签名(同上)
|
||||
const params = { phone: '13800138000', scene: 'register' };
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = generateRandomString(16);
|
||||
const secretKey = 'your_secret_key';
|
||||
const signature = generateSignature(params, secretKey, timestamp, nonce);
|
||||
|
||||
// 2. 构建包含所有参数的JSON对象
|
||||
const allParams = {
|
||||
phone: '13800138000',
|
||||
scene: 'register',
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
};
|
||||
|
||||
// 3. 编码为Base64
|
||||
const jsonString = JSON.stringify(allParams);
|
||||
const encodedData = Buffer.from(jsonString).toString('base64');
|
||||
|
||||
// 4. 发送请求(只传递data字段)
|
||||
const requestBody = {
|
||||
data: encodedData,
|
||||
};
|
||||
```
|
||||
|
||||
### 浏览器 JavaScript 示例
|
||||
|
||||
参考文件:`tyapi-frontend/public/examples/javascript/sms_signature_demo.js`
|
||||
|
||||
#### 方式1:直接传递参数
|
||||
|
||||
```javascript
|
||||
async function generateSignature(params, secretKey, timestamp, nonce) {
|
||||
// 1. 构建待签名字符串
|
||||
const keys = Object.keys(params)
|
||||
.filter(k => k !== 'signature')
|
||||
.sort();
|
||||
|
||||
const parts = keys.map(k => `${k}=${params[k]}`);
|
||||
parts.push(`timestamp=${timestamp}`);
|
||||
parts.push(`nonce=${nonce}`);
|
||||
|
||||
const signString = parts.join('&');
|
||||
|
||||
// 2. 使用Web Crypto API计算HMAC-SHA256签名
|
||||
const encoder = new TextEncoder();
|
||||
const keyData = encoder.encode(secretKey);
|
||||
const messageData = encoder.encode(signString);
|
||||
|
||||
const cryptoKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
keyData,
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['sign']
|
||||
);
|
||||
|
||||
const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
|
||||
const hashArray = Array.from(new Uint8Array(signature));
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
return hashHex;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const params = { phone: '13800138000', scene: 'register' };
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = generateNonce(16);
|
||||
const secretKey = 'your_secret_key';
|
||||
const signature = await generateSignature(params, secretKey, timestamp, nonce);
|
||||
|
||||
// 发送请求
|
||||
const requestBody = {
|
||||
phone: '13800138000',
|
||||
scene: 'register',
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
};
|
||||
```
|
||||
|
||||
#### 方式2:编码后传输(推荐)
|
||||
|
||||
```javascript
|
||||
// 1. 生成签名(同上)
|
||||
const params = { phone: '13800138000', scene: 'register' };
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const nonce = generateNonce(16);
|
||||
const secretKey = 'your_secret_key';
|
||||
const signature = await generateSignature(params, secretKey, timestamp, nonce);
|
||||
|
||||
// 2. 构建包含所有参数的JSON对象
|
||||
const allParams = {
|
||||
phone: '13800138000',
|
||||
scene: 'register',
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
};
|
||||
|
||||
// 3. 编码为Base64(浏览器环境)
|
||||
const jsonString = JSON.stringify(allParams);
|
||||
const encodedData = btoa(unescape(encodeURIComponent(jsonString)));
|
||||
|
||||
// 4. 发送请求(只传递data字段)
|
||||
const requestBody = {
|
||||
data: encodedData,
|
||||
};
|
||||
```
|
||||
|
||||
## 密钥隐藏策略
|
||||
|
||||
由于前端代码可以被查看,完全隐藏密钥是不可能的。但可以通过以下方式增加破解难度:
|
||||
|
||||
### 1. 字符串拆分和拼接
|
||||
|
||||
```javascript
|
||||
function getSecretKey() {
|
||||
const part1 = 'TyApi2024';
|
||||
const part2 = 'SMSSecret';
|
||||
const part3 = 'Key!@#$%^';
|
||||
return part1 + part2 + part3;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 字符数组拼接
|
||||
|
||||
```javascript
|
||||
function getSecretKey() {
|
||||
const chars = ['T', 'y', 'A', 'p', 'i', ...];
|
||||
return chars.join('');
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Base64编码混淆
|
||||
|
||||
```javascript
|
||||
function getSecretKey() {
|
||||
const encoded = 'base64_encoded_string';
|
||||
return atob(encoded);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 代码混淆
|
||||
|
||||
使用构建工具(如webpack、rollup等)进行代码混淆和压缩,使密钥更难被发现。
|
||||
|
||||
### 5. 后端代理(推荐)
|
||||
|
||||
将签名逻辑放在后端代理接口中,前端只调用代理接口,不直接包含密钥。
|
||||
|
||||
## 安全建议
|
||||
|
||||
1. **定期更换密钥**:建议每3-6个月更换一次签名密钥
|
||||
2. **监控异常请求**:监控签名验证失败的请求,及时发现攻击行为
|
||||
3. **结合其他防护措施**:
|
||||
- IP限流
|
||||
- 设备指纹识别
|
||||
- 验证码(图形验证码)
|
||||
- 行为分析
|
||||
|
||||
4. **日志记录**:记录所有签名验证失败的请求,包括IP、User-Agent等信息
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误
|
||||
|
||||
1. **签名字段缺失**:返回 `"签名字段缺失"`
|
||||
2. **时间戳无效**:返回 `"时间戳无效"`
|
||||
3. **请求已过期**:返回 `"请求已过期,时间戳超出容差范围"`
|
||||
4. **签名验证失败**:返回 `"签名验证失败"`
|
||||
|
||||
### 时间戳容差
|
||||
|
||||
系统允许的时间戳容差为 **5分钟**(300秒)。如果请求时间戳与服务器时间差超过5分钟,请求将被拒绝。
|
||||
|
||||
## 测试
|
||||
|
||||
### 测试签名生成
|
||||
|
||||
```bash
|
||||
# 使用Node.js示例
|
||||
node tyapi-frontend/public/examples/nodejs/sms_signature_demo.js
|
||||
```
|
||||
|
||||
### 测试API调用
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/v1/users/send-code \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"phone": "13800138000",
|
||||
"scene": "register",
|
||||
"timestamp": 1704067200,
|
||||
"nonce": "a1b2c3d4e5f6g7h8",
|
||||
"signature": "计算得到的签名"
|
||||
}'
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **时间同步**:确保客户端和服务器时间同步,避免时间戳验证失败
|
||||
2. **随机数唯一性**:每次请求的nonce应该是唯一的,可以使用UUID或时间戳+随机数
|
||||
3. **密钥安全**:生产环境密钥不要提交到代码仓库,应使用环境变量或密钥管理服务
|
||||
4. **向后兼容**:如果需要在开发环境禁用签名验证,可以设置 `signature_enabled: false`
|
||||
|
||||
## 相关文件
|
||||
|
||||
- 后端签名工具:`internal/shared/crypto/signature.go`
|
||||
- 后端Handler:`internal/infrastructure/http/handlers/user_handler.go`
|
||||
- 配置结构:`internal/config/config.go`
|
||||
- Node.js示例:`tyapi-frontend/public/examples/nodejs/sms_signature_demo.js`
|
||||
- 浏览器示例:`tyapi-frontend/public/examples/javascript/sms_signature_demo.js`
|
||||
|
||||
9
go.mod
9
go.mod
@@ -3,6 +3,9 @@ module tyapi-server
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
github.com/alibabacloud-go/captcha-20230305 v1.1.3
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13
|
||||
github.com/alibabacloud-go/tea v1.3.13
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
@@ -46,11 +49,16 @@ require (
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
|
||||
github.com/aliyun/credentials-go v1.4.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // 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
|
||||
@@ -104,6 +112,7 @@ require (
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tiendc/go-deepcopy v1.6.0 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
|
||||
187
go.sum
187
go.sum
@@ -1,4 +1,6 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
@@ -14,8 +16,54 @@ github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWX
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4=
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
||||
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=
|
||||
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
|
||||
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
|
||||
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
|
||||
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=
|
||||
github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
|
||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
|
||||
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
|
||||
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
|
||||
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||
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.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 h1:qagvUyrgOnBIlVRQWOyCZGVKUIYbMBdGdJ104vBpRFU=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
||||
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
|
||||
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/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
@@ -29,11 +77,16 @@ github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCN
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -41,6 +94,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
@@ -98,14 +154,32 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
@@ -132,8 +206,10 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf/v2 v2.17.3 h1:otZXZby2gXJ7uU6pzprXHq/R57lsHLi0WtH79VabWxY=
|
||||
github.com/jung-kurt/gofpdf/v2 v2.17.3/go.mod h1:Qx8ZNg4cNsO5i6uLDiBngnm+ii/FjtAqjRNO6drsoYU=
|
||||
@@ -167,6 +243,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
@@ -183,6 +261,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
@@ -220,6 +299,9 @@ github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw=
|
||||
github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
|
||||
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
|
||||
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
@@ -231,10 +313,12 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -258,6 +342,9 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tiendc/go-deepcopy v1.6.0 h1:0UtfV/imoCwlLxVsyfUd4hNHnB3drXsfle+wzSCA5Wo=
|
||||
github.com/tiendc/go-deepcopy v1.6.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
|
||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
@@ -274,6 +361,8 @@ github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Q
|
||||
github.com/xuri/excelize/v2 v2.9.1/go.mod h1:x7L6pKz2dvo9ejrRuD8Lnl98z4JLt0TGAwjhW+EiP8s=
|
||||
github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q=
|
||||
github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
@@ -309,12 +398,24 @@ golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-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.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
@@ -323,26 +424,63 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
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/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-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/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -350,42 +488,88 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.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/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -394,6 +578,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
@@ -412,6 +597,8 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w=
|
||||
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
@@ -231,6 +231,7 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
||||
&financeEntities.AlipayOrder{},
|
||||
&financeEntities.InvoiceApplication{},
|
||||
&financeEntities.UserInvoiceInfo{},
|
||||
&financeEntities.PurchaseOrder{}, //购买组件订单表
|
||||
|
||||
// 产品域
|
||||
&productEntities.Product{},
|
||||
|
||||
@@ -218,12 +218,38 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
|
||||
return nil, ErrFrozenAccount
|
||||
}
|
||||
|
||||
// 验证产品是否启用
|
||||
if !product.IsEnabled {
|
||||
s.logger.Error("产品未启用", zap.String("product_code", product.Code))
|
||||
return nil, ErrProductDisabled
|
||||
}
|
||||
|
||||
// 4. 验证IP白名单(非开发环境)
|
||||
if !s.config.App.IsDevelopment() && !cmd.Options.IsDebug {
|
||||
// 添加调试日志
|
||||
s.logger.Info("开始验证白名单",
|
||||
zap.String("userId", apiUser.UserId),
|
||||
zap.String("clientIP", cmd.ClientIP),
|
||||
zap.Bool("isDevelopment", s.config.App.IsDevelopment()),
|
||||
zap.Bool("isDebug", cmd.Options.IsDebug),
|
||||
zap.Int("whiteListCount", len(apiUser.WhiteList)))
|
||||
|
||||
// 输出白名单详细信息(用于调试)
|
||||
for idx, item := range apiUser.WhiteList {
|
||||
s.logger.Info("白名单项",
|
||||
zap.Int("index", idx),
|
||||
zap.String("ipAddress", item.IPAddress),
|
||||
zap.String("remark", item.Remark))
|
||||
}
|
||||
|
||||
if !apiUser.IsWhiteListed(cmd.ClientIP) {
|
||||
s.logger.Error("IP不在白名单内", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP))
|
||||
s.logger.Error("IP不在白名单内",
|
||||
zap.String("userId", apiUser.UserId),
|
||||
zap.String("ip", cmd.ClientIP),
|
||||
zap.Int("whiteListSize", len(apiUser.WhiteList)))
|
||||
return nil, ErrInvalidIP
|
||||
}
|
||||
s.logger.Info("白名单验证通过", zap.String("ip", cmd.ClientIP))
|
||||
}
|
||||
|
||||
// 5. 验证钱包状态
|
||||
@@ -583,12 +609,17 @@ func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID
|
||||
// 转换为响应DTO
|
||||
var items []dto.ApiCallRecordResponse
|
||||
for _, call := range calls {
|
||||
// 出于安全考虑,不再在数据库中存储或解密真实请求参数
|
||||
// 这里只保留数据库中的原始占位值(通常为空字符串)
|
||||
requestParamsStr := call.RequestParams
|
||||
|
||||
item := dto.ApiCallRecordResponse{
|
||||
ID: call.ID,
|
||||
AccessId: call.AccessId,
|
||||
UserId: *call.UserId,
|
||||
TransactionId: call.TransactionId,
|
||||
ClientIp: call.ClientIp,
|
||||
RequestParams: requestParamsStr,
|
||||
Status: call.Status,
|
||||
StartAt: call.StartAt.Format("2006-01-02 15:04:05"),
|
||||
CreatedAt: call.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
@@ -649,12 +680,50 @@ func (s *ApiApplicationServiceImpl) GetAdminApiCalls(ctx context.Context, filter
|
||||
continue
|
||||
}
|
||||
|
||||
// // 解密请求参数
|
||||
// var requestParamsStr string = call.RequestParams // 默认使用原始值
|
||||
// if call.UserId != nil && *call.UserId != "" {
|
||||
// // 获取用户的API密钥信息
|
||||
// apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId)
|
||||
// if err != nil {
|
||||
// s.logger.Error("获取用户API信息失败",
|
||||
// zap.Error(err),
|
||||
// zap.String("call_id", call.ID),
|
||||
// zap.String("user_id", *call.UserId))
|
||||
// // 获取失败时使用原始值
|
||||
// } else if apiUser.SecretKey != "" {
|
||||
// // 使用用户的SecretKey解密请求参数
|
||||
// decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{
|
||||
// EncryptedData: call.RequestParams,
|
||||
// SecretKey: apiUser.SecretKey,
|
||||
// })
|
||||
// if err != nil {
|
||||
// s.logger.Error("解密请求参数失败",
|
||||
// zap.Error(err),
|
||||
// zap.String("call_id", call.ID),
|
||||
// zap.String("user_id", *call.UserId))
|
||||
// // 解密失败时使用原始值
|
||||
// } else {
|
||||
// // 将解密后的数据转换为JSON字符串
|
||||
// if jsonBytes, err := json.Marshal(decryptedParams); err == nil {
|
||||
// requestParamsStr = string(jsonBytes)
|
||||
// } else {
|
||||
// s.logger.Error("序列化解密参数失败",
|
||||
// zap.Error(err),
|
||||
// zap.String("call_id", call.ID))
|
||||
// // 序列化失败时使用原始值
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
item := dto.ApiCallRecordResponse{
|
||||
ID: call.ID,
|
||||
AccessId: call.AccessId,
|
||||
TransactionId: call.TransactionId,
|
||||
ClientIp: call.ClientIp,
|
||||
Status: call.Status,
|
||||
// RequestParams: requestParamsStr,
|
||||
Status: call.Status,
|
||||
}
|
||||
|
||||
// 安全设置用户ID
|
||||
@@ -1292,7 +1361,7 @@ func (s *ApiApplicationServiceImpl) UpdateUserBalanceAlertSettings(ctx context.C
|
||||
// TestBalanceAlertSms 测试余额预警短信
|
||||
func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, userID string, phone string, balance float64, alertType string) error {
|
||||
// 获取用户信息以获取企业名称
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
|
||||
@@ -16,13 +16,13 @@ type ApiCallValidationResult struct {
|
||||
SecretKey string `json:"secret_key"`
|
||||
IsValid bool `json:"is_valid"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
|
||||
|
||||
// 新增字段
|
||||
ContractCode string `json:"contract_code"`
|
||||
ApiCall *api_entities.ApiCall `json:"api_call"`
|
||||
RequestParams map[string]interface{} `json:"request_params"`
|
||||
Product *product_entities.Product `json:"product"`
|
||||
Subscription *product_entities.Subscription `json:"subscription"`
|
||||
ContractCode string `json:"contract_code"`
|
||||
ApiCall *api_entities.ApiCall `json:"api_call"`
|
||||
RequestParams map[string]interface{} `json:"request_params"`
|
||||
Product *product_entities.Product `json:"product"`
|
||||
Subscription *product_entities.Subscription `json:"subscription"`
|
||||
}
|
||||
|
||||
// GetUserID 获取用户ID
|
||||
@@ -99,6 +99,6 @@ func (r *ApiCallValidationResult) SetContractCode(code string) {
|
||||
// SetSubscription 设置订阅信息(包含实际扣费金额)
|
||||
func (r *ApiCallValidationResult) SetSubscription(subscription *product_entities.Subscription) {
|
||||
r.SubscriptionID = subscription.ID
|
||||
r.Amount = subscription.Price // 使用订阅价格作为扣费金额
|
||||
r.Amount = subscription.Price // 使用订阅价格作为扣费金额
|
||||
r.Subscription = subscription
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ type ApiCallRecordResponse struct {
|
||||
ProductName *string `json:"product_name,omitempty"`
|
||||
TransactionId string `json:"transaction_id"`
|
||||
ClientIp string `json:"client_ip"`
|
||||
RequestParams string `json:"request_params"`
|
||||
Status string `json:"status"`
|
||||
StartAt string `json:"start_at"`
|
||||
EndAt *string `json:"end_at,omitempty"`
|
||||
|
||||
@@ -32,6 +32,11 @@ type CertificationApplicationService interface {
|
||||
// 获取认证列表(管理员)
|
||||
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) (*responses.CertificationListResponse, error)
|
||||
|
||||
// ================ 管理员后台操作用例 ================
|
||||
|
||||
// AdminCompleteCertificationWithoutContract 管理员代用户完成认证(暂不关联合同)
|
||||
AdminCompleteCertificationWithoutContract(ctx context.Context, cmd *commands.AdminCompleteCertificationCommand) (*responses.CertificationResponse, error)
|
||||
|
||||
// ================ e签宝回调处理 ================
|
||||
|
||||
// 处理e签宝回调
|
||||
|
||||
@@ -18,7 +18,9 @@ import (
|
||||
"tyapi-server/internal/domains/certification/services"
|
||||
finance_service "tyapi-server/internal/domains/finance/services"
|
||||
user_entities "tyapi-server/internal/domains/user/entities"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/infrastructure/external/notification"
|
||||
"tyapi-server/internal/infrastructure/external/storage"
|
||||
"tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/esign"
|
||||
@@ -47,7 +49,8 @@ type CertificationApplicationServiceImpl struct {
|
||||
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
|
||||
txManager *database.TransactionManager
|
||||
|
||||
logger *zap.Logger
|
||||
wechatWorkService *notification.WeChatWorkService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCertificationApplicationService 创建认证应用服务
|
||||
@@ -67,7 +70,12 @@ func NewCertificationApplicationService(
|
||||
ocrService sharedOCR.OCRService,
|
||||
txManager *database.TransactionManager,
|
||||
logger *zap.Logger,
|
||||
cfg *config.Config,
|
||||
) CertificationApplicationService {
|
||||
var wechatSvc *notification.WeChatWorkService
|
||||
if cfg != nil && cfg.WechatWork.WebhookURL != "" {
|
||||
wechatSvc = notification.NewWeChatWorkService(cfg.WechatWork.WebhookURL, cfg.WechatWork.Secret, logger)
|
||||
}
|
||||
return &CertificationApplicationServiceImpl{
|
||||
aggregateService: aggregateService,
|
||||
userAggregateService: userAggregateService,
|
||||
@@ -83,6 +91,7 @@ func NewCertificationApplicationService(
|
||||
enterpriseInfoSubmitRecordService: enterpriseInfoSubmitRecordService,
|
||||
ocrService: ocrService,
|
||||
txManager: txManager,
|
||||
wechatWorkService: wechatSvc,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -109,13 +118,16 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
||||
)
|
||||
|
||||
// 验证验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.LegalPersonPhone, cmd.VerificationCode, user_entities.SMSSceneCertification); err != nil {
|
||||
record.MarkAsFailed(err.Error())
|
||||
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
||||
if saveErr != nil {
|
||||
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
||||
// 特殊验证码"768005"直接跳过验证环节
|
||||
if cmd.VerificationCode != "768005" {
|
||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.LegalPersonPhone, cmd.VerificationCode, user_entities.SMSSceneCertification); err != nil {
|
||||
record.MarkAsFailed(err.Error())
|
||||
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
||||
if saveErr != nil {
|
||||
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
||||
}
|
||||
return nil, fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
return nil, fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
s.logger.Info("开始处理企业信息提交",
|
||||
zap.String("user_id", cmd.UserID))
|
||||
@@ -578,6 +590,110 @@ func (s *CertificationApplicationServiceImpl) HandleEsignCallback(
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 管理员后台操作用例 ================
|
||||
|
||||
// AdminCompleteCertificationWithoutContract 管理员代用户完成认证(暂不关联合同)
|
||||
func (s *CertificationApplicationServiceImpl) AdminCompleteCertificationWithoutContract(
|
||||
ctx context.Context,
|
||||
cmd *commands.AdminCompleteCertificationCommand,
|
||||
) (*responses.CertificationResponse, error) {
|
||||
s.logger.Info("管理员代用户完成认证(不关联合同)",
|
||||
zap.String("admin_id", cmd.AdminID),
|
||||
zap.String("user_id", cmd.UserID),
|
||||
)
|
||||
|
||||
// 1. 基础参数及企业信息校验
|
||||
enterpriseInfo := &certification_value_objects.EnterpriseInfo{
|
||||
CompanyName: cmd.CompanyName,
|
||||
UnifiedSocialCode: cmd.UnifiedSocialCode,
|
||||
LegalPersonName: cmd.LegalPersonName,
|
||||
LegalPersonID: cmd.LegalPersonID,
|
||||
LegalPersonPhone: cmd.LegalPersonPhone,
|
||||
EnterpriseAddress: cmd.EnterpriseAddress,
|
||||
}
|
||||
if err := enterpriseInfo.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("企业信息验证失败: %s", err.Error())
|
||||
}
|
||||
|
||||
// 检查统一社会信用代码唯一性(排除当前用户)
|
||||
exists, err := s.userAggregateService.CheckUnifiedSocialCodeExists(ctx, cmd.UnifiedSocialCode, cmd.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查企业信息失败: %s", err.Error())
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("统一社会信用代码已被其他用户使用")
|
||||
}
|
||||
|
||||
var cert *entities.Certification
|
||||
|
||||
// 2. 事务内:创建/加载认证、写入企业信息、直接完成认证、创建钱包和API用户
|
||||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
// 2.1 检查并创建认证记录
|
||||
existsCert, err := s.aggregateService.ExistsByUserID(txCtx, cmd.UserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查用户认证是否存在失败: %s", err.Error())
|
||||
}
|
||||
if !existsCert {
|
||||
cert, err = s.aggregateService.CreateCertification(txCtx, cmd.UserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建认证信息失败: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
cert, err = s.aggregateService.LoadCertificationByUserID(txCtx, cmd.UserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 2.2 写入/覆盖用户域企业信息
|
||||
if err := s.userAggregateService.CreateOrUpdateEnterpriseInfo(
|
||||
txCtx,
|
||||
cmd.UserID,
|
||||
cmd.CompanyName,
|
||||
cmd.UnifiedSocialCode,
|
||||
cmd.LegalPersonName,
|
||||
cmd.LegalPersonID,
|
||||
cmd.LegalPersonPhone,
|
||||
cmd.EnterpriseAddress,
|
||||
); err != nil {
|
||||
return fmt.Errorf("保存企业信息失败: %s", err.Error())
|
||||
}
|
||||
|
||||
// 2.3 直接将认证状态设置为完成(管理员操作,暂不校验合同信息)
|
||||
if err := cert.TransitionTo(
|
||||
enums.StatusCompleted,
|
||||
enums.ActorTypeAdmin,
|
||||
cmd.AdminID,
|
||||
fmt.Sprintf("管理员代用户完成认证:%s", cmd.Reason),
|
||||
); err != nil {
|
||||
return fmt.Errorf("更新认证状态失败: %s", err.Error())
|
||||
}
|
||||
|
||||
// 2.4 基础激活:创建钱包、API用户并在用户域标记完成认证
|
||||
if err := s.completeUserActivationWithoutContract(txCtx, cert); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2.5 保存认证信息
|
||||
if err := s.aggregateService.SaveCertification(txCtx, cert); err != nil {
|
||||
return fmt.Errorf("保存认证信息失败: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := s.convertToResponse(cert)
|
||||
s.logger.Info("管理员代用户完成认证成功(不关联合同)",
|
||||
zap.String("admin_id", cmd.AdminID),
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("certification_id", cert.ID),
|
||||
)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ================ 辅助方法 ================
|
||||
|
||||
// convertToResponse 转换实体为响应DTO
|
||||
@@ -929,21 +1045,8 @@ func (s *CertificationApplicationServiceImpl) handleContractAfterSignComplete(ct
|
||||
s.logger.Info("合同信息已保存到聚合根", zap.String("file_name", fileName), zap.String("qiniu_url", qiniuURL))
|
||||
}
|
||||
|
||||
_, err = s.walletAggregateService.CreateWallet(ctx, cert.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("创建钱包失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
}
|
||||
|
||||
// 6. 创建API用户
|
||||
err = s.apiUserAggregateService.CreateApiUser(ctx, cert.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("创建API用户失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
}
|
||||
err = s.userAggregateService.CompleteCertification(ctx, cert.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("用户域完成认证失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
}
|
||||
return nil
|
||||
// 合同签署完成后的基础激活流程
|
||||
return s.completeUserActivationWithoutContract(ctx, cert)
|
||||
}
|
||||
|
||||
// downloadFileContent 通过URL下载文件内容
|
||||
@@ -992,6 +1095,56 @@ func (s *CertificationApplicationServiceImpl) AddStatusMetadata(ctx context.Cont
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// completeUserActivationWithoutContract 创建钱包、API用户并在用户域标记完成认证(不依赖合同信息)
|
||||
func (s *CertificationApplicationServiceImpl) completeUserActivationWithoutContract(ctx context.Context, cert *entities.Certification) error {
|
||||
// 创建钱包
|
||||
if _, err := s.walletAggregateService.CreateWallet(ctx, cert.UserID); err != nil {
|
||||
s.logger.Error("创建钱包失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
}
|
||||
|
||||
// 创建API用户
|
||||
if err := s.apiUserAggregateService.CreateApiUser(ctx, cert.UserID); err != nil {
|
||||
s.logger.Error("创建API用户失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
}
|
||||
|
||||
// 标记用户域完成认证
|
||||
if err := s.userAggregateService.CompleteCertification(ctx, cert.UserID); err != nil {
|
||||
s.logger.Error("用户域完成认证失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 企业认证成功企业微信通知(仅展示企业名称和联系手机)
|
||||
if s.wechatWorkService != nil {
|
||||
user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, cert.UserID)
|
||||
if err == nil {
|
||||
companyName := "未知企业"
|
||||
phone := ""
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
if user.EnterpriseInfo.LegalPersonPhone != "" {
|
||||
phone = user.EnterpriseInfo.LegalPersonPhone
|
||||
}
|
||||
}
|
||||
if user.Phone != "" && phone == "" {
|
||||
phone = user.Phone
|
||||
}
|
||||
content := fmt.Sprintf(
|
||||
"### 【天远API】企业认证成功\n"+
|
||||
"> 企业名称:%s\n"+
|
||||
"> 联系手机:%s\n"+
|
||||
"> 完成时间:%s\n"+
|
||||
"\n该企业已完成认证,请相关同事同步更新内部系统。",
|
||||
companyName,
|
||||
phone,
|
||||
time.Now().Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecognizeBusinessLicense OCR识别营业执照
|
||||
func (s *CertificationApplicationServiceImpl) RecognizeBusinessLicense(
|
||||
ctx context.Context,
|
||||
|
||||
@@ -71,7 +71,20 @@ type EsignOrganization struct {
|
||||
// 可以根据需要添加更多企业信息字段
|
||||
}
|
||||
|
||||
|
||||
// AdminCompleteCertificationCommand 管理员代用户完成认证命令(可不关联合同)
|
||||
type AdminCompleteCertificationCommand struct {
|
||||
// AdminID 从JWT中获取,不从请求体传递,因此不做必填校验
|
||||
AdminID string `json:"-"`
|
||||
UserID string `json:"user_id" validate:"required"`
|
||||
CompanyName string `json:"company_name" validate:"required,min=2,max=100"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" validate:"required"`
|
||||
LegalPersonName string `json:"legal_person_name" validate:"required,min=2,max=20"`
|
||||
LegalPersonID string `json:"legal_person_id" validate:"required"`
|
||||
LegalPersonPhone string `json:"legal_person_phone" validate:"required"`
|
||||
EnterpriseAddress string `json:"enterprise_address" validate:"required"`
|
||||
// 备注信息,用于记录后台操作原因
|
||||
Reason string `json:"reason" validate:"required"`
|
||||
}
|
||||
// ForceTransitionStatusCommand 强制状态转换命令(管理员)
|
||||
type ForceTransitionStatusCommand struct {
|
||||
CertificationID string `json:"certification_id" validate:"required"`
|
||||
|
||||
@@ -108,8 +108,10 @@ type AlipayRechargeOrderResponse struct {
|
||||
|
||||
// RechargeConfigResponse 充值配置响应
|
||||
type RechargeConfigResponse struct {
|
||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||
RechargeBonusEnabled bool `json:"recharge_bonus_enabled"` // 是否启用充值赠送
|
||||
ApiStoreRechargeTip string `json:"api_store_recharge_tip"` // API 商店充值提示(大额/批量联系商务)
|
||||
AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
|
||||
}
|
||||
|
||||
@@ -125,3 +127,45 @@ type UserSimpleResponse struct {
|
||||
CompanyName string `json:"company_name"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
// PurchaseRecordResponse 购买记录响应
|
||||
type PurchaseRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
TradeNo *string `json:"trade_no,omitempty"`
|
||||
ProductID string `json:"product_id"`
|
||||
ProductCode string `json:"product_code"`
|
||||
ProductName string `json:"product_name"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Subject string `json:"subject"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
PayAmount *decimal.Decimal `json:"pay_amount,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Platform string `json:"platform"`
|
||||
PayChannel string `json:"pay_channel"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
BuyerID string `json:"buyer_id,omitempty"`
|
||||
SellerID string `json:"seller_id,omitempty"`
|
||||
ReceiptAmount decimal.Decimal `json:"receipt_amount,omitempty"`
|
||||
NotifyTime *time.Time `json:"notify_time,omitempty"`
|
||||
ReturnTime *time.Time `json:"return_time,omitempty"`
|
||||
PayTime *time.Time `json:"pay_time,omitempty"`
|
||||
FilePath *string `json:"file_path,omitempty"`
|
||||
FileSize *int64 `json:"file_size,omitempty"`
|
||||
Remark string `json:"remark,omitempty"`
|
||||
ErrorCode string `json:"error_code,omitempty"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
User *UserSimpleResponse `json:"user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// PurchaseRecordListResponse 购买记录列表响应
|
||||
type PurchaseRecordListResponse struct {
|
||||
Items []PurchaseRecordResponse `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ type FinanceApplicationService interface {
|
||||
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
||||
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
||||
|
||||
// 购买记录
|
||||
GetUserPurchaseRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
|
||||
GetAdminPurchaseRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
|
||||
|
||||
// 获取充值配置
|
||||
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"tyapi-server/internal/application/finance/dto/commands"
|
||||
"tyapi-server/internal/application/finance/dto/queries"
|
||||
@@ -15,6 +14,8 @@ import (
|
||||
finance_services "tyapi-server/internal/domains/finance/services"
|
||||
product_repositories "tyapi-server/internal/domains/product/repositories"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/infrastructure/external/notification"
|
||||
"tyapi-server/internal/shared/component_report"
|
||||
"tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/export"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
@@ -36,12 +37,14 @@ type FinanceApplicationServiceImpl struct {
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
||||
wechatOrderRepo finance_repositories.WechatOrderRepository
|
||||
rechargeRecordRepo finance_repositories.RechargeRecordRepository
|
||||
purchaseOrderRepo finance_repositories.PurchaseOrderRepository
|
||||
componentReportRepo product_repositories.ComponentReportRepository
|
||||
userRepo user_repositories.UserRepository
|
||||
txManager *database.TransactionManager
|
||||
exportManager *export.ExportManager
|
||||
logger *zap.Logger
|
||||
config *config.Config
|
||||
wechatWorkService *notification.WeChatWorkService
|
||||
}
|
||||
|
||||
// NewFinanceApplicationService 创建财务应用服务
|
||||
@@ -54,6 +57,7 @@ func NewFinanceApplicationService(
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
||||
wechatOrderRepo finance_repositories.WechatOrderRepository,
|
||||
rechargeRecordRepo finance_repositories.RechargeRecordRepository,
|
||||
purchaseOrderRepo finance_repositories.PurchaseOrderRepository,
|
||||
componentReportRepo product_repositories.ComponentReportRepository,
|
||||
userRepo user_repositories.UserRepository,
|
||||
txManager *database.TransactionManager,
|
||||
@@ -61,6 +65,11 @@ func NewFinanceApplicationService(
|
||||
config *config.Config,
|
||||
exportManager *export.ExportManager,
|
||||
) FinanceApplicationService {
|
||||
var wechatSvc *notification.WeChatWorkService
|
||||
if config != nil && config.WechatWork.WebhookURL != "" {
|
||||
wechatSvc = notification.NewWeChatWorkService(config.WechatWork.WebhookURL, config.WechatWork.Secret, logger)
|
||||
}
|
||||
|
||||
return &FinanceApplicationServiceImpl{
|
||||
aliPayClient: aliPayClient,
|
||||
wechatPayService: wechatPayService,
|
||||
@@ -70,15 +79,53 @@ func NewFinanceApplicationService(
|
||||
alipayOrderRepo: alipayOrderRepo,
|
||||
wechatOrderRepo: wechatOrderRepo,
|
||||
rechargeRecordRepo: rechargeRecordRepo,
|
||||
purchaseOrderRepo: purchaseOrderRepo,
|
||||
componentReportRepo: componentReportRepo,
|
||||
userRepo: userRepo,
|
||||
txManager: txManager,
|
||||
exportManager: exportManager,
|
||||
logger: logger,
|
||||
config: config,
|
||||
wechatWorkService: wechatSvc,
|
||||
}
|
||||
}
|
||||
|
||||
// getUserContactInfo 获取企业名称和联系手机号(尽量用企业信息里的手机号,退化到用户登录手机号)
|
||||
func (s *FinanceApplicationServiceImpl) getUserContactInfo(ctx context.Context, userID string) (companyName, phone string) {
|
||||
companyName = "未知企业"
|
||||
phone = ""
|
||||
|
||||
if userID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取用户企业信息失败,使用默认企业名称",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 登录手机号
|
||||
if user.Phone != "" {
|
||||
phone = user.Phone
|
||||
}
|
||||
|
||||
// 企业名称和企业手机号
|
||||
if user.EnterpriseInfo != nil {
|
||||
if user.EnterpriseInfo.CompanyName != "" {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
if user.EnterpriseInfo.LegalPersonPhone != "" {
|
||||
phone = user.EnterpriseInfo.LegalPersonPhone
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
|
||||
// 调用钱包聚合服务创建钱包
|
||||
wallet, err := s.walletService.CreateWallet(ctx, cmd.UserID)
|
||||
@@ -854,13 +901,7 @@ func (s *FinanceApplicationServiceImpl) HandleAlipayCallback(ctx context.Context
|
||||
zap.String("trade_no", notification.TradeNo),
|
||||
)
|
||||
|
||||
// 先检查是否是组件报告下载的支付订单
|
||||
s.logger.Info("步骤1: 检查是否是组件报告下载订单",
|
||||
zap.String("out_trade_no", notification.OutTradeNo),
|
||||
)
|
||||
|
||||
// 使用公共方法处理支付成功逻辑(包括更新充值记录状态)
|
||||
// 无论是组件报告下载订单还是普通充值订单,都需要更新充值记录状态
|
||||
// 处理支付宝支付成功逻辑
|
||||
err = s.processAlipayPaymentSuccess(ctx, notification.OutTradeNo, notification.TradeNo, notification.TotalAmount, notification.BuyerId, notification.SellerId)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝支付成功失败",
|
||||
@@ -886,20 +927,52 @@ func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.
|
||||
return err
|
||||
}
|
||||
|
||||
// 直接调用充值记录服务处理支付成功逻辑
|
||||
// 该服务内部会处理所有必要的检查、事务和更新操作
|
||||
// 如果是组件报告下载订单,服务会自动跳过钱包余额增加
|
||||
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
|
||||
// 查找支付宝订单
|
||||
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝支付成功失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查并更新组件报告下载记录状态(如果存在)
|
||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
||||
if alipayOrder == nil {
|
||||
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
|
||||
return fmt.Errorf("支付宝订单不存在")
|
||||
}
|
||||
|
||||
// 判断是否为充值订单还是购买订单
|
||||
_, err = s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
|
||||
if err == nil {
|
||||
// 这是充值订单,调用充值记录服务处理支付成功逻辑
|
||||
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝充值支付成功失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 尝试查找购买订单
|
||||
_, err = s.purchaseOrderRepo.GetByID(ctx, alipayOrder.RechargeID)
|
||||
if err == nil {
|
||||
// 这是购买订单(可能是示例报告购买订单),调用处理购买订单支付成功逻辑
|
||||
err = s.processPurchaseOrderPaymentSuccess(ctx, alipayOrder.RechargeID, tradeNo, amount, buyerID, sellerID)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝购买订单支付成功失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("purchase_order_id", alipayOrder.RechargeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
s.logger.Error("无法确定订单类型",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", alipayOrder.RechargeID),
|
||||
)
|
||||
return fmt.Errorf("无法确定订单类型")
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("支付宝支付成功处理完成",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
@@ -907,6 +980,33 @@ func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.
|
||||
zap.String("amount", amount.String()),
|
||||
)
|
||||
|
||||
// 充值成功企业微信通知(仅充值订单,且忽略发送错误)
|
||||
if s.wechatWorkService != nil {
|
||||
// 再次获取充值记录,拿到用户ID
|
||||
rechargeRecord, err := s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
|
||||
if err == nil {
|
||||
companyName, phone := s.getUserContactInfo(ctx, rechargeRecord.UserID)
|
||||
content := fmt.Sprintf(
|
||||
"### 【天远API】用户充值成功通知\n"+
|
||||
"> 企业名称:%s\n"+
|
||||
"> 联系手机:%s\n"+
|
||||
"> 充值渠道:支付宝\n"+
|
||||
"> 充值金额:%s 元\n"+
|
||||
"> 时间:%s\n",
|
||||
companyName,
|
||||
phone,
|
||||
amount.String(),
|
||||
time.Now().Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||
} else {
|
||||
s.logger.Warn("获取充值记录失败,跳过企业微信充值通知",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1237,17 +1337,26 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
|
||||
|
||||
// GetRechargeConfig 获取充值配置
|
||||
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
|
||||
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
|
||||
for _, rule := range s.config.Wallet.AliPayRechargeBonus {
|
||||
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
|
||||
RechargeAmount: rule.RechargeAmount,
|
||||
BonusAmount: rule.BonusAmount,
|
||||
})
|
||||
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0)
|
||||
if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||
bonus = make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
|
||||
for _, rule := range s.config.Wallet.AliPayRechargeBonus {
|
||||
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
|
||||
RechargeAmount: rule.RechargeAmount,
|
||||
BonusAmount: rule.BonusAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
tip := s.config.Wallet.ApiStoreRechargeTip
|
||||
if tip == "" && !s.config.Wallet.RechargeBonusEnabled {
|
||||
tip = "尊敬的客户,若您的充值金额较大或有批量调价需求,为获取专属商务优惠方案,请直接联系我司商务团队进行洽谈。感谢您的支持!"
|
||||
}
|
||||
return &responses.RechargeConfigResponse{
|
||||
MinAmount: s.config.Wallet.MinAmount,
|
||||
MaxAmount: s.config.Wallet.MaxAmount,
|
||||
AlipayRechargeBonus: bonus,
|
||||
MinAmount: s.config.Wallet.MinAmount,
|
||||
MaxAmount: s.config.Wallet.MaxAmount,
|
||||
RechargeBonusEnabled: s.config.Wallet.RechargeBonusEnabled,
|
||||
ApiStoreRechargeTip: tip,
|
||||
AlipayRechargeBonus: bonus,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1477,30 +1586,7 @@ func (s *FinanceApplicationServiceImpl) HandleWechatPayCallback(ctx context.Cont
|
||||
zap.String("transaction_id", transactionID),
|
||||
)
|
||||
|
||||
// 先检查是否是组件报告下载的支付订单
|
||||
s.logger.Info("步骤1: 检查是否是组件报告下载订单",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
|
||||
// 检查组件报告下载记录
|
||||
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, outTradeNo)
|
||||
if err == nil && download != nil {
|
||||
s.logger.Info("步骤2: 发现组件报告下载订单,直接更新下载记录状态",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("product_id", download.ProductID),
|
||||
zap.String("current_status", download.PaymentStatus),
|
||||
)
|
||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
||||
s.logger.Info("========== 组件报告下载订单处理完成 ==========")
|
||||
return nil
|
||||
}
|
||||
|
||||
s.logger.Info("步骤3: 不是组件报告下载订单,按充值流程处理",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
|
||||
// 处理支付成功逻辑(充值流程)
|
||||
// 处理微信支付成功逻辑(充值流程)
|
||||
err = s.processWechatPaymentSuccess(ctx, outTradeNo, transactionID, totalAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("处理微信支付成功失败",
|
||||
@@ -1535,26 +1621,34 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
return fmt.Errorf("微信订单不存在")
|
||||
}
|
||||
|
||||
// 查找对应的充值记录
|
||||
// 判断是否为充值订单还是购买订单
|
||||
rechargeRecord, err := s.rechargeRecordService.GetByID(ctx, wechatOrder.RechargeID)
|
||||
if err != nil {
|
||||
s.logger.Error("查找充值记录失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", wechatOrder.RechargeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("查找充值记录失败: %w", err)
|
||||
if err == nil {
|
||||
// 这是充值订单,继续原有的处理逻辑
|
||||
} else {
|
||||
// 尝试查找购买订单
|
||||
_, err = s.purchaseOrderRepo.GetByID(ctx, wechatOrder.RechargeID)
|
||||
if err == nil {
|
||||
// 这是购买订单(可能是示例报告购买订单),调用处理购买订单支付成功逻辑
|
||||
err = s.processPurchaseOrderPaymentSuccess(ctx, wechatOrder.RechargeID, transactionID, amount, "", "")
|
||||
if err != nil {
|
||||
s.logger.Error("处理微信购买订单支付成功失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("purchase_order_id", wechatOrder.RechargeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
s.logger.Error("无法确定订单类型",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", wechatOrder.RechargeID),
|
||||
)
|
||||
return fmt.Errorf("无法确定订单类型")
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("步骤4: 检查充值记录备注,判断是否为组件报告下载订单",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", rechargeRecord.ID),
|
||||
zap.String("notes", rechargeRecord.Notes),
|
||||
)
|
||||
|
||||
// 检查是否是组件报告下载订单(通过备注判断)
|
||||
isComponentReportOrder := strings.Contains(rechargeRecord.Notes, "购买") && strings.Contains(rechargeRecord.Notes, "报告示例")
|
||||
|
||||
// 检查订单和充值记录状态,如果都已成功则跳过(只记录一次日志)
|
||||
if wechatOrder.Status == finance_entities.WechatOrderStatusSuccess && rechargeRecord.Status == finance_entities.RechargeStatusSuccess {
|
||||
s.logger.Info("微信支付订单已处理成功,跳过重复处理",
|
||||
@@ -1562,18 +1656,13 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
zap.String("transaction_id", transactionID),
|
||||
zap.String("order_id", wechatOrder.ID),
|
||||
zap.String("recharge_id", rechargeRecord.ID),
|
||||
zap.Bool("is_component_report", isComponentReportOrder),
|
||||
)
|
||||
// 如果是组件报告下载订单,确保更新下载记录状态
|
||||
if isComponentReportOrder {
|
||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算充值赠送金额(复用支付宝的赠送逻辑)
|
||||
// 计算充值赠送金额(复用支付宝的赠送逻辑,受 recharge_bonus_enabled 开关控制)
|
||||
bonusAmount := decimal.Zero
|
||||
if len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||
if s.config.Wallet.RechargeBonusEnabled && len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
||||
for i := len(s.config.Wallet.AliPayRechargeBonus) - 1; i >= 0; i-- {
|
||||
rule := s.config.Wallet.AliPayRechargeBonus[i]
|
||||
if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) {
|
||||
@@ -1638,33 +1727,17 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
)
|
||||
}
|
||||
|
||||
// 检查是否是组件报告下载订单(通过备注判断)
|
||||
isComponentReportOrder := strings.Contains(rechargeRecord.Notes, "购买") && strings.Contains(rechargeRecord.Notes, "报告示例")
|
||||
|
||||
if isComponentReportOrder {
|
||||
s.logger.Info("步骤5: 检测到组件报告下载订单,不增加钱包余额",
|
||||
// 充值到钱包(包含赠送金额)
|
||||
totalRechargeAmount := amount.Add(bonusAmount)
|
||||
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, totalRechargeAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("充值到钱包失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", rechargeRecord.ID),
|
||||
zap.String("notes", rechargeRecord.Notes),
|
||||
zap.String("user_id", rechargeRecord.UserID),
|
||||
zap.String("total_amount", totalRechargeAmount.String()),
|
||||
zap.Error(err),
|
||||
)
|
||||
// 组件报告下载订单不增加钱包余额,只更新订单和充值记录状态
|
||||
} else {
|
||||
s.logger.Info("步骤5: 普通充值订单,增加钱包余额",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", rechargeRecord.ID),
|
||||
)
|
||||
// 充值到钱包(包含赠送金额)
|
||||
totalRechargeAmount := amount.Add(bonusAmount)
|
||||
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, totalRechargeAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("充值到钱包失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("user_id", rechargeRecord.UserID),
|
||||
zap.String("total_amount", totalRechargeAmount.String()),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1680,105 +1753,147 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果是组件报告下载订单,更新下载记录状态
|
||||
if isComponentReportOrder {
|
||||
s.logger.Info("步骤6: 更新组件报告下载记录状态",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
||||
}
|
||||
|
||||
s.logger.Info("微信支付成功处理完成",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("transaction_id", transactionID),
|
||||
zap.String("amount", amount.String()),
|
||||
zap.String("bonus_amount", bonusAmount.String()),
|
||||
zap.String("user_id", rechargeRecord.UserID),
|
||||
zap.Bool("is_component_report", isComponentReportOrder),
|
||||
)
|
||||
|
||||
// 微信充值成功企业微信通知(忽略发送错误)
|
||||
if s.wechatWorkService != nil {
|
||||
companyName, phone := s.getUserContactInfo(ctx, rechargeRecord.UserID)
|
||||
content := fmt.Sprintf(
|
||||
"### 【天远API】用户充值成功通知\n"+
|
||||
"> 企业名称:%s\n"+
|
||||
"> 联系手机:%s\n"+
|
||||
"> 充值渠道:微信\n"+
|
||||
"> 充值金额:%s 元\n"+
|
||||
"> 时间:%s\n",
|
||||
companyName,
|
||||
phone,
|
||||
amount.String(),
|
||||
time.Now().Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateComponentReportDownloadStatus 更新组件报告下载记录状态
|
||||
func (s *FinanceApplicationServiceImpl) updateComponentReportDownloadStatus(ctx context.Context, outTradeNo string) {
|
||||
s.logger.Info("========== 开始更新组件报告下载记录状态 ==========",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
|
||||
if s.componentReportRepo == nil {
|
||||
s.logger.Warn("组件报告下载Repository未初始化,跳过更新")
|
||||
return
|
||||
}
|
||||
|
||||
// 根据支付订单号查找组件报告下载记录
|
||||
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, outTradeNo)
|
||||
// processPurchaseOrderPaymentSuccess 处理购买订单支付成功的逻辑
|
||||
func (s *FinanceApplicationServiceImpl) processPurchaseOrderPaymentSuccess(ctx context.Context, purchaseOrderID, tradeNo string, amount decimal.Decimal, buyerID, sellerID string) error {
|
||||
// 查找购买订单
|
||||
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, purchaseOrderID)
|
||||
if err != nil {
|
||||
s.logger.Info("未找到组件报告下载记录,可能不是组件报告下载订单",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
s.logger.Error("查找购买订单失败",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return
|
||||
return fmt.Errorf("查找购买订单失败: %w", err)
|
||||
}
|
||||
|
||||
if download == nil {
|
||||
s.logger.Info("组件报告下载记录为空,跳过更新",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
if purchaseOrder == nil {
|
||||
s.logger.Error("购买订单不存在",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
)
|
||||
return
|
||||
return fmt.Errorf("购买订单不存在")
|
||||
}
|
||||
|
||||
s.logger.Info("步骤1: 找到组件报告下载记录",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("product_id", download.ProductID),
|
||||
zap.String("current_status", download.PaymentStatus),
|
||||
)
|
||||
|
||||
// 如果已经是成功状态,跳过
|
||||
if download.PaymentStatus == "success" {
|
||||
s.logger.Info("组件报告下载记录已是成功状态,跳过更新",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
// 检查订单状态,如果已支付则跳过
|
||||
if purchaseOrder.Status == finance_entities.PurchaseOrderStatusPaid {
|
||||
s.logger.Info("购买订单已支付,跳过处理",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
s.logger.Info("步骤2: 更新支付状态为成功",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
)
|
||||
|
||||
// 更新支付状态为成功
|
||||
download.PaymentStatus = "success"
|
||||
|
||||
// 设置过期时间(30天后)
|
||||
expiresAt := time.Now().Add(30 * 24 * time.Hour)
|
||||
download.ExpiresAt = &expiresAt
|
||||
|
||||
s.logger.Info("步骤3: 保存更新后的下载记录",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("expires_at", expiresAt.Format("2006-01-02 15:04:05")),
|
||||
)
|
||||
|
||||
// 更新记录
|
||||
err = s.componentReportRepo.UpdateDownload(ctx, download)
|
||||
// 更新购买订单状态
|
||||
purchaseOrder.MarkPaid(tradeNo, buyerID, sellerID, amount, amount)
|
||||
err = s.purchaseOrderRepo.Update(ctx, purchaseOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("更新组件报告下载记录状态失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
s.logger.Error("更新购买订单状态失败",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return
|
||||
return fmt.Errorf("更新购买订单状态失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("========== 组件报告下载记录状态更新成功 ==========",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("product_id", download.ProductID),
|
||||
zap.String("payment_status", download.PaymentStatus),
|
||||
// 更新对应的支付订单状态(微信或支付宝)
|
||||
if purchaseOrder.PayChannel == "alipay" {
|
||||
alipayOrder, err := s.alipayOrderRepo.GetByRechargeID(ctx, purchaseOrderID)
|
||||
if err == nil && alipayOrder != nil {
|
||||
alipayOrder.MarkSuccess(tradeNo, buyerID, sellerID, amount, amount)
|
||||
err = s.alipayOrderRepo.Update(ctx, *alipayOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("更新支付宝订单状态失败",
|
||||
zap.String("out_trade_no", alipayOrder.OutTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if purchaseOrder.PayChannel == "wechat" {
|
||||
wechatOrder, err := s.wechatOrderRepo.GetByRechargeID(ctx, purchaseOrderID)
|
||||
if err == nil && wechatOrder != nil {
|
||||
wechatOrder.MarkSuccess(tradeNo, buyerID, sellerID, amount, amount)
|
||||
err = s.wechatOrderRepo.Update(ctx, *wechatOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("更新微信订单状态失败",
|
||||
zap.String("out_trade_no", wechatOrder.OutTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是组件报告购买,需要生成并更新报告文件
|
||||
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, purchaseOrderID)
|
||||
if err == nil && download != nil {
|
||||
// 创建报告生成器
|
||||
zipGenerator := component_report.NewZipGenerator(s.logger)
|
||||
|
||||
// 生成报告文件
|
||||
zipPath, err := zipGenerator.GenerateZipFile(
|
||||
ctx,
|
||||
download.ProductID,
|
||||
[]string{download.ProductCode}, // 使用简化后的只包含主产品编号的列表
|
||||
nil, // 使用默认的JSON生成器
|
||||
"", // 使用默认路径
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("生成组件报告文件失败",
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
zap.Error(err),
|
||||
)
|
||||
// 不中断流程,即使生成文件失败也继续处理
|
||||
} else {
|
||||
// 更新下载记录的文件路径
|
||||
download.FilePath = &zipPath
|
||||
err = s.componentReportRepo.UpdateDownload(ctx, download)
|
||||
if err != nil {
|
||||
s.logger.Error("更新下载记录文件路径失败",
|
||||
zap.String("download_id", download.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
s.logger.Info("组件报告文件生成成功",
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("file_path", zipPath),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("购买订单支付成功处理完成",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
zap.String("trade_no", tradeNo),
|
||||
zap.String("amount", amount.String()),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleWechatRefundCallback 处理微信退款回调
|
||||
@@ -1842,3 +1957,163 @@ func (s *FinanceApplicationServiceImpl) HandleWechatRefundCallback(ctx context.C
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserPurchaseRecords 获取用户购买记录
|
||||
func (s *FinanceApplicationServiceImpl) GetUserPurchaseRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error) {
|
||||
// 确保 filters 不为 nil
|
||||
if filters == nil {
|
||||
filters = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// 添加 user_id 筛选条件,确保只能查询当前用户的记录
|
||||
filters["user_id"] = userID
|
||||
|
||||
// 获取总数
|
||||
total, err := s.purchaseOrderRepo.CountByFilters(ctx, filters)
|
||||
if err != nil {
|
||||
s.logger.Error("统计用户购买记录失败", zap.Error(err), zap.String("userID", userID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查询用户购买记录(使用筛选和分页功能)
|
||||
orders, err := s.purchaseOrderRepo.GetByFilters(ctx, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询用户购买记录失败", zap.Error(err), zap.String("userID", userID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []responses.PurchaseRecordResponse
|
||||
for _, order := range orders {
|
||||
item := responses.PurchaseRecordResponse{
|
||||
ID: order.ID,
|
||||
UserID: order.UserID,
|
||||
OrderNo: order.OrderNo,
|
||||
TradeNo: order.TradeNo,
|
||||
ProductID: order.ProductID,
|
||||
ProductCode: order.ProductCode,
|
||||
ProductName: order.ProductName,
|
||||
Category: order.Category,
|
||||
Subject: order.Subject,
|
||||
Amount: order.Amount,
|
||||
PayAmount: order.PayAmount,
|
||||
Status: string(order.Status),
|
||||
Platform: order.Platform,
|
||||
PayChannel: order.PayChannel,
|
||||
PaymentType: order.PaymentType,
|
||||
BuyerID: order.BuyerID,
|
||||
SellerID: order.SellerID,
|
||||
ReceiptAmount: order.ReceiptAmount,
|
||||
NotifyTime: order.NotifyTime,
|
||||
ReturnTime: order.ReturnTime,
|
||||
PayTime: order.PayTime,
|
||||
FilePath: order.FilePath,
|
||||
FileSize: order.FileSize,
|
||||
Remark: order.Remark,
|
||||
ErrorCode: order.ErrorCode,
|
||||
ErrorMessage: order.ErrorMessage,
|
||||
CreatedAt: order.CreatedAt,
|
||||
UpdatedAt: order.UpdatedAt,
|
||||
}
|
||||
|
||||
// 获取用户信息和企业名称
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, order.UserID)
|
||||
if err == nil {
|
||||
companyName := "未知企业"
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
item.CompanyName = companyName
|
||||
item.User = &responses.UserSimpleResponse{
|
||||
ID: user.ID,
|
||||
CompanyName: companyName,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &responses.PurchaseRecordListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAdminPurchaseRecords 获取管理端购买记录
|
||||
func (s *FinanceApplicationServiceImpl) GetAdminPurchaseRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error) {
|
||||
// 获取总数
|
||||
total, err := s.purchaseOrderRepo.CountByFilters(ctx, filters)
|
||||
if err != nil {
|
||||
s.logger.Error("统计管理端购买记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查询购买记录
|
||||
orders, err := s.purchaseOrderRepo.GetByFilters(ctx, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询管理端购买记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []responses.PurchaseRecordResponse
|
||||
for _, order := range orders {
|
||||
item := responses.PurchaseRecordResponse{
|
||||
ID: order.ID,
|
||||
UserID: order.UserID,
|
||||
OrderNo: order.OrderNo,
|
||||
TradeNo: order.TradeNo,
|
||||
ProductID: order.ProductID,
|
||||
ProductCode: order.ProductCode,
|
||||
ProductName: order.ProductName,
|
||||
Category: order.Category,
|
||||
Subject: order.Subject,
|
||||
Amount: order.Amount,
|
||||
PayAmount: order.PayAmount,
|
||||
Status: string(order.Status),
|
||||
Platform: order.Platform,
|
||||
PayChannel: order.PayChannel,
|
||||
PaymentType: order.PaymentType,
|
||||
BuyerID: order.BuyerID,
|
||||
SellerID: order.SellerID,
|
||||
ReceiptAmount: order.ReceiptAmount,
|
||||
NotifyTime: order.NotifyTime,
|
||||
ReturnTime: order.ReturnTime,
|
||||
PayTime: order.PayTime,
|
||||
FilePath: order.FilePath,
|
||||
FileSize: order.FileSize,
|
||||
Remark: order.Remark,
|
||||
ErrorCode: order.ErrorCode,
|
||||
ErrorMessage: order.ErrorMessage,
|
||||
CreatedAt: order.CreatedAt,
|
||||
UpdatedAt: order.UpdatedAt,
|
||||
}
|
||||
|
||||
// 获取用户信息和企业名称
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, order.UserID)
|
||||
if err == nil {
|
||||
companyName := "未知企业"
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
item.CompanyName = companyName
|
||||
item.User = &responses.UserSimpleResponse{
|
||||
ID: user.ID,
|
||||
CompanyName: companyName,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &responses.PurchaseRecordListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/application/finance/dto"
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/finance/entities"
|
||||
finance_repo "tyapi-server/internal/domains/finance/repositories"
|
||||
"tyapi-server/internal/domains/finance/services"
|
||||
"tyapi-server/internal/domains/finance/value_objects"
|
||||
user_repo "tyapi-server/internal/domains/user/repositories"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/infrastructure/external/notification"
|
||||
"tyapi-server/internal/infrastructure/external/storage"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
@@ -59,8 +61,9 @@ type InvoiceApplicationServiceImpl struct {
|
||||
userAggregateService user_service.UserAggregateService
|
||||
|
||||
// 外部服务依赖
|
||||
storageService *storage.QiNiuStorageService
|
||||
logger *zap.Logger
|
||||
storageService *storage.QiNiuStorageService
|
||||
logger *zap.Logger
|
||||
wechatWorkServer *notification.WeChatWorkService
|
||||
}
|
||||
|
||||
// NewInvoiceApplicationService 创建发票应用服务
|
||||
@@ -76,7 +79,13 @@ func NewInvoiceApplicationService(
|
||||
userInvoiceInfoService services.UserInvoiceInfoService,
|
||||
storageService *storage.QiNiuStorageService,
|
||||
logger *zap.Logger,
|
||||
cfg *config.Config,
|
||||
) InvoiceApplicationService {
|
||||
var wechatSvc *notification.WeChatWorkService
|
||||
if cfg != nil && cfg.WechatWork.WebhookURL != "" {
|
||||
wechatSvc = notification.NewWeChatWorkService(cfg.WechatWork.WebhookURL, cfg.WechatWork.Secret, logger)
|
||||
}
|
||||
|
||||
return &InvoiceApplicationServiceImpl{
|
||||
invoiceRepo: invoiceRepo,
|
||||
userInvoiceInfoRepo: userInvoiceInfoRepo,
|
||||
@@ -89,6 +98,7 @@ func NewInvoiceApplicationService(
|
||||
userInvoiceInfoService: userInvoiceInfoService,
|
||||
storageService: storageService,
|
||||
logger: logger,
|
||||
wechatWorkServer: wechatSvc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +185,7 @@ func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID
|
||||
}
|
||||
|
||||
// 10. 构建响应DTO
|
||||
return &dto.InvoiceApplicationResponse{
|
||||
resp := &dto.InvoiceApplicationResponse{
|
||||
ID: application.ID,
|
||||
UserID: application.UserID,
|
||||
InvoiceType: application.InvoiceType,
|
||||
@@ -183,7 +193,33 @@ func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID
|
||||
Status: application.Status,
|
||||
InvoiceInfo: invoiceInfo,
|
||||
CreatedAt: application.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 11. 企业微信通知(忽略发送错误),只使用企业名称和联系电话
|
||||
if s.wechatWorkServer != nil {
|
||||
companyName := userWithEnterprise.EnterpriseInfo.CompanyName
|
||||
phone := user.Phone
|
||||
if userWithEnterprise.EnterpriseInfo.LegalPersonPhone != "" {
|
||||
phone = userWithEnterprise.EnterpriseInfo.LegalPersonPhone
|
||||
}
|
||||
|
||||
content := fmt.Sprintf(
|
||||
"### 【天远API】用户申请开发票\n"+
|
||||
"> 企业名称:%s\n"+
|
||||
"> 联系手机:%s\n"+
|
||||
"> 申请开票金额:%s 元\n"+
|
||||
"> 发票类型:%s\n"+
|
||||
"> 申请时间:%s\n",
|
||||
companyName,
|
||||
phone,
|
||||
application.Amount.String(),
|
||||
string(application.InvoiceType),
|
||||
time.Now().Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
_ = s.wechatWorkServer.SendMarkdownMessage(ctx, content)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetUserInvoiceInfo 获取用户发票信息
|
||||
|
||||
1305
internal/application/product/component_report_order_service.go
Normal file
1305
internal/application/product/component_report_order_service.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,17 +2,22 @@ package commands
|
||||
|
||||
// CreateProductCommand 创建产品命令
|
||||
type CreateProductCommand struct {
|
||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
||||
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
||||
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
|
||||
SubCategoryID *string `json:"sub_category_id" binding:"omitempty,uuid" comment:"二级分类ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// UI组件相关字段
|
||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格(组合包使用)"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||
@@ -22,18 +27,23 @@ type CreateProductCommand struct {
|
||||
|
||||
// UpdateProductCommand 更新产品命令
|
||||
type UpdateProductCommand struct {
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
|
||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
||||
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
|
||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
||||
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
|
||||
SubCategoryID *string `json:"sub_category_id" binding:"omitempty,uuid" comment:"二级分类ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// UI组件相关字段
|
||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格(组合包使用)"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package commands
|
||||
|
||||
// CreateSubCategoryCommand 创建二级分类命令
|
||||
type CreateSubCategoryCommand struct {
|
||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"二级分类名称"`
|
||||
Code string `json:"code" binding:"required,min=2,max=50" comment:"二级分类编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=500" comment:"二级分类描述"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
|
||||
Sort int `json:"sort" binding:"min=0" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
}
|
||||
|
||||
// UpdateSubCategoryCommand 更新二级分类命令
|
||||
type UpdateSubCategoryCommand struct {
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"二级分类ID"`
|
||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"二级分类名称"`
|
||||
Code string `json:"code" binding:"required,min=2,max=50" comment:"二级分类编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=500" comment:"二级分类描述"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
|
||||
Sort int `json:"sort" binding:"min=0" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
}
|
||||
|
||||
// DeleteSubCategoryCommand 删除二级分类命令
|
||||
type DeleteSubCategoryCommand struct {
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"二级分类ID"`
|
||||
}
|
||||
@@ -8,15 +8,16 @@ type CreateSubscriptionCommand struct {
|
||||
|
||||
// UpdateSubscriptionPriceCommand 更新订阅价格命令
|
||||
type UpdateSubscriptionPriceCommand struct {
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"订阅ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"订阅价格"`
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"订阅ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"订阅价格"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件价格(组合包使用)"`
|
||||
}
|
||||
|
||||
// BatchUpdateSubscriptionPricesCommand 批量更新订阅价格命令
|
||||
type BatchUpdateSubscriptionPricesCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户ID"`
|
||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户ID"`
|
||||
AdjustmentType string `json:"adjustment_type" binding:"required,oneof=discount cost_multiple" comment:"调整方式(discount:按售价折扣,cost_multiple:按成本价倍数)"`
|
||||
Discount float64 `json:"discount,omitempty" binding:"omitempty,min=0.1,max=10" comment:"折扣比例(0.1-10折)"`
|
||||
CostMultiple float64 `json:"cost_multiple,omitempty" binding:"omitempty,min=0.1" comment:"成本价倍数"`
|
||||
Scope string `json:"scope" binding:"required,oneof=undiscounted all" comment:"改价范围(undiscounted:仅未打折,all:所有)"`
|
||||
}
|
||||
Discount float64 `json:"discount,omitempty" binding:"omitempty,min=0.1,max=10" comment:"折扣比例(0.1-10折)"`
|
||||
CostMultiple float64 `json:"cost_multiple,omitempty" binding:"omitempty,min=0.1" comment:"成本价倍数"`
|
||||
Scope string `json:"scope" binding:"required,oneof=undiscounted all" comment:"改价范围(undiscounted:仅未打折,all:所有)"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package queries
|
||||
|
||||
// GetSubCategoryQuery 获取二级分类查询
|
||||
type GetSubCategoryQuery struct {
|
||||
ID string `json:"id" form:"id" binding:"omitempty,uuid" comment:"二级分类ID"`
|
||||
}
|
||||
|
||||
// ListSubCategoriesQuery 获取二级分类列表查询
|
||||
type ListSubCategoriesQuery struct {
|
||||
Page int `json:"page" form:"page" binding:"min=1" comment:"页码"`
|
||||
PageSize int `json:"page_size" form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||
CategoryID string `json:"category_id" form:"category_id" binding:"omitempty,uuid" comment:"一级分类ID"`
|
||||
IsEnabled *bool `json:"is_enabled" form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `json:"is_visible" form:"is_visible" comment:"是否展示"`
|
||||
SortBy string `json:"sort_by" form:"sort_by" comment:"排序字段"`
|
||||
SortOrder string `json:"sort_order" form:"sort_order" comment:"排序方向"`
|
||||
}
|
||||
@@ -4,23 +4,23 @@ import "time"
|
||||
|
||||
// CategoryInfoResponse 分类详情响应
|
||||
type CategoryInfoResponse struct {
|
||||
ID string `json:"id" comment:"分类ID"`
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
Description string `json:"description" comment:"分类描述"`
|
||||
Sort int `json:"sort" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
|
||||
ID string `json:"id" comment:"分类ID"`
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
Description string `json:"description" comment:"分类描述"`
|
||||
Sort int `json:"sort" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// CategoryListResponse 分类列表响应
|
||||
type CategoryListResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []CategoryInfoResponse `json:"items" comment:"分类列表"`
|
||||
}
|
||||
|
||||
@@ -29,4 +29,38 @@ type CategorySimpleResponse struct {
|
||||
ID string `json:"id" comment:"分类ID"`
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
}
|
||||
}
|
||||
|
||||
// SubCategoryInfoResponse 二级分类详情响应
|
||||
type SubCategoryInfoResponse struct {
|
||||
ID string `json:"id" comment:"二级分类ID"`
|
||||
Name string `json:"name" comment:"二级分类名称"`
|
||||
Code string `json:"code" comment:"二级分类编号"`
|
||||
Description string `json:"description" comment:"二级分类描述"`
|
||||
CategoryID string `json:"category_id" comment:"一级分类ID"`
|
||||
Sort int `json:"sort" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
|
||||
// 关联信息
|
||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// SubCategoryListResponse 二级分类列表响应
|
||||
type SubCategoryListResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []SubCategoryInfoResponse `json:"items" comment:"二级分类列表"`
|
||||
}
|
||||
|
||||
// SubCategorySimpleResponse 二级分类简单信息响应
|
||||
type SubCategorySimpleResponse struct {
|
||||
ID string `json:"id" comment:"二级分类ID"`
|
||||
Name string `json:"name" comment:"二级分类名称"`
|
||||
Code string `json:"code" comment:"二级分类编号"`
|
||||
CategoryID string `json:"category_id" comment:"一级分类ID"`
|
||||
}
|
||||
|
||||
@@ -15,17 +15,21 @@ type PackageItemResponse struct {
|
||||
|
||||
// ProductInfoResponse 产品详情响应
|
||||
type ProductInfoResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"一级分类ID"`
|
||||
SubCategoryID *string `json:"sub_category_id,omitempty" comment:"二级分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||
// UI组件相关字段
|
||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格(组合包使用)"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
@@ -33,7 +37,8 @@ type ProductInfoResponse struct {
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
|
||||
// 关联信息
|
||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
|
||||
SubCategory *SubCategoryInfoResponse `json:"sub_category,omitempty" comment:"二级分类信息"`
|
||||
|
||||
// 组合包信息
|
||||
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
|
||||
@@ -60,21 +65,22 @@ type ProductSearchResponse struct {
|
||||
|
||||
// ProductSimpleResponse 产品简单信息响应
|
||||
type ProductSimpleResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||
}
|
||||
|
||||
// ProductSimpleAdminResponse 管理员产品简单信息响应(包含成本价)
|
||||
type ProductSimpleAdminResponse struct {
|
||||
ProductSimpleResponse
|
||||
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件价格(组合包使用)"`
|
||||
}
|
||||
|
||||
// ProductStatsResponse 产品统计响应
|
||||
@@ -87,19 +93,24 @@ type ProductStatsResponse struct {
|
||||
|
||||
// ProductAdminInfoResponse 管理员产品详情响应
|
||||
type ProductAdminInfoResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||
Remark string `json:"remark" comment:"备注"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"一级分类ID"`
|
||||
SubCategoryID *string `json:"sub_category_id,omitempty" comment:"二级分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||
Remark string `json:"remark" comment:"备注"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// UI组件相关字段
|
||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格(组合包使用)"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
@@ -107,7 +118,8 @@ type ProductAdminInfoResponse struct {
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
|
||||
// 关联信息
|
||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
|
||||
SubCategory *SubCategoryInfoResponse `json:"sub_category,omitempty" comment:"二级分类信息"`
|
||||
|
||||
// 组合包信息
|
||||
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
|
||||
|
||||
@@ -13,47 +13,48 @@ type UserSimpleResponse struct {
|
||||
|
||||
// SubscriptionInfoResponse 订阅详情响应
|
||||
type SubscriptionInfoResponse struct {
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
UserID string `json:"user_id" comment:"用户ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
Price float64 `json:"price" comment:"订阅价格"`
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
UserID string `json:"user_id" comment:"用户ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
Price float64 `json:"price" comment:"订阅价格"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件价格(组合包使用)"`
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
|
||||
// 关联信息
|
||||
User *UserSimpleResponse `json:"user,omitempty" comment:"用户信息"`
|
||||
Product *ProductSimpleResponse `json:"product,omitempty" comment:"产品信息"`
|
||||
// 管理员端使用,包含成本价的产品信息
|
||||
ProductAdmin *ProductSimpleAdminResponse `json:"product_admin,omitempty" comment:"产品信息(管理员端,包含成本价)"`
|
||||
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// SubscriptionListResponse 订阅列表响应
|
||||
type SubscriptionListResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []SubscriptionInfoResponse `json:"items" comment:"订阅列表"`
|
||||
}
|
||||
|
||||
// SubscriptionSimpleResponse 订阅简单信息响应
|
||||
type SubscriptionSimpleResponse struct {
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
Price float64 `json:"price" comment:"订阅价格"`
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
Price float64 `json:"price" comment:"订阅价格"`
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
}
|
||||
|
||||
// SubscriptionUsageResponse 订阅使用情况响应
|
||||
type SubscriptionUsageResponse struct {
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
}
|
||||
|
||||
// SubscriptionStatsResponse 订阅统计响应
|
||||
type SubscriptionStatsResponse struct {
|
||||
TotalSubscriptions int64 `json:"total_subscriptions" comment:"订阅总数"`
|
||||
TotalRevenue float64 `json:"total_revenue" comment:"总收入"`
|
||||
}
|
||||
TotalSubscriptions int64 `json:"total_subscriptions" comment:"订阅总数"`
|
||||
TotalRevenue float64 `json:"total_revenue" comment:"总收入"`
|
||||
}
|
||||
|
||||
@@ -50,24 +50,27 @@ func NewProductApplicationService(
|
||||
}
|
||||
|
||||
// CreateProduct 创建产品
|
||||
// 业务流程<EFBFBD>?. 构建产品实体 2. 创建产品
|
||||
// 业务流程:1. 构建产品实体 2. 创建产品
|
||||
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
|
||||
// 1. 构建产品实体
|
||||
product := &entities.Product{
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
Content: cmd.Content,
|
||||
CategoryID: cmd.CategoryID,
|
||||
Price: decimal.NewFromFloat(cmd.Price),
|
||||
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
||||
Remark: cmd.Remark,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
IsPackage: cmd.IsPackage,
|
||||
SEOTitle: cmd.SEOTitle,
|
||||
SEODescription: cmd.SEODescription,
|
||||
SEOKeywords: cmd.SEOKeywords,
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
Content: cmd.Content,
|
||||
CategoryID: cmd.CategoryID,
|
||||
SubCategoryID: cmd.SubCategoryID,
|
||||
Price: decimal.NewFromFloat(cmd.Price),
|
||||
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
||||
Remark: cmd.Remark,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
IsPackage: cmd.IsPackage,
|
||||
SellUIComponent: cmd.SellUIComponent,
|
||||
UIComponentPrice: decimal.NewFromFloat(cmd.UIComponentPrice),
|
||||
SEOTitle: cmd.SEOTitle,
|
||||
SEODescription: cmd.SEODescription,
|
||||
SEOKeywords: cmd.SEOKeywords,
|
||||
}
|
||||
|
||||
// 2. 创建产品
|
||||
@@ -95,12 +98,15 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
|
||||
existingProduct.Description = cmd.Description
|
||||
existingProduct.Content = cmd.Content
|
||||
existingProduct.CategoryID = cmd.CategoryID
|
||||
existingProduct.SubCategoryID = cmd.SubCategoryID
|
||||
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
|
||||
existingProduct.CostPrice = decimal.NewFromFloat(cmd.CostPrice)
|
||||
existingProduct.Remark = cmd.Remark
|
||||
existingProduct.IsEnabled = cmd.IsEnabled
|
||||
existingProduct.IsVisible = cmd.IsVisible
|
||||
existingProduct.IsPackage = cmd.IsPackage
|
||||
existingProduct.SellUIComponent = cmd.SellUIComponent
|
||||
existingProduct.UIComponentPrice = decimal.NewFromFloat(cmd.UIComponentPrice)
|
||||
existingProduct.SEOTitle = cmd.SEOTitle
|
||||
existingProduct.SEODescription = cmd.SEODescription
|
||||
existingProduct.SEOKeywords = cmd.SEOKeywords
|
||||
@@ -486,28 +492,36 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Contex
|
||||
// convertToProductInfoResponse 转换为产品信息响应
|
||||
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
||||
response := &responses.ProductInfoResponse{
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsPackage: product.IsPackage,
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
SubCategoryID: product.SubCategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsPackage: product.IsPackage,
|
||||
SellUIComponent: product.SellUIComponent,
|
||||
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加分类信息
|
||||
// 添加一级分类信息
|
||||
if product.Category != nil {
|
||||
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
||||
}
|
||||
|
||||
// 添加二级分类信息
|
||||
if product.SubCategory != nil {
|
||||
response.SubCategory = s.convertToSubCategoryInfoResponse(product.SubCategory)
|
||||
}
|
||||
|
||||
// 转换组合包项目信息
|
||||
if product.IsPackage && len(product.PackageItems) > 0 {
|
||||
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
|
||||
@@ -530,31 +544,39 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
|
||||
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
|
||||
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
|
||||
response := &responses.ProductAdminInfoResponse{
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
Remark: product.Remark,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||
IsPackage: product.IsPackage,
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
SubCategoryID: product.SubCategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
Remark: product.Remark,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||
IsPackage: product.IsPackage,
|
||||
SellUIComponent: product.SellUIComponent,
|
||||
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加分类信息
|
||||
// 添加一级分类信息
|
||||
if product.Category != nil {
|
||||
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
||||
}
|
||||
|
||||
// 添加二级分类信息
|
||||
if product.SubCategory != nil {
|
||||
response.SubCategory = s.convertToSubCategoryInfoResponse(product.SubCategory)
|
||||
}
|
||||
|
||||
// 转换组合包项目信息
|
||||
if product.IsPackage && len(product.PackageItems) > 0 {
|
||||
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
|
||||
@@ -586,6 +608,28 @@ func (s *ProductApplicationServiceImpl) convertToCategoryInfoResponse(category *
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ProductApplicationServiceImpl) convertToSubCategoryInfoResponse(subCategory *entities.ProductSubCategory) *responses.SubCategoryInfoResponse {
|
||||
response := &responses.SubCategoryInfoResponse{
|
||||
ID: subCategory.ID,
|
||||
Name: subCategory.Name,
|
||||
Code: subCategory.Code,
|
||||
Description: subCategory.Description,
|
||||
CategoryID: subCategory.CategoryID,
|
||||
Sort: subCategory.Sort,
|
||||
IsEnabled: subCategory.IsEnabled,
|
||||
IsVisible: subCategory.IsVisible,
|
||||
CreatedAt: subCategory.CreatedAt,
|
||||
UpdatedAt: subCategory.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加一级分类信息
|
||||
if subCategory.Category != nil {
|
||||
response.Category = s.convertToCategoryInfoResponse(subCategory.Category)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// GetProductApiConfig 获取产品API配置
|
||||
func (s *ProductApplicationServiceImpl) GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error) {
|
||||
return s.productApiConfigAppService.GetProductApiConfig(ctx, productID)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
"tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
)
|
||||
|
||||
// SubCategoryApplicationService 二级分类应用服务接口
|
||||
type SubCategoryApplicationService interface {
|
||||
// 二级分类管理
|
||||
CreateSubCategory(ctx context.Context, cmd *commands.CreateSubCategoryCommand) error
|
||||
UpdateSubCategory(ctx context.Context, cmd *commands.UpdateSubCategoryCommand) error
|
||||
DeleteSubCategory(ctx context.Context, cmd *commands.DeleteSubCategoryCommand) error
|
||||
|
||||
GetSubCategoryByID(ctx context.Context, query *queries.GetSubCategoryQuery) (*responses.SubCategoryInfoResponse, error)
|
||||
ListSubCategories(ctx context.Context, query *queries.ListSubCategoriesQuery) (*responses.SubCategoryListResponse, error)
|
||||
ListSubCategoriesByCategoryID(ctx context.Context, categoryID string) ([]*responses.SubCategorySimpleResponse, error)
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
"tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// SubCategoryApplicationServiceImpl 二级分类应用服务实现
|
||||
type SubCategoryApplicationServiceImpl struct {
|
||||
categoryRepo repositories.ProductCategoryRepository
|
||||
subCategoryRepo repositories.ProductSubCategoryRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewSubCategoryApplicationService 创建二级分类应用服务
|
||||
func NewSubCategoryApplicationService(
|
||||
categoryRepo repositories.ProductCategoryRepository,
|
||||
subCategoryRepo repositories.ProductSubCategoryRepository,
|
||||
logger *zap.Logger,
|
||||
) SubCategoryApplicationService {
|
||||
return &SubCategoryApplicationServiceImpl{
|
||||
categoryRepo: categoryRepo,
|
||||
subCategoryRepo: subCategoryRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSubCategory 创建二级分类
|
||||
func (s *SubCategoryApplicationServiceImpl) CreateSubCategory(ctx context.Context, cmd *commands.CreateSubCategoryCommand) error {
|
||||
// 1. 参数验证
|
||||
if err := s.validateCreateSubCategory(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 验证一级分类是否存在
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.CategoryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("一级分类不存在: %w", err)
|
||||
}
|
||||
if !category.IsValid() {
|
||||
return errors.New("一级分类已禁用或删除")
|
||||
}
|
||||
|
||||
// 3. 验证二级分类编号唯一性
|
||||
if err := s.validateSubCategoryCode(cmd.Code, "", cmd.CategoryID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 创建二级分类实体
|
||||
subCategory := &entities.ProductSubCategory{
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
CategoryID: cmd.CategoryID,
|
||||
Sort: cmd.Sort,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
}
|
||||
|
||||
// 5. 保存到仓储
|
||||
createdSubCategory, err := s.subCategoryRepo.Create(ctx, *subCategory)
|
||||
if err != nil {
|
||||
s.logger.Error("创建二级分类失败", zap.Error(err), zap.String("code", cmd.Code))
|
||||
return fmt.Errorf("创建二级分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("创建二级分类成功", zap.String("id", createdSubCategory.ID), zap.String("code", cmd.Code))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSubCategory 更新二级分类
|
||||
func (s *SubCategoryApplicationServiceImpl) UpdateSubCategory(ctx context.Context, cmd *commands.UpdateSubCategoryCommand) error {
|
||||
// 1. 参数验证
|
||||
if err := s.validateUpdateSubCategory(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 获取现有二级分类
|
||||
existingSubCategory, err := s.subCategoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("二级分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 3. 验证一级分类是否存在
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.CategoryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("一级分类不存在: %w", err)
|
||||
}
|
||||
if !category.IsValid() {
|
||||
return errors.New("一级分类已禁用或删除")
|
||||
}
|
||||
|
||||
// 4. 验证二级分类编号唯一性(排除当前分类)
|
||||
if err := s.validateSubCategoryCode(cmd.Code, cmd.ID, cmd.CategoryID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. 更新二级分类信息
|
||||
existingSubCategory.Name = cmd.Name
|
||||
existingSubCategory.Code = cmd.Code
|
||||
existingSubCategory.Description = cmd.Description
|
||||
existingSubCategory.CategoryID = cmd.CategoryID
|
||||
existingSubCategory.Sort = cmd.Sort
|
||||
existingSubCategory.IsEnabled = cmd.IsEnabled
|
||||
existingSubCategory.IsVisible = cmd.IsVisible
|
||||
|
||||
// 6. 保存到仓储
|
||||
if err := s.subCategoryRepo.Update(ctx, *existingSubCategory); err != nil {
|
||||
s.logger.Error("更新二级分类失败", zap.Error(err), zap.String("id", cmd.ID))
|
||||
return fmt.Errorf("更新二级分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新二级分类成功", zap.String("id", cmd.ID), zap.String("code", cmd.Code))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSubCategory 删除二级分类
|
||||
func (s *SubCategoryApplicationServiceImpl) DeleteSubCategory(ctx context.Context, cmd *commands.DeleteSubCategoryCommand) error {
|
||||
// 1. 检查二级分类是否存在
|
||||
existingSubCategory, err := s.subCategoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("二级分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 删除二级分类
|
||||
if err := s.subCategoryRepo.Delete(ctx, cmd.ID); err != nil {
|
||||
s.logger.Error("删除二级分类失败", zap.Error(err), zap.String("id", cmd.ID))
|
||||
return fmt.Errorf("删除二级分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("删除二级分类成功", zap.String("id", cmd.ID), zap.String("code", existingSubCategory.Code))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubCategoryByID 根据ID获取二级分类
|
||||
func (s *SubCategoryApplicationServiceImpl) GetSubCategoryByID(ctx context.Context, query *queries.GetSubCategoryQuery) (*responses.SubCategoryInfoResponse, error) {
|
||||
subCategory, err := s.subCategoryRepo.GetByID(ctx, query.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("二级分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 加载一级分类信息
|
||||
if subCategory.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(ctx, subCategory.CategoryID)
|
||||
if err == nil {
|
||||
subCategory.Category = &category
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
response := s.convertToSubCategoryInfoResponse(subCategory)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ListSubCategories 获取二级分类列表
|
||||
func (s *SubCategoryApplicationServiceImpl) ListSubCategories(ctx context.Context, query *queries.ListSubCategoriesQuery) (*responses.SubCategoryListResponse, error) {
|
||||
// 构建查询条件
|
||||
categoryID := query.CategoryID
|
||||
isEnabled := query.IsEnabled
|
||||
isVisible := query.IsVisible
|
||||
|
||||
var subCategories []*entities.ProductSubCategory
|
||||
var err error
|
||||
|
||||
// 根据条件查询
|
||||
if categoryID != "" {
|
||||
// 按一级分类查询
|
||||
subCategories, err = s.subCategoryRepo.FindByCategoryID(ctx, categoryID)
|
||||
} else {
|
||||
// 查询所有二级分类
|
||||
subCategories, err = s.subCategoryRepo.List(ctx)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("获取二级分类列表失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取二级分类列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 过滤状态
|
||||
filteredSubCategories := make([]*entities.ProductSubCategory, 0)
|
||||
for _, subCategory := range subCategories {
|
||||
if isEnabled != nil && *isEnabled != subCategory.IsEnabled {
|
||||
continue
|
||||
}
|
||||
if isVisible != nil && *isVisible != subCategory.IsVisible {
|
||||
continue
|
||||
}
|
||||
filteredSubCategories = append(filteredSubCategories, subCategory)
|
||||
}
|
||||
|
||||
// 加载一级分类信息
|
||||
for _, subCategory := range filteredSubCategories {
|
||||
if subCategory.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(ctx, subCategory.CategoryID)
|
||||
if err == nil {
|
||||
subCategory.Category = &category
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]responses.SubCategoryInfoResponse, len(filteredSubCategories))
|
||||
for i, subCategory := range filteredSubCategories {
|
||||
items[i] = *s.convertToSubCategoryInfoResponse(subCategory)
|
||||
}
|
||||
|
||||
return &responses.SubCategoryListResponse{
|
||||
Total: int64(len(items)),
|
||||
Page: query.Page,
|
||||
Size: query.PageSize,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListSubCategoriesByCategoryID 根据一级分类ID获取二级分类列表
|
||||
func (s *SubCategoryApplicationServiceImpl) ListSubCategoriesByCategoryID(ctx context.Context, categoryID string) ([]*responses.SubCategorySimpleResponse, error) {
|
||||
subCategories, err := s.subCategoryRepo.FindByCategoryID(ctx, categoryID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取二级分类列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.SubCategorySimpleResponse, len(subCategories))
|
||||
for i, subCategory := range subCategories {
|
||||
items[i] = &responses.SubCategorySimpleResponse{
|
||||
ID: subCategory.ID,
|
||||
Name: subCategory.Name,
|
||||
Code: subCategory.Code,
|
||||
CategoryID: subCategory.CategoryID,
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// convertToSubCategoryInfoResponse 转换为二级分类信息响应
|
||||
func (s *SubCategoryApplicationServiceImpl) convertToSubCategoryInfoResponse(subCategory *entities.ProductSubCategory) *responses.SubCategoryInfoResponse {
|
||||
response := &responses.SubCategoryInfoResponse{
|
||||
ID: subCategory.ID,
|
||||
Name: subCategory.Name,
|
||||
Code: subCategory.Code,
|
||||
Description: subCategory.Description,
|
||||
CategoryID: subCategory.CategoryID,
|
||||
Sort: subCategory.Sort,
|
||||
IsEnabled: subCategory.IsEnabled,
|
||||
IsVisible: subCategory.IsVisible,
|
||||
CreatedAt: subCategory.CreatedAt,
|
||||
UpdatedAt: subCategory.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加一级分类信息
|
||||
if subCategory.Category != nil {
|
||||
response.Category = &responses.CategoryInfoResponse{
|
||||
ID: subCategory.Category.ID,
|
||||
Name: subCategory.Category.Name,
|
||||
Description: subCategory.Category.Description,
|
||||
Sort: subCategory.Category.Sort,
|
||||
IsEnabled: subCategory.Category.IsEnabled,
|
||||
IsVisible: subCategory.Category.IsVisible,
|
||||
CreatedAt: subCategory.Category.CreatedAt,
|
||||
UpdatedAt: subCategory.Category.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// validateCreateSubCategory 验证创建二级分类参数
|
||||
func (s *SubCategoryApplicationServiceImpl) validateCreateSubCategory(cmd *commands.CreateSubCategoryCommand) error {
|
||||
if cmd.Name == "" {
|
||||
return errors.New("二级分类名称不能为空")
|
||||
}
|
||||
if cmd.Code == "" {
|
||||
return errors.New("二级分类编号不能为空")
|
||||
}
|
||||
if cmd.CategoryID == "" {
|
||||
return errors.New("一级分类ID不能为空")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateUpdateSubCategory 验证更新二级分类参数
|
||||
func (s *SubCategoryApplicationServiceImpl) validateUpdateSubCategory(cmd *commands.UpdateSubCategoryCommand) error {
|
||||
if cmd.ID == "" {
|
||||
return errors.New("二级分类ID不能为空")
|
||||
}
|
||||
if cmd.Name == "" {
|
||||
return errors.New("二级分类名称不能为空")
|
||||
}
|
||||
if cmd.Code == "" {
|
||||
return errors.New("二级分类编号不能为空")
|
||||
}
|
||||
if cmd.CategoryID == "" {
|
||||
return errors.New("一级分类ID不能为空")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSubCategoryCode 验证二级分类编号唯一性
|
||||
func (s *SubCategoryApplicationServiceImpl) validateSubCategoryCode(code, excludeID, categoryID string) error {
|
||||
if code == "" {
|
||||
return errors.New("二级分类编号不能为空")
|
||||
}
|
||||
|
||||
existingSubCategory, err := s.subCategoryRepo.FindByCode(context.Background(), code)
|
||||
if err == nil && existingSubCategory != nil && existingSubCategory.ID != excludeID {
|
||||
// 如果指定了分类ID,检查是否在同一分类下
|
||||
if categoryID == "" || existingSubCategory.CategoryID == categoryID {
|
||||
return errors.New("二级分类编号已存在")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func NewSubscriptionApplicationService(
|
||||
// UpdateSubscriptionPrice 更新订阅价格
|
||||
// 业务流程:1. 获取订阅 2. 更新价格 3. 保存订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error {
|
||||
return s.productSubscriptionService.UpdateSubscriptionPrice(ctx, cmd.ID, cmd.Price)
|
||||
return s.productSubscriptionService.UpdateSubscriptionPriceWithUIComponent(ctx, cmd.ID, cmd.Price, cmd.UIComponentPrice)
|
||||
}
|
||||
|
||||
// BatchUpdateSubscriptionPrices 一键改价
|
||||
@@ -377,16 +377,23 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(s
|
||||
productResponse = s.convertToProductSimpleResponse(subscription.Product)
|
||||
}
|
||||
|
||||
// 获取UI组件价格,如果订阅中没有设置,则从产品中获取
|
||||
uiComponentPrice := subscription.UIComponentPrice.InexactFloat64()
|
||||
if uiComponentPrice == 0 && subscription.Product != nil && (subscription.Product.IsPackage) {
|
||||
uiComponentPrice = subscription.Product.UIComponentPrice.InexactFloat64()
|
||||
}
|
||||
|
||||
return &responses.SubscriptionInfoResponse{
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price.InexactFloat64(),
|
||||
User: userInfo,
|
||||
Product: productResponse,
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price.InexactFloat64(),
|
||||
UIComponentPrice: uiComponentPrice,
|
||||
User: userInfo,
|
||||
Product: productResponse,
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,16 +440,23 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponseFo
|
||||
productAdminResponse = s.convertToProductSimpleAdminResponse(subscription.Product)
|
||||
}
|
||||
|
||||
// 获取UI组件价格,如果订阅中没有设置,则从产品中获取
|
||||
uiComponentPrice := subscription.UIComponentPrice.InexactFloat64()
|
||||
if uiComponentPrice == 0 && subscription.Product != nil && (subscription.Product.IsPackage) {
|
||||
uiComponentPrice = subscription.Product.UIComponentPrice.InexactFloat64()
|
||||
}
|
||||
|
||||
return &responses.SubscriptionInfoResponse{
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price.InexactFloat64(),
|
||||
User: userInfo,
|
||||
ProductAdmin: productAdminResponse,
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price.InexactFloat64(),
|
||||
UIComponentPrice: uiComponentPrice,
|
||||
User: userInfo,
|
||||
ProductAdmin: productAdminResponse,
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,7 +478,8 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleAdminResponse
|
||||
Category: categoryResponse,
|
||||
IsPackage: product.IsPackage,
|
||||
},
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ import (
|
||||
"mime/multipart"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// UIComponentApplicationService UI组件应用服务接口
|
||||
@@ -93,6 +95,7 @@ type UIComponentApplicationServiceImpl struct {
|
||||
productUIComponentRepo repositories.ProductUIComponentRepository
|
||||
fileStorageService FileStorageService
|
||||
fileService UIComponentFileService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// FileStorageService 文件存储服务接口
|
||||
@@ -108,12 +111,14 @@ func NewUIComponentApplicationService(
|
||||
productUIComponentRepo repositories.ProductUIComponentRepository,
|
||||
fileStorageService FileStorageService,
|
||||
fileService UIComponentFileService,
|
||||
logger *zap.Logger,
|
||||
) UIComponentApplicationService {
|
||||
return &UIComponentApplicationServiceImpl{
|
||||
uiComponentRepo: uiComponentRepo,
|
||||
productUIComponentRepo: productUIComponentRepo,
|
||||
fileStorageService: fileStorageService,
|
||||
fileService: fileService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,10 +187,14 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFile(ctx contex
|
||||
fileType := strings.ToLower(filepath.Ext(file.Filename))
|
||||
|
||||
// 更新组件信息
|
||||
folderPath := "resources/Pure Component/src/ui"
|
||||
folderPath := "resources/Pure_Component/src/ui"
|
||||
createdComponent.FolderPath = &folderPath
|
||||
createdComponent.FileType = &fileType
|
||||
|
||||
// 记录文件上传时间
|
||||
now := time.Now()
|
||||
createdComponent.FileUploadTime = &now
|
||||
|
||||
// 仅对ZIP文件设置已解压标记
|
||||
if fileType == ".zip" {
|
||||
createdComponent.IsExtracted = true
|
||||
@@ -255,9 +264,13 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFiles(ctx conte
|
||||
}
|
||||
|
||||
// 更新组件信息
|
||||
folderPath := "resources/Pure Component/src/ui"
|
||||
folderPath := "resources/Pure_Component/src/ui"
|
||||
createdComponent.FolderPath = &folderPath
|
||||
|
||||
// 记录文件上传时间
|
||||
now := time.Now()
|
||||
createdComponent.FileUploadTime = &now
|
||||
|
||||
// 检查是否有ZIP文件
|
||||
hasZipFile := false
|
||||
for _, fileHeader := range files {
|
||||
@@ -363,9 +376,13 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFilesAndPaths(c
|
||||
}
|
||||
|
||||
// 更新组件信息
|
||||
folderPath := "resources/Pure Component/src/ui"
|
||||
folderPath := "resources/Pure_Component/src/ui"
|
||||
createdComponent.FolderPath = &folderPath
|
||||
|
||||
// 记录文件上传时间
|
||||
now := time.Now()
|
||||
createdComponent.FileUploadTime = &now
|
||||
|
||||
// 检查是否有ZIP文件
|
||||
hasZipFile := false
|
||||
for _, fileHeader := range files {
|
||||
@@ -442,20 +459,56 @@ func (s *UIComponentApplicationServiceImpl) UpdateUIComponent(ctx context.Contex
|
||||
|
||||
// DeleteUIComponent 删除UI组件
|
||||
func (s *UIComponentApplicationServiceImpl) DeleteUIComponent(ctx context.Context, id string) error {
|
||||
// 获取组件信息
|
||||
component, err := s.uiComponentRepo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
s.logger.Error("获取UI组件失败", zap.Error(err), zap.String("id", id))
|
||||
return fmt.Errorf("获取UI组件失败: %w", err)
|
||||
}
|
||||
if component == nil {
|
||||
s.logger.Warn("UI组件不存在", zap.String("id", id))
|
||||
return ErrComponentNotFound
|
||||
}
|
||||
|
||||
// 删除关联的文件
|
||||
if component.FilePath != nil {
|
||||
_ = s.fileStorageService.DeleteFile(ctx, *component.FilePath)
|
||||
// 记录组件信息
|
||||
s.logger.Info("开始删除UI组件",
|
||||
zap.String("id", id),
|
||||
zap.String("componentCode", component.ComponentCode),
|
||||
zap.String("componentName", component.ComponentName),
|
||||
zap.Bool("isExtracted", component.IsExtracted),
|
||||
zap.Any("filePath", component.FilePath),
|
||||
zap.Any("folderPath", component.FolderPath))
|
||||
|
||||
// 使用智能删除方法,根据组件编码和上传时间删除相关文件
|
||||
if err := s.fileService.DeleteFilesByComponentCode(component.ComponentCode, component.FileUploadTime); err != nil {
|
||||
// 记录错误但不阻止删除数据库记录
|
||||
s.logger.Error("删除组件文件失败",
|
||||
zap.Error(err),
|
||||
zap.String("componentCode", component.ComponentCode),
|
||||
zap.Any("fileUploadTime", component.FileUploadTime))
|
||||
}
|
||||
|
||||
return s.uiComponentRepo.Delete(ctx, id)
|
||||
// 删除关联的文件(FilePath指向的文件)
|
||||
if component.FilePath != nil {
|
||||
if err := s.fileStorageService.DeleteFile(ctx, *component.FilePath); err != nil {
|
||||
s.logger.Error("删除文件失败",
|
||||
zap.Error(err),
|
||||
zap.String("filePath", *component.FilePath))
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据库记录
|
||||
if err := s.uiComponentRepo.Delete(ctx, id); err != nil {
|
||||
s.logger.Error("删除UI组件数据库记录失败",
|
||||
zap.Error(err),
|
||||
zap.String("id", id))
|
||||
return fmt.Errorf("删除UI组件数据库记录失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("UI组件删除成功",
|
||||
zap.String("id", id),
|
||||
zap.String("componentCode", component.ComponentCode))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUIComponents 获取UI组件列表
|
||||
@@ -634,10 +687,14 @@ func (s *UIComponentApplicationServiceImpl) UploadAndExtractUIComponentFile(ctx
|
||||
fileType := strings.ToLower(filepath.Ext(file.Filename))
|
||||
|
||||
// 更新组件信息
|
||||
folderPath := "resources/Pure Component/src/ui"
|
||||
folderPath := "resources/Pure_Component/src/ui"
|
||||
component.FolderPath = &folderPath
|
||||
component.FileType = &fileType
|
||||
|
||||
// 记录文件上传时间
|
||||
now := time.Now()
|
||||
component.FileUploadTime = &now
|
||||
|
||||
// 仅对ZIP文件设置已解压标记
|
||||
if fileType == ".zip" {
|
||||
component.IsExtracted = true
|
||||
|
||||
@@ -32,6 +32,9 @@ type UIComponentFileService interface {
|
||||
|
||||
// 获取文件夹内容
|
||||
GetFolderContent(folderPath string) ([]FileInfo, error)
|
||||
|
||||
// 根据组件编码和上传时间智能删除组件相关文件
|
||||
DeleteFilesByComponentCode(componentCode string, uploadTime *time.Time) error
|
||||
}
|
||||
|
||||
// FileInfo 文件信息
|
||||
@@ -222,11 +225,34 @@ func (s *UIComponentFileServiceImpl) CreateFolderByCode(componentCode string) (s
|
||||
|
||||
// DeleteFolder 删除组件文件夹
|
||||
func (s *UIComponentFileServiceImpl) DeleteFolder(folderPath string) error {
|
||||
// 记录尝试删除的文件夹路径
|
||||
s.logger.Info("尝试删除文件夹", zap.String("folderPath", folderPath))
|
||||
|
||||
// 获取文件夹信息,用于调试
|
||||
if info, err := os.Stat(folderPath); err == nil {
|
||||
s.logger.Info("文件夹信息",
|
||||
zap.String("folderPath", folderPath),
|
||||
zap.Bool("isDir", info.IsDir()),
|
||||
zap.Int64("size", info.Size()),
|
||||
zap.Time("modTime", info.ModTime()))
|
||||
} else {
|
||||
s.logger.Error("获取文件夹信息失败",
|
||||
zap.Error(err),
|
||||
zap.String("folderPath", folderPath))
|
||||
}
|
||||
|
||||
// 检查文件夹是否存在
|
||||
if !s.FolderExists(folderPath) {
|
||||
s.logger.Info("文件夹不存在", zap.String("folderPath", folderPath))
|
||||
return nil // 文件夹不存在,不视为错误
|
||||
}
|
||||
|
||||
// 尝试删除文件夹
|
||||
s.logger.Info("开始删除文件夹", zap.String("folderPath", folderPath))
|
||||
if err := os.RemoveAll(folderPath); err != nil {
|
||||
s.logger.Error("删除文件夹失败",
|
||||
zap.Error(err),
|
||||
zap.String("folderPath", folderPath))
|
||||
return fmt.Errorf("删除文件夹失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -339,3 +365,95 @@ func (s *UIComponentFileServiceImpl) extractZipFile(zipPath, destPath string) er
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteFilesByComponentCode 根据组件编码和上传时间智能删除组件相关文件
|
||||
func (s *UIComponentFileServiceImpl) DeleteFilesByComponentCode(componentCode string, uploadTime *time.Time) error {
|
||||
// 记录基础路径和组件编码
|
||||
s.logger.Info("开始删除组件文件",
|
||||
zap.String("basePath", s.basePath),
|
||||
zap.String("componentCode", componentCode),
|
||||
zap.Any("uploadTime", uploadTime))
|
||||
|
||||
// 1. 查找名为组件编码的文件夹
|
||||
componentDir := filepath.Join(s.basePath, componentCode)
|
||||
s.logger.Info("检查组件文件夹", zap.String("componentDir", componentDir))
|
||||
|
||||
if s.FolderExists(componentDir) {
|
||||
s.logger.Info("找到组件文件夹,开始删除", zap.String("componentDir", componentDir))
|
||||
if err := s.DeleteFolder(componentDir); err != nil {
|
||||
s.logger.Error("删除组件文件夹失败",
|
||||
zap.Error(err),
|
||||
zap.String("componentCode", componentCode),
|
||||
zap.String("componentDir", componentDir))
|
||||
return fmt.Errorf("删除组件文件夹失败: %w", err)
|
||||
}
|
||||
s.logger.Info("成功删除组件文件夹", zap.String("componentCode", componentCode))
|
||||
return nil
|
||||
} else {
|
||||
s.logger.Info("组件文件夹不存在", zap.String("componentDir", componentDir))
|
||||
}
|
||||
|
||||
// 2. 查找文件名包含组件编码的文件
|
||||
pattern := filepath.Join(s.basePath, "*"+componentCode+"*")
|
||||
s.logger.Info("查找匹配文件", zap.String("pattern", pattern))
|
||||
|
||||
files, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
s.logger.Error("查找组件文件失败",
|
||||
zap.Error(err),
|
||||
zap.String("pattern", pattern))
|
||||
return fmt.Errorf("查找组件文件失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("找到匹配文件",
|
||||
zap.Strings("files", files),
|
||||
zap.Int("count", len(files)))
|
||||
|
||||
// 3. 如果没有上传时间,删除所有匹配的文件
|
||||
if uploadTime == nil {
|
||||
for _, file := range files {
|
||||
if err := os.Remove(file); err != nil {
|
||||
s.logger.Error("删除文件失败", zap.String("file", file), zap.Error(err))
|
||||
} else {
|
||||
s.logger.Info("成功删除文件", zap.String("file", file))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 4. 如果有上传时间,根据文件修改时间和上传时间的匹配度来删除文件
|
||||
var deletedFiles []string
|
||||
for _, file := range files {
|
||||
// 获取文件信息
|
||||
fileInfo, err := os.Stat(file)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取文件信息失败", zap.String("file", file), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 计算文件修改时间与上传时间的差异(以秒为单位)
|
||||
timeDiff := fileInfo.ModTime().Sub(*uploadTime).Seconds()
|
||||
|
||||
// 如果时间差在60秒内,认为是最匹配的文件
|
||||
if timeDiff < 60 && timeDiff > -60 {
|
||||
if err := os.Remove(file); err != nil {
|
||||
s.logger.Warn("删除文件失败", zap.String("file", file), zap.Error(err))
|
||||
} else {
|
||||
deletedFiles = append(deletedFiles, file)
|
||||
s.logger.Info("成功删除文件", zap.String("file", file),
|
||||
zap.Time("uploadTime", *uploadTime),
|
||||
zap.Time("fileModTime", fileInfo.ModTime()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到匹配的文件,记录警告但返回成功
|
||||
if len(deletedFiles) == 0 && len(files) > 0 {
|
||||
s.logger.Warn("没有找到匹配时间戳的文件",
|
||||
zap.String("componentCode", componentCode),
|
||||
zap.Time("uploadTime", *uploadTime),
|
||||
zap.Int("foundFiles", len(files)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2772,18 +2772,19 @@ func (s *StatisticsApplicationServiceImpl) AdminGetTodayCertifiedEnterprises(ctx
|
||||
var enterprises []map[string]interface{}
|
||||
for _, cert := range completedCertifications {
|
||||
// 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, cert.UserID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取企业信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取用户基本信息(仅需要用户名)
|
||||
user, err := s.userRepo.GetByID(ctx, cert.UserID)
|
||||
// 使用预加载方法一次性获取用户和企业信息
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, cert.UserID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取用户信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取企业信息
|
||||
enterpriseInfo := user.EnterpriseInfo
|
||||
if enterpriseInfo == nil {
|
||||
s.logger.Warn("用户没有企业信息", zap.String("user_id", cert.UserID))
|
||||
continue
|
||||
}
|
||||
|
||||
enterprise := map[string]interface{}{
|
||||
"id": cert.ID,
|
||||
|
||||
@@ -43,10 +43,20 @@ type ResetPasswordCommand struct {
|
||||
}
|
||||
|
||||
// SendCodeCommand 发送验证码命令
|
||||
// @Description 发送短信验证码请求参数
|
||||
// @Description 发送短信验证码请求参数。只接收编码后的data字段(使用自定义编码方案,非Base64)
|
||||
type SendCodeCommand struct {
|
||||
Phone string `json:"phone" binding:"required,phone" example:"13800138000"`
|
||||
Scene string `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind certification" example:"register"`
|
||||
// 编码后的数据(使用自定义编码方案的JSON字符串,包含所有参数:phone, scene, timestamp, nonce, signature)
|
||||
Data string `json:"data" binding:"required" example:"K8mN9vP2sL7kH3oB6yC1zA5uF0qE9tW..."` // 自定义编码后的数据
|
||||
|
||||
// 阿里云滑块验证码参数(直接接收,不参与编码)
|
||||
CaptchaVerifyParam string `json:"captchaVerifyParam,omitempty" example:"..."` // 滑块验证码验证参数
|
||||
|
||||
// 以下字段从data解码后填充,不直接接收
|
||||
Phone string `json:"-"` // 从data解码后获取
|
||||
Scene string `json:"-"` // 从data解码后获取
|
||||
Timestamp int64 `json:"-"` // 从data解码后获取
|
||||
Nonce string `json:"-"` // 从data解码后获取
|
||||
Signature string `json:"-"` // 从data解码后获取
|
||||
}
|
||||
|
||||
// UpdateProfileCommand 更新用户信息命令
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
|
||||
func (s *UserApplicationServiceImpl) SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error {
|
||||
// 1. 检查频率限制
|
||||
if err := s.smsCodeService.CheckRateLimit(ctx, cmd.Phone, entities.SMSScene(cmd.Scene)); err != nil {
|
||||
if err := s.smsCodeService.CheckRateLimit(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent)
|
||||
err := s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent, cmd.CaptchaVerifyParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -212,12 +212,6 @@ func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *comm
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SendSMS 发送短信验证码
|
||||
// 业务流程:1. 发送短信验证码
|
||||
func (s *UserApplicationServiceImpl) SendSMS(ctx context.Context, cmd *commands.SendCodeCommand) error {
|
||||
return s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), "", "")
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
// 业务流程:1. 修改用户密码
|
||||
func (s *UserApplicationServiceImpl) ChangePassword(ctx context.Context, cmd *commands.ChangePasswordCommand) error {
|
||||
@@ -345,19 +339,19 @@ func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queri
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
}
|
||||
|
||||
|
||||
// 获取企业合同信息
|
||||
contracts, err := s.contractService.FindByUserID(ctx, user.ID)
|
||||
if err == nil && len(contracts) > 0 {
|
||||
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
|
||||
for _, contract := range contracts {
|
||||
contractItems = append(contractItems, &responses.ContractInfoItem{
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ContractTypeName: contract.GetContractTypeName(),
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
})
|
||||
}
|
||||
item.EnterpriseInfo.Contracts = contractItems
|
||||
@@ -417,19 +411,19 @@ func (s *UserApplicationServiceImpl) GetUserDetail(ctx context.Context, userID s
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
}
|
||||
|
||||
|
||||
// 获取企业合同信息
|
||||
contracts, err := s.contractService.FindByUserID(ctx, user.ID)
|
||||
if err == nil && len(contracts) > 0 {
|
||||
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
|
||||
for _, contract := range contracts {
|
||||
contractItems = append(contractItems, &responses.ContractInfoItem{
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ContractTypeName: contract.GetContractTypeName(),
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
})
|
||||
}
|
||||
item.EnterpriseInfo.Contracts = contractItems
|
||||
|
||||
@@ -38,6 +38,10 @@ type Config struct {
|
||||
TianYanCha TianYanChaConfig `mapstructure:"tianyancha"`
|
||||
Alicloud AlicloudConfig `mapstructure:"alicloud"`
|
||||
Xingwei XingweiConfig `mapstructure:"xingwei"`
|
||||
Jiguang JiguangConfig `mapstructure:"jiguang"`
|
||||
Shumai ShumaiConfig `mapstructure:"shumai"`
|
||||
Shujubao ShujubaoConfig `mapstructure:"shujubao"`
|
||||
PDFGen PDFGenConfig `mapstructure:"pdfgen"`
|
||||
}
|
||||
|
||||
// ServerConfig HTTP服务器配置
|
||||
@@ -209,6 +213,14 @@ type SMSConfig struct {
|
||||
ExpireTime time.Duration `mapstructure:"expire_time"`
|
||||
RateLimit SMSRateLimit `mapstructure:"rate_limit"`
|
||||
MockEnabled bool `mapstructure:"mock_enabled"` // 是否启用模拟短信服务
|
||||
// 签名验证配置
|
||||
SignatureEnabled bool `mapstructure:"signature_enabled"` // 是否启用签名验证
|
||||
SignatureSecret string `mapstructure:"signature_secret"` // 签名密钥
|
||||
// 滑块验证码配置
|
||||
CaptchaEnabled bool `mapstructure:"captcha_enabled"` // 是否启用滑块验证码
|
||||
CaptchaSecret string `mapstructure:"captcha_secret"` // 阿里云验证码密钥
|
||||
CaptchaEndpoint string `mapstructure:"captcha_endpoint"` // 阿里云验证码服务Endpoint
|
||||
SceneID string `mapstructure:"scene_id"` // 阿里云验证码场景ID
|
||||
}
|
||||
|
||||
// SMSRateLimit 短信限流配置
|
||||
@@ -319,11 +331,13 @@ type SignConfig struct {
|
||||
|
||||
// WalletConfig 钱包配置
|
||||
type WalletConfig struct {
|
||||
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
|
||||
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
|
||||
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
|
||||
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
|
||||
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
|
||||
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
|
||||
RechargeBonusEnabled bool `mapstructure:"recharge_bonus_enabled"` // 是否启用充值赠送,关闭后仅展示商务洽谈提示
|
||||
ApiStoreRechargeTip string `mapstructure:"api_store_recharge_tip"` // API 商店充值提示文案(大额/批量需求联系商务)
|
||||
AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"`
|
||||
BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"`
|
||||
}
|
||||
|
||||
// BalanceAlertConfig 余额预警配置
|
||||
@@ -520,6 +534,108 @@ type XingweiLevelFileConfig struct {
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// JiguangConfig 极光配置
|
||||
type JiguangConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
AppID string `mapstructure:"app_id"`
|
||||
AppSecret string `mapstructure:"app_secret"`
|
||||
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac,默认 hmac
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
|
||||
// 极光日志配置
|
||||
Logging JiguangLoggingConfig `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
// JiguangLoggingConfig 极光日志配置
|
||||
type JiguangLoggingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
LogDir string `mapstructure:"log_dir"`
|
||||
UseDaily bool `mapstructure:"use_daily"`
|
||||
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||
LevelConfigs map[string]JiguangLevelFileConfig `mapstructure:"level_configs"`
|
||||
}
|
||||
|
||||
// JiguangLevelFileConfig 极光级别文件配置
|
||||
type JiguangLevelFileConfig struct {
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// ShumaiConfig 数脉配置
|
||||
type ShumaiConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
AppID string `mapstructure:"app_id"`
|
||||
AppSecret string `mapstructure:"app_secret"`
|
||||
AppID2 string `mapstructure:"app_id2"` // 走政务接口使用这个
|
||||
AppSecret2 string `mapstructure:"app_secret2"` // 走政务接口使用这个
|
||||
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac,默认 hmac
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
|
||||
Logging ShumaiLoggingConfig `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
// ShumaiLoggingConfig 数脉日志配置
|
||||
type ShumaiLoggingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
LogDir string `mapstructure:"log_dir"`
|
||||
UseDaily bool `mapstructure:"use_daily"`
|
||||
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||
LevelConfigs map[string]ShumaiLevelFileConfig `mapstructure:"level_configs"`
|
||||
}
|
||||
|
||||
// ShumaiLevelFileConfig 数脉级别文件配置
|
||||
type ShumaiLevelFileConfig struct {
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// ShujubaoConfig 数据宝配置
|
||||
type ShujubaoConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
AppSecret string `mapstructure:"app_secret"`
|
||||
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac,默认 hmac
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
|
||||
Logging ShujubaoLoggingConfig `mapstructure:"logging"`
|
||||
}
|
||||
|
||||
// ShujubaoLoggingConfig 数据宝日志配置
|
||||
type ShujubaoLoggingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
LogDir string `mapstructure:"log_dir"`
|
||||
UseDaily bool `mapstructure:"use_daily"`
|
||||
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||
LevelConfigs map[string]ShujubaoLevelFileConfig `mapstructure:"level_configs"`
|
||||
}
|
||||
|
||||
// ShujubaoLevelFileConfig 数据宝级别文件配置
|
||||
type ShujubaoLevelFileConfig struct {
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// PDFGenConfig PDF生成服务配置
|
||||
type PDFGenConfig struct {
|
||||
DevelopmentURL string `mapstructure:"development_url"` // 开发环境服务地址
|
||||
ProductionURL string `mapstructure:"production_url"` // 生产环境服务地址
|
||||
APIPath string `mapstructure:"api_path"` // API路径
|
||||
Timeout time.Duration `mapstructure:"timeout"` // 请求超时时间
|
||||
Cache PDFGenCacheConfig `mapstructure:"cache"` // 缓存配置
|
||||
}
|
||||
|
||||
// PDFGenCacheConfig PDF生成缓存配置
|
||||
type PDFGenCacheConfig struct {
|
||||
TTL time.Duration `mapstructure:"ttl"` // 缓存过期时间
|
||||
CacheDir string `mapstructure:"cache_dir"` // 缓存目录(空则使用默认目录)
|
||||
MaxSize int64 `mapstructure:"max_size"` // 最大缓存大小(0表示不限制,单位:字节)
|
||||
}
|
||||
|
||||
// DomainConfig 域名配置
|
||||
type DomainConfig struct {
|
||||
API string `mapstructure:"api"` // API域名
|
||||
|
||||
@@ -20,60 +20,60 @@ func SetupGormCache(db *gorm.DB, cacheService interfaces.CacheService, cfg *conf
|
||||
|
||||
// 以下是原有的缓存配置代码,已注释掉
|
||||
/*
|
||||
// 创建缓存配置
|
||||
cacheConfig := cache.CacheConfig{
|
||||
DefaultTTL: 30 * time.Minute,
|
||||
TablePrefix: "gorm_cache",
|
||||
MaxCacheSize: 1000,
|
||||
CacheComplexSQL: false,
|
||||
EnableStats: true,
|
||||
EnableWarmup: true,
|
||||
PenetrationGuard: true,
|
||||
BloomFilter: false,
|
||||
AutoInvalidate: true,
|
||||
InvalidateDelay: 100 * time.Millisecond,
|
||||
// 创建缓存配置
|
||||
cacheConfig := cache.CacheConfig{
|
||||
DefaultTTL: 30 * time.Minute,
|
||||
TablePrefix: "gorm_cache",
|
||||
MaxCacheSize: 1000,
|
||||
CacheComplexSQL: false,
|
||||
EnableStats: true,
|
||||
EnableWarmup: true,
|
||||
PenetrationGuard: true,
|
||||
BloomFilter: false,
|
||||
AutoInvalidate: true,
|
||||
InvalidateDelay: 100 * time.Millisecond,
|
||||
|
||||
// 配置启用缓存的表
|
||||
EnabledTables: []string{
|
||||
// "product",
|
||||
// "product_category",
|
||||
// "enterprise_info_submit_records",
|
||||
// "sms_codes",
|
||||
// "wallets",
|
||||
// "subscription",
|
||||
// "product_category",
|
||||
// "product_documentation",
|
||||
// "enterprise_infos",
|
||||
// "api_users",
|
||||
// 添加更多需要缓存的表
|
||||
},
|
||||
// 配置启用缓存的表
|
||||
EnabledTables: []string{
|
||||
// "product",
|
||||
// "product_category",
|
||||
// "enterprise_info_submit_records",
|
||||
// "sms_codes",
|
||||
// "wallets",
|
||||
// "subscription",
|
||||
// "product_category",
|
||||
// "product_documentation",
|
||||
// "enterprise_infos",
|
||||
// "api_users",
|
||||
// 添加更多需要缓存的表
|
||||
},
|
||||
|
||||
// 配置禁用缓存的表(日志表等)
|
||||
DisabledTables: []string{
|
||||
"audit_logs", // 审计日志
|
||||
"system_logs", // 系统日志
|
||||
"operation_logs", // 操作日志
|
||||
"api_calls", // API调用日志表,变化频繁,不适合缓存
|
||||
},
|
||||
}
|
||||
// 配置禁用缓存的表(日志表等)
|
||||
DisabledTables: []string{
|
||||
"audit_logs", // 审计日志
|
||||
"system_logs", // 系统日志
|
||||
"operation_logs", // 操作日志
|
||||
"api_calls", // API调用日志表,变化频繁,不适合缓存
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化全局缓存配置管理器
|
||||
cache.InitCacheConfigManager(cacheConfig)
|
||||
// 初始化全局缓存配置管理器
|
||||
cache.InitCacheConfigManager(cacheConfig)
|
||||
|
||||
// 创建缓存插件
|
||||
cachePlugin := cache.NewGormCachePlugin(cacheService, logger, cacheConfig)
|
||||
// 创建缓存插件
|
||||
cachePlugin := cache.NewGormCachePlugin(cacheService, logger, cacheConfig)
|
||||
|
||||
// 注册插件到GORM
|
||||
if err := db.Use(cachePlugin); err != nil {
|
||||
logger.Error("注册GORM缓存插件失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
// 注册插件到GORM
|
||||
if err := db.Use(cachePlugin); err != nil {
|
||||
logger.Error("注册GORM缓存插件失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("GORM缓存插件已成功注册",
|
||||
zap.Duration("default_ttl", cacheConfig.DefaultTTL),
|
||||
zap.Strings("enabled_tables", cacheConfig.EnabledTables),
|
||||
zap.Strings("disabled_tables", cacheConfig.DisabledTables),
|
||||
)
|
||||
logger.Info("GORM缓存插件已成功注册",
|
||||
zap.Duration("default_ttl", cacheConfig.DefaultTTL),
|
||||
zap.Strings("enabled_tables", cacheConfig.EnabledTables),
|
||||
zap.Strings("disabled_tables", cacheConfig.DisabledTables),
|
||||
)
|
||||
*/
|
||||
|
||||
// return nil
|
||||
@@ -103,13 +103,13 @@ func GetCacheConfig(cfg *config.Config) cache.CacheConfig {
|
||||
},
|
||||
|
||||
DisabledTables: []string{
|
||||
"api_calls", // API调用日志表,变化频繁,不适合缓存
|
||||
"api_calls", // API调用日志表,变化频繁,不适合缓存
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// 初始化全局缓存配置管理器
|
||||
cache.InitCacheConfigManager(cacheConfig)
|
||||
|
||||
|
||||
return cacheConfig
|
||||
}
|
||||
|
||||
@@ -134,13 +134,13 @@ func GetCacheConfig(cfg *config.Config) cache.CacheConfig {
|
||||
},
|
||||
|
||||
DisabledTables: []string{
|
||||
"api_calls", // API调用日志表,变化频繁,不适合缓存
|
||||
"api_calls", // API调用日志表,变化频繁,不适合缓存
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// 初始化全局缓存配置管理器
|
||||
cache.InitCacheConfigManager(cacheConfig)
|
||||
|
||||
|
||||
return cacheConfig
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"go.uber.org/fx"
|
||||
@@ -38,9 +37,13 @@ import (
|
||||
product_repo "tyapi-server/internal/infrastructure/database/repositories/product"
|
||||
infra_events "tyapi-server/internal/infrastructure/events"
|
||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||
"tyapi-server/internal/infrastructure/external/captcha"
|
||||
"tyapi-server/internal/infrastructure/external/email"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
"tyapi-server/internal/infrastructure/external/ocr"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
"tyapi-server/internal/infrastructure/external/sms"
|
||||
"tyapi-server/internal/infrastructure/external/storage"
|
||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||
@@ -236,6 +239,19 @@ func NewContainer() *Container {
|
||||
},
|
||||
// 短信服务
|
||||
sms.NewAliSMSService,
|
||||
// 验证码服务
|
||||
fx.Annotate(
|
||||
func(cfg *config.Config) *captcha.CaptchaService {
|
||||
return captcha.NewCaptchaService(captcha.CaptchaConfig{
|
||||
AccessKeyID: cfg.SMS.AccessKeyID,
|
||||
AccessKeySecret: cfg.SMS.AccessKeySecret,
|
||||
EndpointURL: cfg.SMS.CaptchaEndpoint,
|
||||
SceneID: cfg.SMS.SceneID,
|
||||
EncryptKey: cfg.SMS.CaptchaSecret, // 加密模式 ekey(Base64 编码的 32 字节)
|
||||
})
|
||||
},
|
||||
fx.ResultTags(`name:"captchaService"`),
|
||||
),
|
||||
// 邮件服务
|
||||
fx.Annotate(
|
||||
func(cfg *config.Config, logger *zap.Logger) *email.QQEmailService {
|
||||
@@ -366,6 +382,18 @@ func NewContainer() *Container {
|
||||
func(cfg *config.Config) (*xingwei.XingweiService, error) {
|
||||
return xingwei.NewXingweiServiceWithConfig(cfg)
|
||||
},
|
||||
// JiguangService - 极光服务
|
||||
func(cfg *config.Config) (*jiguang.JiguangService, error) {
|
||||
return jiguang.NewJiguangServiceWithConfig(cfg)
|
||||
},
|
||||
// ShumaiService - 数脉服务
|
||||
func(cfg *config.Config) (*shumai.ShumaiService, error) {
|
||||
return shumai.NewShumaiServiceWithConfig(cfg)
|
||||
},
|
||||
// ShujubaoService - 数据宝服务
|
||||
func(cfg *config.Config) (*shujubao.ShujubaoService, error) {
|
||||
return shujubao.NewShujubaoServiceWithConfig(cfg)
|
||||
},
|
||||
func(cfg *config.Config) *yushan.YushanService {
|
||||
return yushan.NewYushanService(
|
||||
cfg.Yushan.URL,
|
||||
@@ -552,6 +580,11 @@ func NewContainer() *Container {
|
||||
product_repo.NewGormProductCategoryRepository,
|
||||
fx.As(new(domain_product_repo.ProductCategoryRepository)),
|
||||
),
|
||||
// 产品二级分类仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
product_repo.NewGormProductSubCategoryRepository,
|
||||
fx.As(new(domain_product_repo.ProductSubCategoryRepository)),
|
||||
),
|
||||
// 订阅仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
product_repo.NewGormSubscriptionRepository,
|
||||
@@ -571,6 +604,11 @@ func NewContainer() *Container {
|
||||
product_repo.NewGormComponentReportRepository,
|
||||
fx.As(new(domain_product_repo.ComponentReportRepository)),
|
||||
),
|
||||
// 购买订单仓储
|
||||
fx.Annotate(
|
||||
finance_repo.NewGormPurchaseOrderRepository,
|
||||
fx.As(new(domain_finance_repo.PurchaseOrderRepository)),
|
||||
),
|
||||
// UI组件仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
product_repo.NewGormUIComponentRepository,
|
||||
@@ -646,7 +684,10 @@ func NewContainer() *Container {
|
||||
user_service.NewUserAggregateService,
|
||||
),
|
||||
user_service.NewUserAuthService,
|
||||
user_service.NewSMSCodeService,
|
||||
fx.Annotate(
|
||||
user_service.NewSMSCodeService,
|
||||
fx.ParamTags(``, ``, ``, `name:"captchaService"`),
|
||||
),
|
||||
user_service.NewContractAggregateService,
|
||||
product_service.NewProductManagementService,
|
||||
product_service.NewProductSubscriptionService,
|
||||
@@ -861,6 +902,7 @@ func NewContainer() *Container {
|
||||
ocrService sharedOCR.OCRService,
|
||||
txManager *shared_database.TransactionManager,
|
||||
logger *zap.Logger,
|
||||
cfg *config.Config,
|
||||
) certification.CertificationApplicationService {
|
||||
return certification.NewCertificationApplicationService(
|
||||
aggregateService,
|
||||
@@ -878,6 +920,7 @@ func NewContainer() *Container {
|
||||
ocrService,
|
||||
txManager,
|
||||
logger,
|
||||
cfg,
|
||||
)
|
||||
},
|
||||
fx.As(new(certification.CertificationApplicationService)),
|
||||
@@ -893,6 +936,7 @@ func NewContainer() *Container {
|
||||
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
||||
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
||||
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
||||
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
|
||||
userRepo domain_user_repo.UserRepository,
|
||||
txManager *shared_database.TransactionManager,
|
||||
logger *zap.Logger,
|
||||
@@ -909,6 +953,7 @@ func NewContainer() *Container {
|
||||
alipayOrderRepo,
|
||||
wechatOrderRepo,
|
||||
rechargeRecordRepo,
|
||||
purchaseOrderRepo,
|
||||
componentReportRepo,
|
||||
userRepo,
|
||||
txManager,
|
||||
@@ -950,6 +995,36 @@ func NewContainer() *Container {
|
||||
},
|
||||
fx.As(new(product.ProductApplicationService)),
|
||||
),
|
||||
// 组件报告订单服务
|
||||
func(
|
||||
productRepo domain_product_repo.ProductRepository,
|
||||
docRepo domain_product_repo.ProductDocumentationRepository,
|
||||
apiConfigRepo domain_product_repo.ProductApiConfigRepository,
|
||||
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
|
||||
componentReportRepo domain_product_repo.ComponentReportRepository,
|
||||
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
||||
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
||||
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
||||
subscriptionRepo domain_product_repo.SubscriptionRepository,
|
||||
aliPayService *payment.AliPayService,
|
||||
wechatPayService *payment.WechatPayService,
|
||||
logger *zap.Logger,
|
||||
) *product.ComponentReportOrderService {
|
||||
return product.NewComponentReportOrderService(
|
||||
productRepo,
|
||||
docRepo,
|
||||
apiConfigRepo,
|
||||
purchaseOrderRepo,
|
||||
componentReportRepo,
|
||||
rechargeRecordRepo,
|
||||
alipayOrderRepo,
|
||||
wechatOrderRepo,
|
||||
subscriptionRepo,
|
||||
aliPayService,
|
||||
wechatPayService,
|
||||
logger,
|
||||
)
|
||||
},
|
||||
// 产品API配置应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
product.NewProductApiConfigApplicationService,
|
||||
@@ -960,6 +1035,11 @@ func NewContainer() *Container {
|
||||
product.NewCategoryApplicationService,
|
||||
fx.As(new(product.CategoryApplicationService)),
|
||||
),
|
||||
// 二级分类应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
product.NewSubCategoryApplicationService,
|
||||
fx.As(new(product.SubCategoryApplicationService)),
|
||||
),
|
||||
fx.Annotate(
|
||||
product.NewDocumentationApplicationService,
|
||||
fx.As(new(product.DocumentationApplicationServiceInterface)),
|
||||
@@ -1055,7 +1135,7 @@ func NewContainer() *Container {
|
||||
logger *zap.Logger,
|
||||
) product.UIComponentApplicationService {
|
||||
// 创建UI组件文件服务
|
||||
basePath := "resources/Pure Component/src/ui"
|
||||
basePath := "resources/Pure_Component/src/ui"
|
||||
fileService := product.NewUIComponentFileService(basePath, logger)
|
||||
|
||||
return product.NewUIComponentApplicationService(
|
||||
@@ -1063,6 +1143,7 @@ func NewContainer() *Container {
|
||||
productUIComponentRepo,
|
||||
fileStorageService,
|
||||
fileService,
|
||||
logger,
|
||||
)
|
||||
},
|
||||
fx.As(new(product.UIComponentApplicationService)),
|
||||
@@ -1087,36 +1168,33 @@ func NewContainer() *Container {
|
||||
return pdf.NewPDFGenerator(logger)
|
||||
},
|
||||
),
|
||||
// PDF缓存管理器
|
||||
// PDF缓存管理器(用于PDFG)
|
||||
fx.Provide(
|
||||
func(logger *zap.Logger) (*pdf.PDFCacheManager, error) {
|
||||
// 使用默认配置:缓存目录在临时目录,TTL为24小时,最大缓存大小为500MB
|
||||
cacheDir := "" // 使用默认目录(临时目录下的tyapi_pdf_cache)
|
||||
ttl := 24 * time.Hour
|
||||
maxSize := int64(500 * 1024 * 1024) // 500MB
|
||||
func(cfg *config.Config, logger *zap.Logger) (*pdf.PDFCacheManager, error) {
|
||||
cacheDir := cfg.PDFGen.Cache.CacheDir
|
||||
ttl := cfg.PDFGen.Cache.TTL
|
||||
if ttl == 0 {
|
||||
ttl = 24 * time.Hour
|
||||
}
|
||||
|
||||
// 可以通过环境变量覆盖
|
||||
if envCacheDir := os.Getenv("PDF_CACHE_DIR"); envCacheDir != "" {
|
||||
// 环境变量可以覆盖配置
|
||||
if envCacheDir := os.Getenv("PDFG_CACHE_DIR"); envCacheDir != "" {
|
||||
cacheDir = envCacheDir
|
||||
}
|
||||
if envTTL := os.Getenv("PDF_CACHE_TTL"); envTTL != "" {
|
||||
if envTTL := os.Getenv("PDFG_CACHE_TTL"); envTTL != "" {
|
||||
if parsedTTL, err := time.ParseDuration(envTTL); err == nil {
|
||||
ttl = parsedTTL
|
||||
}
|
||||
}
|
||||
if envMaxSize := os.Getenv("PDF_CACHE_MAX_SIZE"); envMaxSize != "" {
|
||||
if parsedMaxSize, err := strconv.ParseInt(envMaxSize, 10, 64); err == nil {
|
||||
maxSize = parsedMaxSize
|
||||
}
|
||||
}
|
||||
|
||||
maxSize := cfg.PDFGen.Cache.MaxSize
|
||||
cacheManager, err := pdf.NewPDFCacheManager(logger, cacheDir, ttl, maxSize)
|
||||
if err != nil {
|
||||
logger.Warn("PDF缓存管理器初始化失败,将禁用缓存功能", zap.Error(err))
|
||||
return nil, nil // 返回nil,handler中会检查
|
||||
logger.Warn("PDFG缓存管理器初始化失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("PDF缓存管理器已初始化",
|
||||
logger.Info("PDFG缓存管理器已初始化",
|
||||
zap.String("cache_dir", cacheDir),
|
||||
zap.Duration("ttl", ttl),
|
||||
zap.Int64("max_size", maxSize),
|
||||
@@ -1155,6 +1233,8 @@ func NewContainer() *Container {
|
||||
handlers.NewProductHandler,
|
||||
// 产品管理员HTTP处理器
|
||||
handlers.NewProductAdminHandler,
|
||||
// 二级分类HTTP处理器
|
||||
handlers.NewSubCategoryHandler,
|
||||
// API Handler
|
||||
handlers.NewApiHandler,
|
||||
// 统计HTTP处理器
|
||||
@@ -1177,12 +1257,15 @@ func NewContainer() *Container {
|
||||
) *handlers.AnnouncementHandler {
|
||||
return handlers.NewAnnouncementHandler(appService, responseBuilder, validator, logger)
|
||||
},
|
||||
// PDFG HTTP处理器
|
||||
handlers.NewPDFGHandler,
|
||||
// 组件报告处理器
|
||||
func(
|
||||
productRepo domain_product_repo.ProductRepository,
|
||||
docRepo domain_product_repo.ProductDocumentationRepository,
|
||||
apiConfigRepo domain_product_repo.ProductApiConfigRepository,
|
||||
componentReportRepo domain_product_repo.ComponentReportRepository,
|
||||
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
|
||||
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
||||
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
||||
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
||||
@@ -1190,7 +1273,16 @@ func NewContainer() *Container {
|
||||
wechatPayService *payment.WechatPayService,
|
||||
logger *zap.Logger,
|
||||
) *component_report.ComponentReportHandler {
|
||||
return component_report.NewComponentReportHandler(productRepo, docRepo, apiConfigRepo, componentReportRepo, rechargeRecordRepo, alipayOrderRepo, wechatOrderRepo, aliPayService, wechatPayService, logger)
|
||||
return component_report.NewComponentReportHandler(productRepo, docRepo, apiConfigRepo, componentReportRepo, purchaseOrderRepo, rechargeRecordRepo, alipayOrderRepo, wechatOrderRepo, aliPayService, wechatPayService, logger)
|
||||
},
|
||||
// 组件报告订单处理器
|
||||
func(
|
||||
componentReportOrderService *product.ComponentReportOrderService,
|
||||
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
|
||||
config *config.Config,
|
||||
logger *zap.Logger,
|
||||
) *handlers.ComponentReportOrderHandler {
|
||||
return handlers.NewComponentReportOrderHandler(componentReportOrderService, purchaseOrderRepo, config, logger)
|
||||
},
|
||||
// UI组件HTTP处理器
|
||||
func(
|
||||
@@ -1201,12 +1293,19 @@ func NewContainer() *Container {
|
||||
) *handlers.UIComponentHandler {
|
||||
return handlers.NewUIComponentHandler(uiComponentAppService, responseBuilder, validator, logger)
|
||||
},
|
||||
// 验证码HTTP处理器
|
||||
fx.Annotate(
|
||||
handlers.NewCaptchaHandler,
|
||||
fx.ParamTags(`name:"captchaService"`, ``, ``, ``),
|
||||
),
|
||||
),
|
||||
|
||||
// 路由注册
|
||||
fx.Provide(
|
||||
// 用户路由
|
||||
routes.NewUserRoutes,
|
||||
// 验证码路由
|
||||
routes.NewCaptchaRoutes,
|
||||
// 认证路由
|
||||
routes.NewCertificationRoutes,
|
||||
// 财务路由
|
||||
@@ -1215,6 +1314,10 @@ func NewContainer() *Container {
|
||||
routes.NewProductRoutes,
|
||||
// 产品管理员路由
|
||||
routes.NewProductAdminRoutes,
|
||||
// 二级分类路由
|
||||
routes.NewSubCategoryRoutes,
|
||||
// 组件报告订单路由
|
||||
routes.NewComponentReportOrderRoutes,
|
||||
// UI组件路由
|
||||
routes.NewUIComponentRoutes,
|
||||
// 文章路由
|
||||
@@ -1225,6 +1328,8 @@ func NewContainer() *Container {
|
||||
routes.NewApiRoutes,
|
||||
// 统计路由
|
||||
routes.NewStatisticsRoutes,
|
||||
// PDFG路由
|
||||
routes.NewPDFGRoutes,
|
||||
),
|
||||
|
||||
// 应用生命周期
|
||||
@@ -1327,15 +1432,19 @@ func RegisterMiddlewares(
|
||||
func RegisterRoutes(
|
||||
router *sharedhttp.GinRouter,
|
||||
userRoutes *routes.UserRoutes,
|
||||
captchaRoutes *routes.CaptchaRoutes,
|
||||
certificationRoutes *routes.CertificationRoutes,
|
||||
financeRoutes *routes.FinanceRoutes,
|
||||
productRoutes *routes.ProductRoutes,
|
||||
productAdminRoutes *routes.ProductAdminRoutes,
|
||||
subCategoryRoutes *routes.SubCategoryRoutes,
|
||||
componentReportOrderRoutes *routes.ComponentReportOrderRoutes,
|
||||
uiComponentRoutes *routes.UIComponentRoutes,
|
||||
articleRoutes *routes.ArticleRoutes,
|
||||
announcementRoutes *routes.AnnouncementRoutes,
|
||||
apiRoutes *routes.ApiRoutes,
|
||||
statisticsRoutes *routes.StatisticsRoutes,
|
||||
pdfgRoutes *routes.PDFGRoutes,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
adminAuth *middleware.AdminAuthMiddleware,
|
||||
cfg *config.Config,
|
||||
@@ -1348,20 +1457,19 @@ func RegisterRoutes(
|
||||
|
||||
// 所有域名路由路由
|
||||
userRoutes.Register(router)
|
||||
captchaRoutes.Register(router)
|
||||
certificationRoutes.Register(router)
|
||||
financeRoutes.Register(router)
|
||||
productRoutes.Register(router)
|
||||
productAdminRoutes.Register(router)
|
||||
|
||||
// UI组件路由需要特殊处理,因为它需要管理员中间件
|
||||
engine := router.GetEngine()
|
||||
adminGroup := engine.Group("/api/v1/admin")
|
||||
adminGroup.Use(adminAuth.Handle())
|
||||
uiComponentRoutes.RegisterRoutes(adminGroup, adminAuth)
|
||||
subCategoryRoutes.Register(router)
|
||||
componentReportOrderRoutes.Register(router)
|
||||
uiComponentRoutes.Register(router)
|
||||
|
||||
articleRoutes.Register(router)
|
||||
announcementRoutes.Register(router)
|
||||
statisticsRoutes.Register(router)
|
||||
pdfgRoutes.Register(router)
|
||||
|
||||
// 打印注册的路由信息
|
||||
router.PrintRoutes()
|
||||
|
||||
@@ -100,11 +100,69 @@ type JRZQDCBEReq struct {
|
||||
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type JRZQACABReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
// shujubao
|
||||
type QYGL2ACDReq struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
}
|
||||
type QYGLUY3SReq struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntRegno string `json:"ent_reg_no" validate:"omitempty"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
}
|
||||
|
||||
type JRZQOCRYReq struct {
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
type JRZQOCREReq struct {
|
||||
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||
ImageUrl string `json:"image_url" validate:"omitempty,url"`
|
||||
}
|
||||
|
||||
type YYSYK9R4Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type QCXG9F5CReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXG3M7ZReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
PlateColor string `json:"plate_color" validate:"omitempty"`
|
||||
}
|
||||
type QCXG3B8ZReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXGM7R9Req struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXGP1W3Req struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXG5U0ZReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
}
|
||||
type QCXGU2K4Req struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXGY7F2Req struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
VehicleName string `json:"vehicle_name" validate:"omitempty"`
|
||||
VehicleLocation string `json:"vehicle_location" validate:"required"`
|
||||
FirstRegistrationdate string `json:"first_registrationdate" validate:"required"`
|
||||
Color string `json:"color" validate:"omitempty"`
|
||||
}
|
||||
type QYGL6F2DReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
@@ -170,6 +228,24 @@ type YYSYD50FReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type IVYZZQT3Req struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
|
||||
type IVYZSFELReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
|
||||
}
|
||||
type IVYZBPQ2Req struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
type YYSYF7DBReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
StartDate string `json:"start_date" validate:"required,validDate" encrypt:"false"`
|
||||
@@ -247,21 +323,126 @@ type COMB86PMReq struct {
|
||||
}
|
||||
|
||||
type QCXG7A2BReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
type QCXG4896Req struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
|
||||
}
|
||||
type QCXG5F3AReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"omitempty,min=1,validName"`
|
||||
}
|
||||
|
||||
type QCXGGB2QReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
CarPlateType string `json:"carplate_type" validate:"required"`
|
||||
}
|
||||
type QCXGJJ2AReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
EngineNumber string `json:"engine_number" validate:"omitempty"`
|
||||
NoticeModel string `json:"notice_model" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type QCXG4I1ZReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
}
|
||||
|
||||
type QCXG1H7YReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
}
|
||||
|
||||
type QCXG1U4UReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||
ImageURL string `json:"image_url" validate:"omitempty,url"`
|
||||
RegURL string `json:"reg_url" validate:"omitempty,url"`
|
||||
EngineNumber string `json:"engine_number" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type IVYZ0S0DReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type IVYZ1J7HReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
CarPlateType string `json:"carplate_type" validate:"required"`
|
||||
}
|
||||
|
||||
type QCXG3Z3LReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||
ImageURL string `json:"image_url" validate:"omitempty,url"`
|
||||
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||
EngineNumber string `json:"engine_number" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type QCXG2T6SReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||
ImageURL string `json:"image_url" validate:"required,url"`
|
||||
}
|
||||
|
||||
type QCXGYTS2Req struct {
|
||||
VinCode string `json:"vin_code" validate:"omitempty"`
|
||||
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type QCXGGJ3AReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
}
|
||||
|
||||
type QCXGP00WReq struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||
VlPhotoData string `json:"vlphoto_data" validate:"omitempty,validBase64Image"`
|
||||
}
|
||||
|
||||
type QCXG4D2EReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
UserType string `json:"user_type" validate:"required,oneof=1 2 3"`
|
||||
}
|
||||
type COMENT01Req struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
}
|
||||
|
||||
type JRZQ09J8Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type JRZQO6L7Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type JRZQS7G0Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type JRZQO7L1Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
}
|
||||
type FLXGDEA8Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
@@ -361,6 +542,23 @@ type QYGL5A3CReq struct {
|
||||
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||
}
|
||||
|
||||
type QYGL2naoReq struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||
}
|
||||
type QYGLNIO8Req struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
}
|
||||
|
||||
type QYGLP0HTReq struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
Flag string `json:"flag" validate:"omitempty"`
|
||||
Dir string `json:"dir" validate:"omitempty,oneof=up down"`
|
||||
MinPercent string `json:"min_percent" validate:"omitempty"`
|
||||
MaxPercent string `json:"max_percent" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type QYGL8B4DReq struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
@@ -399,10 +597,18 @@ type YYSY6F2BReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZOCR1Req struct {
|
||||
PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"`
|
||||
ImageUrl string `json:"image_url" validate:"omitempty,url"`
|
||||
}
|
||||
type YYSY8B1CReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type QYGLJ0Q1Req struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
}
|
||||
type YYSY6D9AReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
@@ -565,11 +771,7 @@ type FLXG8B4DReq struct {
|
||||
}
|
||||
|
||||
type QCXG9P1CReq struct {
|
||||
VehicleType string `json:"vehicle_type" validate:"omitempty,oneof=0 1 2"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"omitempty,min=1,validName"`
|
||||
UserType string `json:"user_type" validate:"omitempty,oneof=1 2 3"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type QCXG8A3DReq struct {
|
||||
@@ -614,6 +816,13 @@ type QYGL2S0WReq struct {
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
}
|
||||
|
||||
// 全国企业司法模型服务查询_V1
|
||||
type QYGL66SLReq struct {
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
AuthDate string `json:"auth_date" validate:"required,validAuthDate"`
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
|
||||
}
|
||||
type JRZQ2F8AReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
@@ -701,6 +910,102 @@ type IVYZ6M8PReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type IVYZ9H2MReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type YYSY9E4AReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
// YYSY运营商相关API DTO
|
||||
type YYSY3M8SReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYC4R9Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYH6D2Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYH6F3Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
type YYSYP0T4Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYE7V5Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYS9W1Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYK8R3Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type YYSYF2T7Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
DateRange string `json:"date_range" validate:"required,validDateRange"`
|
||||
}
|
||||
|
||||
type QYGL5S1IReq struct {
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||
}
|
||||
|
||||
// 数脉 API
|
||||
type IVYZ3M8SReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZ9K7FReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type IVYZA1B3Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||
}
|
||||
|
||||
type IVYZC4R9Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZP0T4Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
type IVYZX5QZReq struct {
|
||||
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||
}
|
||||
|
||||
type IVYZX5Q2Req struct {
|
||||
Token string `json:"token" validate:"required"`
|
||||
}
|
||||
|
||||
type JRZQ1P5GReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
|
||||
}
|
||||
|
||||
10
internal/domains/api/dto/pdfg_dto.go
Normal file
10
internal/domains/api/dto/pdfg_dto.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package dto
|
||||
|
||||
// PDFG01GZReq PDFG01GZ 请求参数
|
||||
type PDFG01GZReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"` // 授权标识,0或1
|
||||
}
|
||||
|
||||
@@ -71,9 +71,6 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
|
||||
if accessId == "" {
|
||||
return nil, errors.New("AccessId不能为空")
|
||||
}
|
||||
if requestParams == "" {
|
||||
return nil, errors.New("请求参数不能为空")
|
||||
}
|
||||
if clientIp == "" {
|
||||
return nil, errors.New("ClientIp不能为空")
|
||||
}
|
||||
@@ -83,7 +80,7 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
|
||||
AccessId: accessId,
|
||||
TransactionId: GenerateTransactionID(),
|
||||
ClientIp: clientIp,
|
||||
RequestParams: requestParams,
|
||||
RequestParams: "",
|
||||
Status: ApiCallStatusPending,
|
||||
StartAt: time.Now(),
|
||||
}, nil
|
||||
@@ -92,11 +89,11 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
|
||||
// MarkSuccess 标记为成功
|
||||
func (a *ApiCall) MarkSuccess(cost decimal.Decimal) error {
|
||||
// 校验除ErrorMsg和ErrorType外所有字段不能为空
|
||||
if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.RequestParams == "" || a.Status == "" || a.StartAt.IsZero() {
|
||||
if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.Status == "" || a.StartAt.IsZero() {
|
||||
return errors.New("ApiCall字段不能为空(除ErrorMsg和ErrorType)")
|
||||
}
|
||||
// 可选字段也要有值
|
||||
if a.UserId == nil || a.ProductId == nil {
|
||||
if a.UserId == nil || a.ProductId == nil {
|
||||
return errors.New("ApiCall标记成功时UserId、ProductId不能为空")
|
||||
}
|
||||
a.Status = ApiCallStatusSuccess
|
||||
@@ -132,9 +129,6 @@ func (a *ApiCall) Validate() error {
|
||||
if a.TransactionId == "" {
|
||||
return errors.New("TransactionId不能为空")
|
||||
}
|
||||
if a.RequestParams == "" {
|
||||
return errors.New("请求参数不能为空")
|
||||
}
|
||||
if a.Status != ApiCallStatusPending && a.Status != ApiCallStatusSuccess && a.Status != ApiCallStatusFailed {
|
||||
return errors.New("无效的调用状态")
|
||||
}
|
||||
|
||||
@@ -6,19 +6,24 @@ import (
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/domains/api/services/processors/comb"
|
||||
"tyapi-server/internal/domains/api/services/processors/dwbg"
|
||||
"tyapi-server/internal/domains/api/services/processors/flxg"
|
||||
"tyapi-server/internal/domains/api/services/processors/ivyz"
|
||||
"tyapi-server/internal/domains/api/services/processors/jrzq"
|
||||
"tyapi-server/internal/domains/api/services/processors/pdfg"
|
||||
"tyapi-server/internal/domains/api/services/processors/qcxg"
|
||||
"tyapi-server/internal/domains/api/services/processors/qygl"
|
||||
"tyapi-server/internal/domains/api/services/processors/test"
|
||||
"tyapi-server/internal/domains/api/services/processors/yysy"
|
||||
"tyapi-server/internal/domains/product/services"
|
||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
@@ -44,24 +49,29 @@ type ApiRequestService struct {
|
||||
validator interfaces.RequestValidator
|
||||
processorDeps *processors.ProcessorDependencies
|
||||
combService *comb.CombService
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewApiRequestService(
|
||||
westDexService *westdex.WestDexService,
|
||||
shujubaoService *shujubao.ShujubaoService,
|
||||
muziService *muzi.MuziService,
|
||||
yushanService *yushan.YushanService,
|
||||
tianYanChaService *tianyancha.TianYanChaService,
|
||||
alicloudService *alicloud.AlicloudService,
|
||||
zhichaService *zhicha.ZhichaService,
|
||||
xingweiService *xingwei.XingweiService,
|
||||
jiguangService *jiguang.JiguangService,
|
||||
shumaiService *shumai.ShumaiService,
|
||||
validator interfaces.RequestValidator,
|
||||
productManagementService *services.ProductManagementService,
|
||||
cfg *config.Config,
|
||||
) *ApiRequestService {
|
||||
// 创建组合包服务
|
||||
combService := comb.NewCombService(productManagementService)
|
||||
|
||||
// 创建处理器依赖容器
|
||||
processorDeps := processors.NewProcessorDependencies(westDexService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, validator, combService)
|
||||
processorDeps := processors.NewProcessorDependencies(westDexService, shujubaoService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, jiguangService, shumaiService, validator, combService)
|
||||
|
||||
// 统一注册所有处理器
|
||||
registerAllProcessors(combService)
|
||||
@@ -75,6 +85,7 @@ func NewApiRequestService(
|
||||
validator: validator,
|
||||
processorDeps: processorDeps,
|
||||
combService: combService,
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +123,7 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
|
||||
"JRZQ4AA8": jrzq.ProcessJRZQ4AA8Request,
|
||||
"JRZQDCBE": jrzq.ProcessJRZQDCBERequest,
|
||||
"JRZQACAB": jrzq.ProcessJRZQACABERequest, // 银行卡四要素
|
||||
"JRZQ09J8": jrzq.ProcessJRZQ09J8Request,
|
||||
"JRZQ1D09": jrzq.ProcessJRZQ1D09Request,
|
||||
"JRZQ3C7B": jrzq.ProcessJRZQ3C7BRequest,
|
||||
@@ -133,6 +145,12 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"JRZQ1W4X": jrzq.ProcessJRZQ1W4XRequest,
|
||||
"JRZQ3P01": jrzq.ProcessJRZQ3P01Request,
|
||||
"JRZQ3AG6": jrzq.ProcessJRZQ3AG6Request,
|
||||
"JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版
|
||||
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
|
||||
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
|
||||
"JRZQ1P5G": jrzq.ProcessJRZQ1P5GRequest, // 全国自然人借贷压力指数查询(2)
|
||||
"JRZQOCRE": jrzq.ProcessJRZQOCREERequest, // 银行卡OCR数卖
|
||||
"JRZQOCRY": jrzq.ProcessJRZQOCRYERequest, // 银行卡OCR数据宝
|
||||
|
||||
// QYGL系列处理器
|
||||
"QYGL8261": qygl.ProcessQYGL8261Request,
|
||||
@@ -157,6 +175,13 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"QYGL5A9T": qygl.ProcessQYGL5A9TRequest, //全国企业各类工商风险统计数量查询
|
||||
"QYGL2S0W": qygl.ProcessQYGL2S0WRequest, //失信被执行企业个人查询
|
||||
"QYGL5CMP": qygl.ProcessQYGL5CMPRequest, //企业五要素验证
|
||||
"QYGL66SL": qygl.ProcessQYGL66SLRequest, //全国企业司法模型服务查询_V1
|
||||
"QYGL2NAO": qygl.ProcessQYGL2naoRequest, //股权变更
|
||||
"QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息
|
||||
"QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透
|
||||
"QYGL5S1I": qygl.ProcessQYGL5S1IRequest, //企业司法涉诉I
|
||||
"QYGLJ0Q1": qygl.ProcessQYGLJ0Q1Request, //企业股权结构全景查询
|
||||
"QYGLUY3S": qygl.ProcessQYGLUY3SRequest, //企业经营状态全景查询
|
||||
|
||||
// YYSY系列处理器
|
||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||
@@ -177,6 +202,16 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
|
||||
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest,
|
||||
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest,
|
||||
"YYSY3M8S": yysy.ProcessYYSY3M8SRequest, //运营商二要素查询
|
||||
"YYSYC4R9": yysy.ProcessYYSYC4R9Request, //运营商三要素详版查询
|
||||
"YYSYH6D2": yysy.ProcessYYSYH6D2Request, //运营商三要素简版查询
|
||||
"YYSYP0T4": yysy.ProcessYYSYP0T4Request, //在网时长查询
|
||||
"YYSYE7V5": yysy.ProcessYYSYE7V5Request, //手机在网状态查询
|
||||
"YYSYS9W1": yysy.ProcessYYSYS9W1Request, //手机携号转网查询
|
||||
"YYSYK8R3": yysy.ProcessYYSYK8R3Request, //手机空号检测查询
|
||||
"YYSYH6F3": yysy.ProcessYYSYH6F3Request, //运营商三要素即时版查询
|
||||
"YYSYK9R4": yysy.ProcessYYSYK9R4Request, //全网手机三要素验证1979周更新版
|
||||
"YYSYF2T7": yysy.ProcessYYSYF2T7Request, //手机二次放号检测查询
|
||||
|
||||
// IVYZ系列处理器
|
||||
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
|
||||
@@ -206,6 +241,19 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
|
||||
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
|
||||
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
|
||||
"IVYZ9H2M": ivyz.ProcessIVYZ9H2MRequest, //极光个人婚姻查询(V2版)
|
||||
"IVYZZQT3": ivyz.ProcessIVYZZQT3Request, //人脸比对V3
|
||||
"IVYZBPQ2": ivyz.ProcessIVYZBPQ2Request, //人脸比对V2
|
||||
"IVYZSFEL": ivyz.ProcessIVYZSFELRequest, //全国自然人人像三要素核验_V1
|
||||
"IVYZ0S0D": ivyz.ProcessIVYZ0S0DRequest, //劳动仲裁信息查询(个人版)
|
||||
"IVYZ1J7H": ivyz.ProcessIVYZ1J7HRequest, //行驶证核查v2
|
||||
"IVYZ9K7F": ivyz.ProcessIVYZ9K7FRequest, //身份证实名认证即时版
|
||||
"IVYZA1B3": ivyz.ProcessIVYZA1B3Request, //公安三要素人脸识别
|
||||
"IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证政务版
|
||||
"IVYZX5QZ": ivyz.ProcessIVYZX5QZRequest, //活体检测
|
||||
"IVYZX5Q2": ivyz.ProcessIVYZX5Q2Request, //活体识别步骤二
|
||||
"IVYZOCR1": ivyz.ProcessIVYZOCR1Request, //身份证OCR
|
||||
"IVYZOCR2": ivyz.ProcessIVYZOCR2Request, //身份证OCR2数卖
|
||||
|
||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||
@@ -217,6 +265,28 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"QCXG9P1C": qcxg.ProcessQCXG9P1CRequest,
|
||||
"QCXG8A3D": qcxg.ProcessQCXG8A3DRequest,
|
||||
"QCXG6B4E": qcxg.ProcessQCXG6B4ERequest,
|
||||
"QCXG4896": qcxg.ProcessQCXG4896Request,
|
||||
"QCXG5F3A": qcxg.ProcessQCXG5F3ARequest, // 极光个人车辆查询
|
||||
"QCXG4D2E": qcxg.ProcessQCXG4D2ERequest, // 极光名下车辆数量查询
|
||||
"QCXGJJ2A": qcxg.ProcessQCXGJJ2ARequest, // vin码查车辆信息(一对多)
|
||||
"QCXGGJ3A": qcxg.ProcessQCXGGJ3ARequest, // 车辆vin码查询号牌
|
||||
"QCXGYTS2": qcxg.ProcessQCXGYTS2Request, // 车辆二要素核验v2
|
||||
"QCXGP00W": qcxg.ProcessQCXGP00WRequest, // 车辆出险详版查询
|
||||
"QCXGGB2Q": qcxg.ProcessQCXGGB2QRequest, // 车辆二要素核验V1
|
||||
"QCXG4I1Z": qcxg.ProcessQCXG4I1ZRequest, // 车辆过户详版查询
|
||||
"QCXG1H7Y": qcxg.ProcessQCXG1H7YRequest, // 车辆过户简版查询
|
||||
"QCXG3Z3L": qcxg.ProcessQCXG3Z3LRequest, // 车辆维保详细版查询
|
||||
"QCXG3Y6B": qcxg.ProcessQCXG3Y6BRequest, // 车辆维保简版查询
|
||||
"QCXG2T6S": qcxg.ProcessQCXG2T6SRequest, // 车辆里程记录(品牌查询)
|
||||
"QCXG1U4U": qcxg.ProcessQCXG1U4URequest, //
|
||||
"QCXG9F5C": qcxg.ProcessQCXG9F5CERequest, //疑似营运车辆注册平台数 10386
|
||||
"QCXG3B8Z": qcxg.ProcessQCXG3B8ZRequest, //疑似运营车辆查询(月度里程)10268
|
||||
"QCXGP1W3": qcxg.ProcessQCXGP1W3Request, //疑似运营车辆查询(季度里程)10269
|
||||
"QCXGM7R9": qcxg.ProcessQCXGM7R9Request, //疑似运营车辆查询(半年度里程)10270
|
||||
"QCXGU2K4": qcxg.ProcessQCXGU2K4Request, //疑似运营车辆查询(年度里程)10271
|
||||
"QCXG5U0Z": qcxg.ProcessQCXG5U0ZRequest, // 车辆静态信息查询 10479
|
||||
"QCXGY7F2": qcxg.ProcessQCXGY7F2Request, // 二手车VIN估值 10443
|
||||
"QCXG3M7Z": qcxg.ProcessQCXG3M7ZRequest, //人车关系核验(ETC)10093 月更
|
||||
|
||||
// DWBG系列处理器 - 多维报告
|
||||
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
|
||||
@@ -230,6 +300,9 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"TEST001": test.ProcessTestRequest,
|
||||
"TEST002": test.ProcessTestErrorRequest,
|
||||
"TEST003": test.ProcessTestTimeoutRequest,
|
||||
|
||||
// PDFG系列处理器 - PDF生成
|
||||
"PDFG01GZ": pdfg.ProcessPDFG01GZRequest,
|
||||
}
|
||||
|
||||
// 批量注册到组合包服务
|
||||
@@ -249,6 +322,11 @@ func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, apiCode st
|
||||
// 设置Options和CallContext到依赖容器
|
||||
deps := a.processorDeps.WithOptions(options).WithCallContext(callContext)
|
||||
|
||||
// 将apiCode放入context,供外部服务使用
|
||||
ctx = context.WithValue(ctx, "api_code", apiCode)
|
||||
// 将config放入context,供处理器使用
|
||||
ctx = context.WithValue(ctx, "config", a.config)
|
||||
|
||||
// 1. 优先查找已注册的自定义处理器
|
||||
if processor, exists := RequestProcessors[apiCode]; exists {
|
||||
return processor(ctx, params, deps)
|
||||
|
||||
@@ -172,7 +172,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"IVYZ6G7H": &dto.IVYZ6G7HReq{},
|
||||
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
||||
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
||||
"COMBHZY2": &dto.COMBHZY2Req{}, // 自此无imp11.28
|
||||
"COMBHZY2": &dto.COMBHZY2Req{}, //
|
||||
"QCXG8A3D": &dto.QCXG8A3DReq{},
|
||||
"QCXG6B4E": &dto.QCXG6B4EReq{},
|
||||
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
||||
@@ -198,7 +198,69 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
|
||||
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
|
||||
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
|
||||
"IVYZ9H2M": &dto.IVYZ9H2MReq{}, //极光个人婚姻查询(V2版)
|
||||
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
|
||||
"QCXG4896": &dto.QCXG4896Req{}, //网约车风险查询
|
||||
"IVYZZQT3": &dto.IVYZZQT3Req{}, //人脸比对V3
|
||||
"IVYZBPQ2": &dto.IVYZBPQ2Req{}, //人脸比对V2
|
||||
"IVYZSFEL": &dto.IVYZSFELReq{}, //全国自然人人像三要素核验_V1
|
||||
"QYGL66SL": &dto.QYGL66SLReq{}, //全国企业司法模型服务查询_V1
|
||||
"QCXG5F3A": &dto.QCXG5F3AReq{}, //极光个人车辆查询
|
||||
"QCXG4D2E": &dto.QCXG4D2EReq{}, //极光名下车辆数量查询
|
||||
"QYGLP0HT": &dto.QYGLP0HTReq{}, //股权穿透
|
||||
"QYGL2NAO": &dto.QYGL2naoReq{}, //股权变更
|
||||
"QYGLNIO8": &dto.QYGLNIO8Req{}, //企业基本信息
|
||||
"QYGL4B2E": &dto.QYGL5A3CReq{}, //税收违法
|
||||
"QYGL7D9A": &dto.QYGL5A3CReq{}, //欠税公告
|
||||
"IVYZ0S0D": &dto.IVYZ0S0DReq{}, //劳动仲裁信息查询(个人版)
|
||||
"IVYZ1J7H": &dto.IVYZ1J7HReq{}, //行驶证核查v2
|
||||
"QCXGJJ2A": &dto.QCXGJJ2AReq{}, //vin码查车辆信息(一对多)
|
||||
"QCXGGJ3A": &dto.QCXGGJ3AReq{}, //车辆vin码查询号牌
|
||||
"QCXGYTS2": &dto.QCXGYTS2Req{}, //车辆二要素核验v2
|
||||
"QCXGP00W": &dto.QCXGP00WReq{}, //车辆出险详版查询
|
||||
"QCXGGB2Q": &dto.QCXGGB2QReq{}, //车辆二要素核验V1
|
||||
"QCXG4I1Z": &dto.QCXG4I1ZReq{}, //车辆过户详版查询
|
||||
"QCXG1H7Y": &dto.QCXG1H7YReq{}, //车辆过户简版查询
|
||||
"QCXG3Z3L": &dto.QCXG3Z3LReq{}, //车辆维保详细版查询
|
||||
"QCXG3Y6B": &dto.QCXG1U4UReq{}, //车辆维保简版查询
|
||||
"QCXG2T6S": &dto.QCXG2T6SReq{}, //车辆里程记录(品牌查询)
|
||||
"QCXG1U4U": &dto.QCXG1U4UReq{}, //车辆里程记录(混合查询)
|
||||
"JRZQO6L7": &dto.JRZQO6L7Req{}, //全国自然人经济特征评分模型v3 简版
|
||||
"JRZQO7L1": &dto.JRZQO7L1Req{}, //全国自然人经济特征评分模型v4 详版
|
||||
"JRZQS7G0": &dto.JRZQS7G0Req{}, //社保综合评分V1
|
||||
"IVYZ9K7F": &dto.IVYZ9K7FReq{}, //身份证实名认证即时版
|
||||
"YYSY3M8S": &dto.YYSY3M8SReq{}, //运营商二要素查询
|
||||
"YYSYC4R9": &dto.YYSYC4R9Req{}, //运营商三要素详版查询
|
||||
"YYSYH6D2": &dto.YYSYH6D2Req{}, //运营商三要素简版政务版查询
|
||||
"YYSYP0T4": &dto.YYSYP0T4Req{}, //在网时长查询
|
||||
"YYSYE7V5": &dto.YYSYE7V5Req{}, //手机在网状态查询
|
||||
"YYSYS9W1": &dto.YYSYS9W1Req{}, //手机携号转网查询
|
||||
"YYSYK8R3": &dto.YYSYK8R3Req{}, //手机空号检测查询
|
||||
"YYSYF2T7": &dto.YYSYF2T7Req{}, //手机二次放号检测查询
|
||||
"IVYZA1B3": &dto.IVYZA1B3Req{}, //公安三要素人脸识别
|
||||
"IVYZX5QZ": &dto.IVYZX5QZReq{}, //活体识别
|
||||
"IVYZN2P8": &dto.IVYZ9K7FReq{}, //身份证实名认证政务版
|
||||
"YYSYH6F3": &dto.YYSYH6F3Req{}, //运营商三要素简版即时版查询
|
||||
"IVYZX5Q2": &dto.IVYZX5Q2Req{}, //活体识别步骤二
|
||||
"PDFG01GZ": &dto.PDFG01GZReq{}, //
|
||||
"QYGL5S1I": &dto.QYGL5S1IReq{}, //企业司法涉诉V2
|
||||
"JRZQACAB": &dto.JRZQACABReq{}, //银行卡四要素
|
||||
"QCXG9F5C": &dto.QCXG9F5CReq{}, //疑似营运车辆注册平台数 10386
|
||||
"QCXG3B8Z": &dto.QCXG3B8ZReq{}, //疑似运营车辆查询(月度里程)10268
|
||||
"QCXGP1W3": &dto.QCXGP1W3Req{}, //疑似运营车辆查询(季度里程)10269
|
||||
"QCXGM7R9": &dto.QCXGM7R9Req{}, //疑似运营车辆查询(半年度里程)10270
|
||||
"QCXGU2K4": &dto.QCXGU2K4Req{}, //疑似运营车辆查询(年度里程)10271
|
||||
"QCXG5U0Z": &dto.QCXG5U0ZReq{}, //车辆静态信息查询 10479
|
||||
"QCXGY7F2": &dto.QCXGY7F2Req{}, //二手车VIN估值 10443
|
||||
"YYSYK9R4": &dto.YYSYK9R4Req{}, //全网手机三要素验证1979周更新版
|
||||
"QCXG3M7Z": &dto.QCXG3M7ZReq{}, //人车关系核验(ETC)10093 月更
|
||||
"JRZQ1P5G": &dto.JRZQ1P5GReq{}, //全国自然人借贷压力指数查询(2)
|
||||
"IVYZOCR1": &dto.IVYZOCR1Req{}, //身份证OCR
|
||||
"IVYZOCR2": &dto.IVYZOCR1Req{}, //身份证OCR2数卖
|
||||
"QYGLJ0Q1": &dto.QYGLJ0Q1Req{}, //企业股权结构全景查询
|
||||
"QYGLUY3S": &dto.QYGLUY3SReq{}, //企业全量信息核验V2 可用
|
||||
"JRZQOCRE": &dto.JRZQOCREReq{}, //银行卡OCR数卖
|
||||
"JRZQOCRY": &dto.JRZQOCRYReq{}, //银行卡OCR数据宝
|
||||
}
|
||||
|
||||
// 优先返回已配置的DTO
|
||||
@@ -306,6 +368,8 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
|
||||
frontendRules = append(frontendRules, "日期格式")
|
||||
case rule == "validAuthDate":
|
||||
frontendRules = append(frontendRules, "授权日期格式")
|
||||
case rule == "validDateRange":
|
||||
frontendRules = append(frontendRules, "日期范围格式(YYYYMMDD-YYYYMMDD)")
|
||||
case rule == "validTimeRange":
|
||||
frontendRules = append(frontendRules, "时间范围格式")
|
||||
case rule == "validMobileType":
|
||||
@@ -321,6 +385,7 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
|
||||
case strings.HasPrefix(rule, "oneof="):
|
||||
values := strings.TrimPrefix(rule, "oneof=")
|
||||
frontendRules = append(frontendRules, "可选值: "+values)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -342,6 +407,8 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
|
||||
return "text" // time_range是HH:MM-HH:MM格式,使用文本输入
|
||||
} else if strings.Contains(validation, "授权日期格式") {
|
||||
return "text" // auth_date是YYYYMMDD-YYYYMMDD格式,使用文本输入
|
||||
} else if strings.Contains(validation, "日期范围格式") {
|
||||
return "text" // date_range 为 YYYYMMDD-YYYYMMDD,使用文本输入便于直接输入
|
||||
} else if strings.Contains(validation, "日期") {
|
||||
return "date"
|
||||
} else if strings.Contains(validation, "链接") {
|
||||
@@ -350,6 +417,8 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
|
||||
return "select"
|
||||
} else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "base64") {
|
||||
return "textarea"
|
||||
} else if strings.Contains(validation, "图片地址") {
|
||||
return "url"
|
||||
}
|
||||
return "text"
|
||||
case reflect.Int64:
|
||||
@@ -375,7 +444,9 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
||||
"ent_name": "企业名称",
|
||||
"legal_person": "法人姓名",
|
||||
"ent_code": "企业代码",
|
||||
"ent_reg_no": "企业注册号",
|
||||
"auth_date": "授权日期",
|
||||
"date_range": "日期范围",
|
||||
"time_range": "时间范围",
|
||||
"authorized": "是否授权",
|
||||
"authorization_url": "授权链接",
|
||||
@@ -395,10 +466,26 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
||||
"plate_type": "号牌类型",
|
||||
"vin_code": "车辆识别代号VIN码",
|
||||
"return_type": "返回类型",
|
||||
"photo_data": "人脸图片",
|
||||
"photo_data": "入参图片base64编码",
|
||||
"owner_type": "企业主类型",
|
||||
"type": "查询类型",
|
||||
"query_reason_id": "查询原因ID",
|
||||
"flag": "层次",
|
||||
"dir": "方向",
|
||||
"min_percent": "股权穿透比例下限",
|
||||
"max_percent": "股权穿透比例上限",
|
||||
"engine_number": "发动机号码",
|
||||
"notice_model": "车辆型号",
|
||||
"vlphoto_data": "行驶证图片",
|
||||
"carplate_type": "车辆号牌类型",
|
||||
"image_url": "入参图片地址",
|
||||
"reg_url": "车辆登记证图片地址",
|
||||
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
||||
"vehicle_name": "车型名称",
|
||||
"vehicle_location": "车辆所在地",
|
||||
"first_registrationdate": "首次登记日期",
|
||||
"color": "颜色",
|
||||
"plate_color": "车牌颜色",
|
||||
}
|
||||
|
||||
if label, exists := labelMap[jsonTag]; exists {
|
||||
@@ -420,7 +507,9 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
||||
"ent_name": "示例企业有限公司",
|
||||
"legal_person": "王五",
|
||||
"ent_code": "91110000123456789X",
|
||||
"ent_reg_no": "110000000123456",
|
||||
"auth_date": "20240101-20241231",
|
||||
"date_range": "20240101-20241231",
|
||||
"time_range": "09:00-18:00",
|
||||
"authorized": "1",
|
||||
"years": "5",
|
||||
@@ -444,6 +533,22 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
||||
"ownerType": "1",
|
||||
"type": "per",
|
||||
"query_reason_id": "1",
|
||||
"flag": "4",
|
||||
"dir": "down",
|
||||
"min_percent": "0",
|
||||
"max_percent": "1",
|
||||
"engine_number": "1234567890",
|
||||
"notice_model": "1",
|
||||
"vlphoto_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"carplate_type": "01",
|
||||
"image_url": "https://example.com/images/driving_license.jpg",
|
||||
"reg_url": "https://example.com/images/vehicle_registration.jpg",
|
||||
"token": "0fc79b80371f45e2ac1c693ef9136b24",
|
||||
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",
|
||||
"vehicle_location": "车辆所在地,示例:北京",
|
||||
"first_registrationdate": "初登日期,示例:2020-05",
|
||||
"color": "示例:白色",
|
||||
"plate_color": "车牌颜色(0:蓝色,1:黄色,2:黑色,3:白色,4:渐变绿色,5:黄绿双拼色,6:蓝白渐变色,7:临时牌照,11:绿色,12:红色)默认标准车牌查蓝色,新能源车牌查绿色)",
|
||||
}
|
||||
|
||||
if example, exists := exampleMap[jsonTag]; exists {
|
||||
@@ -474,7 +579,9 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
||||
"ent_name": "请输入企业全称",
|
||||
"legal_person": "请输入法人真实姓名",
|
||||
"ent_code": "请输入统一社会信用代码",
|
||||
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
|
||||
"auth_date": "请输入授权日期范围(YYYYMMDD-YYYYMMDD)",
|
||||
"date_range": "请输入日期范围(YYYYMMDD-YYYYMMDD)",
|
||||
"time_range": "请输入时间范围(HH:MM-HH:MM)",
|
||||
"authorized": "请选择是否授权",
|
||||
"years": "请输入查询年数(0-100)",
|
||||
@@ -494,10 +601,26 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
||||
"plate_type": "请选择号牌类型(01或02)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码",
|
||||
"return_type": "请选择返回类型",
|
||||
"photo_data": "请输入base64编码的人脸图片(支持JPG、BMP、PNG格式)",
|
||||
"photo_data": "请输入base64编码的入参图片(支持JPG、BMP、PNG格式)",
|
||||
"ownerType": "请选择企业主类型",
|
||||
"type": "请选择查询类型",
|
||||
"query_reason_id": "请选择查询原因ID",
|
||||
"flag": "请输入层次(最大4)",
|
||||
"dir": "请选择方向(up-向上,down-向下)",
|
||||
"min_percent": "请输入股权穿透比例下限(默认0)",
|
||||
"max_percent": "请输入股权穿透比例上限(默认1)",
|
||||
"engine_number": "请输入发动机号码",
|
||||
"notice_model": "请输入车辆型号",
|
||||
"vlphoto_data": "请输入行驶证图片",
|
||||
"carplate_type": "请选择车辆号牌类型(01-大型汽车 02-小型汽车 03-使馆汽车 04-领馆汽车 05-境外汽车 06-外籍汽车 07-普通摩托车 08-轻便摩托车 09-使馆摩托车 10-领馆摩托车 11-境外摩托车 12-外籍摩托车 13-低速车 14-拖拉机 15-挂车 16-教练汽车 17-教练摩托车 20-临时入境汽车 21-临时入境摩托车 22-临时行驶车 23-警用汽车 24-警用摩托 51-新能源大型车 52-新能源小型车)",
|
||||
"image_url": "请输入行驶证图片地址",
|
||||
"reg_url": "请输入车辆登记证图片地址",
|
||||
"token": "请输入token",
|
||||
"vehicle_name": "请输入车型名称",
|
||||
"vehicle_location": "请输入车辆所在地",
|
||||
"first_registrationdate": "请输入首次登记日期,格式:YYYY-MM",
|
||||
"color": "请输入颜色",
|
||||
"plate_color": "请输入车牌颜色",
|
||||
}
|
||||
|
||||
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
||||
@@ -530,7 +653,9 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
||||
"ent_name": "请输入企业全称",
|
||||
"legal_person": "请输入法人真实姓名",
|
||||
"ent_code": "请输入统一社会信用代码",
|
||||
"ent_reg_no": "请输入企业注册号(统一社会信用代码)",
|
||||
"auth_date": "请输入授权日期范围,格式:YYYYMMDD-YYYYMMDD,且日期范围必须包括今天",
|
||||
"date_range": "请输入日期范围,格式:YYYYMMDD-YYYYMMDD",
|
||||
"time_range": "请输入时间范围,格式:HH:MM-HH:MM",
|
||||
"authorized": "请输入是否授权:0-未授权,1-已授权",
|
||||
"years": "请输入查询年数(0-100)",
|
||||
@@ -550,10 +675,26 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
||||
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
||||
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
||||
"photo_data": "人脸图片(必填):base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"photo_data": "入参图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"owner_type": "企业主类型编码:1-法定代表人;2-主要人员;3-自然人股东;4-法定代表人及自然人股东;5-其他",
|
||||
"type": "查询类型:per-人员,ent-企业 ",
|
||||
"query_reason_id": "查询原因ID:1-授信审批;2-贷中管理;3-贷后管理;4-异议处理;5-担保查询;6-租赁资质审查;7-融资租赁审批;8-借贷撮合查询;9-保险审批;10-资质审核;11-风控审核;12-企业背调",
|
||||
"flag": "层次,最大4",
|
||||
"dir": "方向:up-向上穿透,down-向下穿透",
|
||||
"min_percent": "股权穿透比例下限(大于等于),默认为0,支持小数点后两位(以小数指代百分比)",
|
||||
"max_percent": "股权穿透比例上限(小于等于),默认为1,支持小数点后两位(以小数指代百分比)",
|
||||
"engine_number": "发动机号码",
|
||||
"notice_model": "车辆型号",
|
||||
"vlphoto_data": "行驶证图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"carplate_type": "车辆号牌类型:01-大型汽车;02-小型汽车;03-使馆汽车;04-领馆汽车;05-境外汽车;06-外籍汽车;07-普通摩托车;08-轻便摩托车;09-使馆摩托车;10-领馆摩托车;11-境外摩托车;12-外籍摩托车;13-低速车;14-拖拉机;15-挂车;16-教练汽车;17-教练摩托车;20-临时入境汽车;21-临时入境摩托车;22-临时行驶车;23-警用汽车;24-警用摩托;51-新能源大型车;52-新能源小型车",
|
||||
"image_url": "入参图片url地址",
|
||||
"reg_url": "车辆登记证图片地址(非必填):请提供车辆登记证的图片URL地址",
|
||||
"token": "token采集及获取结果时所使用的凭证,有效期2个小时,在此时效内,应用侧可以发起采集请求(重复的采集所触发的结果会被忽略)和结果查询",
|
||||
"vehicle_name": "车型名称,示例:凌派 2020款 锐·混动 1.5L 锐·舒适版",
|
||||
"vehicle_location": "车辆所在地",
|
||||
"first_registrationdate": "首次登记日期,格式:YYYY-MM",
|
||||
"color": "颜色",
|
||||
"plate_color": "车牌颜色",
|
||||
}
|
||||
|
||||
if desc, exists := descMap[jsonTag]; exists {
|
||||
|
||||
@@ -32,6 +32,12 @@ func (cs *CombService) RegisterProcessor(apiCode string, processor processors.Pr
|
||||
cs.processorRegistry[apiCode] = processor
|
||||
}
|
||||
|
||||
// GetProcessor 获取处理器(用于内部调用)
|
||||
func (cs *CombService) GetProcessor(apiCode string) (processors.ProcessorFunc, bool) {
|
||||
processor, exists := cs.processorRegistry[apiCode]
|
||||
return processor, exists
|
||||
}
|
||||
|
||||
// ProcessCombRequest 处理组合包请求 - 实现 CombServiceInterface
|
||||
func (cs *CombService) ProcessCombRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies, packageCode string) (*processors.CombinedResult, error) {
|
||||
// 1. 根据组合包code获取产品信息
|
||||
|
||||
@@ -48,6 +48,7 @@ type baseProductData struct {
|
||||
RiskWarning riskWarning `json:"riskWarning"`
|
||||
StandLiveInfo standLiveInfo `json:"standLiveInfo"`
|
||||
VerifyRule string `json:"verifyRule"`
|
||||
MultCourtInfo multCourtInfo `json:"multCourtInfo"`
|
||||
}
|
||||
|
||||
// baseInfo 存放被查询人的基础身份信息
|
||||
@@ -171,6 +172,34 @@ type standLiveInfo struct {
|
||||
FinalAuthResult string `json:"finalAuthResult"`
|
||||
}
|
||||
|
||||
// multCourtInfo 司法风险核验产品,统一承载涉案/执行/失信/限高四类公告
|
||||
type multCourtInfo struct {
|
||||
LegalCasesFlag int `json:"legalCasesFlag"`
|
||||
LegalCases []multCaseItem `json:"legalCases"`
|
||||
ExecutionCasesFlag int `json:"executionCasesFlag"`
|
||||
ExecutionCases []multCaseItem `json:"executionCases"`
|
||||
DisinCasesFlag int `json:"disinCasesFlag"`
|
||||
DisinCases []multCaseItem `json:"disinCases"`
|
||||
LimitCasesFlag int `json:"limitCasesFlag"`
|
||||
LimitCases []multCaseItem `json:"limitCases"`
|
||||
}
|
||||
|
||||
// multCaseItem 司法各类公告的通用记录结构
|
||||
type multCaseItem struct {
|
||||
CaseNumber string `json:"caseNumber"`
|
||||
CaseType string `json:"caseType"`
|
||||
Court string `json:"court"`
|
||||
LitigantType string `json:"litigantType"`
|
||||
FilingTime string `json:"filingTime"`
|
||||
DisposalTime string `json:"disposalTime"`
|
||||
CaseStatus string `json:"caseStatus"`
|
||||
ExecutionAmount string `json:"executionAmount"`
|
||||
RepaidAmount string `json:"repaidAmount"`
|
||||
CaseReason string `json:"caseReason"`
|
||||
DisposalMethod string `json:"disposalMethod"`
|
||||
JudgmentResult string `json:"judgmentResult"`
|
||||
}
|
||||
|
||||
// --- FLXG7E8F ---
|
||||
|
||||
// judicialProductData 对应 FLXG7E8F 司法产品数据
|
||||
@@ -702,52 +731,60 @@ func buildBasicInfo(ctx context.Context, sourceCtx *sourceContext) reportBasicIn
|
||||
Details: carrierDetails,
|
||||
})
|
||||
|
||||
// 兼容处理:安全访问JudicialData
|
||||
var stat *lawsuitStat
|
||||
if sourceCtx != nil && sourceCtx.JudicialData != nil {
|
||||
stat = &sourceCtx.JudicialData.JudicialData.LawsuitStat
|
||||
}
|
||||
|
||||
totalCaseCount := 0
|
||||
totalCriminal := 0
|
||||
// 兼容处理:从DWBG8B4D.multCourtInfo获取司法记录条数,并按案件类型拆分
|
||||
totalExecution := 0
|
||||
if stat != nil {
|
||||
// 兼容处理:安全访问Cases数组
|
||||
totalCriminal = safeLen(stat.Criminal.Cases)
|
||||
totalCaseCount = totalCriminal + safeLen(stat.Civil.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases)
|
||||
totalExecution = safeLen(stat.Implement.Cases)
|
||||
}
|
||||
|
||||
totalDishonest := 0
|
||||
totalRestriction := 0
|
||||
if sourceCtx != nil && sourceCtx.JudicialData != nil {
|
||||
totalDishonest = safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList)
|
||||
totalRestriction = safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList)
|
||||
criminalCount := 0
|
||||
civilCount := 0
|
||||
administrativeCount := 0
|
||||
preservationCount := 0
|
||||
bankruptCount := 0
|
||||
|
||||
if sourceCtx != nil && sourceCtx.BaseData != nil {
|
||||
mc := sourceCtx.BaseData.MultCourtInfo
|
||||
totalExecution = safeLen(mc.ExecutionCases)
|
||||
totalDishonest = safeLen(mc.DisinCases)
|
||||
totalRestriction = safeLen(mc.LimitCases)
|
||||
|
||||
for _, c := range mc.LegalCases {
|
||||
switch strings.TrimSpace(c.CaseType) {
|
||||
case "刑事案件":
|
||||
criminalCount++
|
||||
case "民事案件":
|
||||
civilCount++
|
||||
case "行政案件":
|
||||
administrativeCount++
|
||||
case "保全审查":
|
||||
preservationCount++
|
||||
case "破产清算":
|
||||
bankruptCount++
|
||||
default:
|
||||
// 其它类型暂不单独展示,只参与是否有司法记录的判断
|
||||
civilCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if totalCaseCount > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
||||
detailParts := make([]string, 0, 5)
|
||||
if stat != nil {
|
||||
addCaseDetail := func(label string, count int) {
|
||||
if count > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count))
|
||||
}
|
||||
totalLegal := criminalCount + civilCount + administrativeCount + preservationCount + bankruptCount
|
||||
|
||||
if totalLegal > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
||||
detailParts := make([]string, 0, 8)
|
||||
addCaseDetail := func(label string, count int) {
|
||||
if count > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count))
|
||||
}
|
||||
addCaseDetail("刑事案件", safeLen(stat.Criminal.Cases))
|
||||
addCaseDetail("民事案件", safeLen(stat.Civil.Cases))
|
||||
addCaseDetail("行政案件", safeLen(stat.Administrative.Cases))
|
||||
addCaseDetail("非诉保全审查案件", safeLen(stat.Preservation.Cases))
|
||||
addCaseDetail("强制清算与破产案件", safeLen(stat.Bankrupt.Cases))
|
||||
}
|
||||
if totalExecution > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("执行案件%d条", totalExecution))
|
||||
}
|
||||
if totalDishonest > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("失信案件%d条", totalDishonest))
|
||||
}
|
||||
if totalRestriction > 0 {
|
||||
detailParts = append(detailParts, fmt.Sprintf("限高案件%d条", totalRestriction))
|
||||
}
|
||||
|
||||
addCaseDetail("刑事案件", criminalCount)
|
||||
addCaseDetail("民事案件", civilCount)
|
||||
addCaseDetail("行政案件", administrativeCount)
|
||||
addCaseDetail("保全审查案件", preservationCount)
|
||||
addCaseDetail("强制清算与破产案件", bankruptCount)
|
||||
addCaseDetail("执行案件", totalExecution)
|
||||
addCaseDetail("失信案件", totalDishonest)
|
||||
addCaseDetail("限高案件", totalRestriction)
|
||||
|
||||
details := buildCaseDetails(detailParts)
|
||||
verifications = append(verifications, verificationItem{
|
||||
Item: "法院信息",
|
||||
@@ -821,50 +858,78 @@ func buildRiskIdentification(ctx context.Context, sourceCtx *sourceContext) risk
|
||||
},
|
||||
}
|
||||
|
||||
// 兼容处理:安全访问JudicialData
|
||||
if sourceCtx == nil || sourceCtx.JudicialData == nil {
|
||||
log.Debug("JudicialData为空,返回空的风险识别数据",
|
||||
// 兼容处理:从DWBG8B4D.multCourtInfo获取司法信息
|
||||
if sourceCtx == nil || sourceCtx.BaseData == nil {
|
||||
log.Debug("BaseData为空,返回空的风险识别数据",
|
||||
zap.String("api_code", "COMBHZY2"),
|
||||
)
|
||||
return identification
|
||||
}
|
||||
|
||||
stat := sourceCtx.JudicialData.JudicialData.LawsuitStat
|
||||
mc := sourceCtx.BaseData.MultCourtInfo
|
||||
baseName := ""
|
||||
baseID := ""
|
||||
if sourceCtx.BaseData != nil {
|
||||
baseName = sourceCtx.BaseData.BaseInfo.Name
|
||||
baseID = sourceCtx.BaseData.BaseInfo.IdCard
|
||||
}
|
||||
baseName = sourceCtx.BaseData.BaseInfo.Name
|
||||
baseID = sourceCtx.BaseData.BaseInfo.IdCard
|
||||
|
||||
// 兼容处理:安全访问Cases数组
|
||||
// 涉案公告列表:直接使用multCourtInfo.legalCases
|
||||
caseRecords := make([]caseAnnouncementRecord, 0)
|
||||
if stat.Civil.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...)
|
||||
}
|
||||
if stat.Criminal.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Criminal.Cases, "刑事案件")...)
|
||||
}
|
||||
if stat.Administrative.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Administrative.Cases, "行政案件")...)
|
||||
}
|
||||
if stat.Preservation.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Preservation.Cases, "非诉保全审查")...)
|
||||
}
|
||||
if stat.Bankrupt.Cases != nil {
|
||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Bankrupt.Cases, "强制清算与破产")...)
|
||||
for _, c := range mc.LegalCases {
|
||||
record := caseAnnouncementRecord{
|
||||
CaseNumber: defaultIfEmpty(c.CaseNumber, "-"),
|
||||
CaseType: defaultIfEmpty(c.CaseType, "-"),
|
||||
FilingDate: defaultIfEmpty(c.FilingTime, ""),
|
||||
Authority: defaultIfEmpty(c.Court, ""),
|
||||
}
|
||||
caseRecords = append(caseRecords, record)
|
||||
}
|
||||
identification.CaseAnnouncements.Records = caseRecords
|
||||
|
||||
if stat.Implement.Cases != nil {
|
||||
identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases)
|
||||
// 执行公告列表:multCourtInfo.executionCases
|
||||
enfRecords := make([]enforcementAnnouncementRecord, 0, len(mc.ExecutionCases))
|
||||
for _, c := range mc.ExecutionCases {
|
||||
amountStr := strings.TrimSpace(c.ExecutionAmount)
|
||||
targetAmount := "-"
|
||||
if amountStr != "" && amountStr != "-" {
|
||||
targetAmount = formatCurrencyYuan(parseFloatSafe(amountStr))
|
||||
}
|
||||
record := enforcementAnnouncementRecord{
|
||||
CaseNumber: defaultIfEmpty(c.CaseNumber, "-"),
|
||||
TargetAmount: targetAmount,
|
||||
FilingDate: defaultIfEmpty(c.FilingTime, ""),
|
||||
Court: defaultIfEmpty(c.Court, ""),
|
||||
Status: defaultIfEmpty(c.CaseStatus, "-"),
|
||||
}
|
||||
enfRecords = append(enfRecords, record)
|
||||
}
|
||||
if sourceCtx.JudicialData.JudicialData.BreachCaseList != nil {
|
||||
identification.DishonestAnnouncements.Records = convertDishonestAnnouncements(sourceCtx.JudicialData.JudicialData.BreachCaseList, baseName, baseID)
|
||||
identification.EnforcementAnnouncements.Records = enfRecords
|
||||
|
||||
// 失信公告列表:multCourtInfo.disinCases
|
||||
dishonestRecords := make([]dishonestAnnouncementRecord, 0, len(mc.DisinCases))
|
||||
for _, item := range mc.DisinCases {
|
||||
record := dishonestAnnouncementRecord{
|
||||
DishonestPerson: defaultIfEmpty(baseName, "-"),
|
||||
IdCard: defaultIfEmpty(baseID, "-"),
|
||||
Court: defaultIfEmpty(item.Court, ""),
|
||||
FilingDate: defaultIfEmpty(item.FilingTime, item.DisposalTime),
|
||||
PerformanceStatus: defaultIfEmpty(item.JudgmentResult, defaultIfEmpty(item.CaseStatus, "-")),
|
||||
}
|
||||
dishonestRecords = append(dishonestRecords, record)
|
||||
}
|
||||
if sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList != nil {
|
||||
identification.HighConsumptionRestrictionAnn.Records = convertConsumptionRestrictions(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList, baseName, baseID)
|
||||
identification.DishonestAnnouncements.Records = dishonestRecords
|
||||
|
||||
// 限高公告列表:multCourtInfo.limitCases
|
||||
limitRecords := make([]highRestrictionAnnouncementRecord, 0, len(mc.LimitCases))
|
||||
for _, item := range mc.LimitCases {
|
||||
record := highRestrictionAnnouncementRecord{
|
||||
RestrictedPerson: defaultIfEmpty(baseName, "-"),
|
||||
IdCard: defaultIfEmpty(baseID, "-"),
|
||||
Court: defaultIfEmpty(item.Court, ""),
|
||||
StartDate: defaultIfEmpty(item.FilingTime, item.DisposalTime),
|
||||
Measure: "限制高消费",
|
||||
}
|
||||
limitRecords = append(limitRecords, record)
|
||||
}
|
||||
identification.HighConsumptionRestrictionAnn.Records = limitRecords
|
||||
|
||||
return identification
|
||||
}
|
||||
@@ -1299,13 +1364,13 @@ func gatherOtherRiskDetails(sourceCtx *sourceContext) string {
|
||||
if risk.VeryFrequentRentalApplications > 0 {
|
||||
hits = append(hits, "租赁机构申请次数极多")
|
||||
}
|
||||
// 兼容处理:安全访问JudicialData
|
||||
if sourceCtx != nil && sourceCtx.JudicialData != nil {
|
||||
stat := sourceCtx.JudicialData.JudicialData.LawsuitStat
|
||||
totalCase := safeLen(stat.Civil.Cases) + safeLen(stat.Criminal.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases)
|
||||
totalExecution := safeLen(stat.Implement.Cases)
|
||||
totalDishonest := safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList)
|
||||
totalRestriction := safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList)
|
||||
// 兼容处理:根据DWBG8B4D.multCourtInfo判断是否存在司法记录
|
||||
if sourceCtx != nil && sourceCtx.BaseData != nil {
|
||||
mc := sourceCtx.BaseData.MultCourtInfo
|
||||
totalCase := safeLen(mc.LegalCases)
|
||||
totalExecution := safeLen(mc.ExecutionCases)
|
||||
totalDishonest := safeLen(mc.DisinCases)
|
||||
totalRestriction := safeLen(mc.LimitCases)
|
||||
if totalCase > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
||||
hits = append(hits, "存在司法风险记录")
|
||||
}
|
||||
@@ -1393,11 +1458,10 @@ func buildRuleHitBullet(summary reportSummary) (string, bool, bool) {
|
||||
}
|
||||
|
||||
func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) {
|
||||
if ctx.JudicialData == nil {
|
||||
if ctx == nil || ctx.BaseData == nil {
|
||||
return "", false, false
|
||||
}
|
||||
|
||||
stat := ctx.JudicialData.JudicialData.LawsuitStat
|
||||
mc := ctx.BaseData.MultCourtInfo
|
||||
parts := make([]string, 0, 6)
|
||||
|
||||
addPart := func(label string, count int) {
|
||||
@@ -1406,12 +1470,10 @@ func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
addPart("刑事案件", len(stat.Criminal.Cases))
|
||||
addPart("民事案件", len(stat.Civil.Cases))
|
||||
addPart("行政案件", len(stat.Administrative.Cases))
|
||||
addPart("执行案件", len(stat.Implement.Cases))
|
||||
addPart("失信记录", len(ctx.JudicialData.JudicialData.BreachCaseList))
|
||||
addPart("限高记录", len(ctx.JudicialData.JudicialData.ConsumptionRestrictionList))
|
||||
addPart("涉案公告", len(mc.LegalCases))
|
||||
addPart("执行案件", len(mc.ExecutionCases))
|
||||
addPart("失信记录", len(mc.DisinCases))
|
||||
addPart("限高记录", len(mc.LimitCases))
|
||||
|
||||
if len(parts) == 0 {
|
||||
return "", false, false
|
||||
|
||||
@@ -4,9 +4,12 @@ import (
|
||||
"context"
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
"tyapi-server/internal/infrastructure/external/yushan"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
@@ -26,12 +29,15 @@ type CallContext struct {
|
||||
// ProcessorDependencies 处理器依赖容器
|
||||
type ProcessorDependencies struct {
|
||||
WestDexService *westdex.WestDexService
|
||||
ShujubaoService *shujubao.ShujubaoService
|
||||
MuziService *muzi.MuziService
|
||||
YushanService *yushan.YushanService
|
||||
TianYanChaService *tianyancha.TianYanChaService
|
||||
AlicloudService *alicloud.AlicloudService
|
||||
ZhichaService *zhicha.ZhichaService
|
||||
XingweiService *xingwei.XingweiService
|
||||
JiguangService *jiguang.JiguangService
|
||||
ShumaiService *shumai.ShumaiService
|
||||
Validator interfaces.RequestValidator
|
||||
CombService CombServiceInterface // Changed to interface to break import cycle
|
||||
Options *commands.ApiCallOptions // 添加Options支持
|
||||
@@ -41,23 +47,29 @@ type ProcessorDependencies struct {
|
||||
// NewProcessorDependencies 创建处理器依赖容器
|
||||
func NewProcessorDependencies(
|
||||
westDexService *westdex.WestDexService,
|
||||
shujubaoService *shujubao.ShujubaoService,
|
||||
muziService *muzi.MuziService,
|
||||
yushanService *yushan.YushanService,
|
||||
tianYanChaService *tianyancha.TianYanChaService,
|
||||
alicloudService *alicloud.AlicloudService,
|
||||
zhichaService *zhicha.ZhichaService,
|
||||
xingweiService *xingwei.XingweiService,
|
||||
jiguangService *jiguang.JiguangService,
|
||||
shumaiService *shumai.ShumaiService,
|
||||
validator interfaces.RequestValidator,
|
||||
combService CombServiceInterface, // Changed to interface
|
||||
) *ProcessorDependencies {
|
||||
return &ProcessorDependencies{
|
||||
WestDexService: westDexService,
|
||||
ShujubaoService: shujubaoService,
|
||||
MuziService: muziService,
|
||||
YushanService: yushanService,
|
||||
TianYanChaService: tianYanChaService,
|
||||
AlicloudService: alicloudService,
|
||||
ZhichaService: zhichaService,
|
||||
XingweiService: xingweiService,
|
||||
JiguangService: jiguangService,
|
||||
ShumaiService: shumaiService,
|
||||
Validator: validator,
|
||||
CombService: combService,
|
||||
Options: nil, // 初始化为nil,在调用时设置
|
||||
|
||||
@@ -54,7 +54,7 @@ func ProcessDWBG6A2CRequest(ctx context.Context, params []byte, deps *processors
|
||||
if respMap, ok := respData.(map[string]interface{}); ok {
|
||||
delete(respMap, "reportUrl")
|
||||
delete(respMap, "multCourtInfo")
|
||||
delete(respMap, "judiciaRiskInfos")
|
||||
// delete(respMap, "judiciaRiskInfos")
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
219
internal/domains/api/services/processors/dwbg/谛听报告_风险标识判断标准.md
Normal file
219
internal/domains/api/services/processors/dwbg/谛听报告_风险标识判断标准.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# 谛听多维报告(DWBG8B4D)风险标识判断标准
|
||||
|
||||
本文档汇总 `dwbg8b4d_processor.go` 中所有风险标识(*Flag / riskFlag)的判定规则与数据来源,便于查阅和评审。
|
||||
|
||||
---
|
||||
|
||||
## 一、elementVerificationDetail(要素核验产品)内风险标识
|
||||
|
||||
### 1. sjsysFlag(手机三要素简版风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 未查得 | 无 YYSYH6D2 数据或无法解析 |
|
||||
| 1 | 高风险 | YYSYH6D2.result ≠ "0"(身份证+手机号+姓名 不一致) |
|
||||
| 2 | 低风险 | YYSYH6D2.result == "0"(三要素一致) |
|
||||
|
||||
**数据源**:YYSYH6D2(手机三要素简版)
|
||||
|
||||
---
|
||||
|
||||
### 2. sfzeysFlag(身份证二要素风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 未查得 | 无 IVYZ9K7F 数据或 status/desc/result 无法解析 |
|
||||
| 1 | 高风险 | 核验结果不为「一致」 |
|
||||
| 2 | 低风险 | 核验结果为「一致」 |
|
||||
|
||||
**数据源**:IVYZ9K7F(身份证二要素)。status 来自 data.status、desc 或 result(0=一致,非0=不一致)。
|
||||
|
||||
---
|
||||
|
||||
### 3. onlineRiskFlag(手机在网时长风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 未查得 | 无 YYSY8B1C 数据或 inTime 为空 |
|
||||
| 1 | 高风险 | inTime == "0" 或 "3"(在网时长极短) |
|
||||
| 2 | 低风险 | inTime 为其他非空值 |
|
||||
|
||||
**数据源**:YYSY8B1C(手机在网时长)
|
||||
|
||||
---
|
||||
|
||||
### 4. phoneVailRiskFlag(手机信息验证风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 未查得 | 无 YYSYE7V5 数据或 status 无法解析 |
|
||||
| 1 | 高风险 | status == 1(不在网) |
|
||||
| 2 | 低风险 | status == 0(在网/实号) |
|
||||
|
||||
**数据源**:YYSYE7V5(手机在网状态等)
|
||||
|
||||
---
|
||||
|
||||
### 5. belongRiskFlag(身份证号与手机号归属地风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 未查得 | 无 YYSY9E4A 或 YYSYH6D2,或户籍/归属地任一方为空 |
|
||||
| 1 | 高风险 | 户籍所在地与号码归属地**不一致**(经智能比较) |
|
||||
| 2 | 低风险 | 户籍所在地与号码归属地**一致** |
|
||||
|
||||
**数据源**:YYSY9E4A(号码归属地)、YYSYH6D2(address 作户籍)。使用 `compareLocation` 做省/市归一化后比较。
|
||||
|
||||
---
|
||||
|
||||
### 6. highRiskFlag(公安重点人员核验风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 无风险 | keyPersonCheckList 五项(fontFlag、jingJiFontFlag、fangAiFlag、zhongDianFlag、sheJiaoTongFlag)**均为 0** |
|
||||
| 1 | 高风险 | **任一项** fontFlag / jingJiFontFlag / fangAiFlag / zhongDianFlag 为 1(涉刑/经侦/妨害/重点/涉交通以外) |
|
||||
| 2 | 低风险 | **仅** sheJiaoTongFlag 为 1(仅涉交通命中),其余为 0 |
|
||||
|
||||
**数据源**:FLXGDEA9 的 level 解析后填充 keyPersonCheckList(A→fontFlag, B→jingJiFontFlag, C→fangAiFlag, D→zhongDianFlag, E→sheJiaoTongFlag)。**不看是否有数据**,仅根据五项当前值判定。
|
||||
|
||||
---
|
||||
|
||||
## 二、overdueRiskProduct(逾期风险产品)内风险标识
|
||||
|
||||
### 7. lyjlhyFlag(履约/借贷逾期类风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 未查得 | 无 JRZQ5E9F 数据(默认) |
|
||||
| 1 | 高风险 | 当前存在未结清逾期 **或** 近 7/14/30 天有逾期(hasUnsettledOverdue=="逾期" 或 overdueLast7Days/14Days/30Days 任一为「逾期」) |
|
||||
| 2 | 低风险 | 有 JRZQ5E9F 数据且无上述逾期情况 |
|
||||
|
||||
**数据源**:JRZQ5E9F(借选指数评估),字段:xyp_cpl0044、xyp_cpl0029、xyp_cpl0030、xyp_cpl0031 等。
|
||||
|
||||
---
|
||||
|
||||
### 8. dkzhktjFlag(贷款综合情况风险标识)
|
||||
|
||||
与 **lyjlhyFlag** 使用同一套逻辑,同时赋值:有逾期或近期逾期则为 1,否则为 2。
|
||||
|
||||
**数据源**:JRZQ5E9F。
|
||||
|
||||
---
|
||||
|
||||
### 9. tsmdyzFlag(特殊名单验证风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 未查得 | 无 JRZQ8A2D 数据(默认) |
|
||||
| 1 | 高风险 | specialListVerification 列表**非空**(身份证或手机号任一侧命中:法院失信人/被执行人、银行/非银中风险/一般风险/高风险等任一) |
|
||||
| 2 | 低风险 | 有 JRZQ8A2D 数据且 specialListVerification 为空 |
|
||||
|
||||
**数据源**:JRZQ8A2D(特殊名单验证 B)。命中规则:id/cell 下对应字段值为 "0" 表示命中(如 court_bad、court_executed、bank_lost 等)。
|
||||
|
||||
---
|
||||
|
||||
## 三、multCourtInfo(司法风险核验产品)内风险标识
|
||||
|
||||
### 10. legalCasesFlag(涉案公告案件风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 无 | 无 FLXG7E8F 或 lawsuitStat 下无民事/刑事/行政/保全/破产案件 |
|
||||
| 1 | 高风险 | legalCases 列表**非空**(民事、刑事、行政、保全、破产任一类 cases 非空) |
|
||||
|
||||
**数据源**:FLXG7E8F(个人司法涉诉查询)→ judicial_data.lawsuitStat → civil/criminal/administrative/preservation/bankrupt.cases。
|
||||
|
||||
---
|
||||
|
||||
### 11. executionCasesFlag(执行案件风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 无 | 无 FLXG7E8F 或 implement.cases 为空 |
|
||||
| 1 | 高风险 | executionCases 列表**非空** |
|
||||
|
||||
**数据源**:FLXG7E8F → judicial_data.lawsuitStat.implement.cases。
|
||||
|
||||
---
|
||||
|
||||
### 12. disinCasesFlag(失信案件风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 无 | 无 FLXG7E8F 或 breachCaseList 为空 |
|
||||
| 1 | 高风险 | disinCases 列表**非空** |
|
||||
|
||||
**数据源**:FLXG7E8F → judicial_data.breachCaseList。
|
||||
|
||||
---
|
||||
|
||||
### 13. limitCasesFlag(限高案件风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 无 | 无 FLXG7E8F 或 consumptionRestrictionList 为空 |
|
||||
| 1 | 高风险 | limitCases 列表**非空** |
|
||||
|
||||
**数据源**:FLXG7E8F → judicial_data.consumptionRestrictionList。
|
||||
|
||||
---
|
||||
|
||||
## 四、loanEvaluationVerificationDetail(借贷评估产品)内风险标识
|
||||
|
||||
### 14. riskFlag(借贷评估风险标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 未查得 | 无 JRZQ6F2A 或 risk_screen_v2.variables[0].variableValue 无法解析 |
|
||||
| 1 | 高风险 | checkLoanRisk 为 true:**任一时间段**(d7/d15/m1/m3)下,银行或非银申请次数 **≥ 10**(als_{period}_id_bank_allnum / als_{period}_id_nbank_allnum) |
|
||||
| 2 | 低风险 | 有数据且 checkLoanRisk 为 false |
|
||||
|
||||
**数据源**:JRZQ6F2A(借贷意向验证 A)→ risk_screen_v2.variables[0].variableValue。
|
||||
|
||||
---
|
||||
|
||||
## 五、leasingRiskAssessment(租赁风险评估产品)内风险标识
|
||||
|
||||
### 15. riskFlag(租赁风险评估标识)
|
||||
|
||||
| 取值 | 含义 | 判断条件 |
|
||||
|------|--------|----------|
|
||||
| 0 | 无风险 | 近 12 月(m12)**总次数为 0**(身份证维度 alc_m12_id_allnum 与手机号维度 alc_m12_cell_allnum 取**较大值**后为 0) |
|
||||
| 1 | 高风险 | 近 12 月总次数 **> 10** |
|
||||
| 2 | 低风险 | 近 12 月总次数 **1~10**(含 10) |
|
||||
|
||||
**数据源**:JRZQ1D09(3C 租赁申请意向)。**仅用近 12 月(Last12 / m12)**,totalCount = max(idCount, cellCount)。
|
||||
|
||||
---
|
||||
|
||||
## 六、riskWarning(规则风险提示)内各项命中规则摘要
|
||||
|
||||
以下为「命中即置 1」的规则,未命中或未查得均为 0。不改变上述各产品内 *Flag 的取值含义。
|
||||
|
||||
| 类别 | 字段示例 | 命中条件(简要) |
|
||||
|----------------|----------|------------------|
|
||||
| 要素核查 | idCardTwoElementMismatch | IVYZ9K7F 二要素结果非「一致」 |
|
||||
| | phoneThreeElementMismatch | YYSYH6D2.result ≠ "0" |
|
||||
| 运营商/在网 | shortPhoneDuration | YYSY8B1C.inTime == "0" |
|
||||
| | shortPhoneDurationSlight | YYSY8B1C.inTime == "3" |
|
||||
| | noPhoneDuration | YYSYE7V5.status==1 且 desc 含「风险」 |
|
||||
| 归属地 | idCardPhoneProvinceMismatch | 户籍与号码归属地智能比较不一致 |
|
||||
| 公安重点人员 | hasCriminalRecord / isEconomyFront / isDisrupSocial / isKeyPerson / isTrafficRelated | FLXGDEA9.level 解析后 A/B/C/D/E 前缀 |
|
||||
| 涉赌涉诈 | isAntiFraudInfo | FLXG8B4D 中 moneyLaundering/deceiver/gamblerPlayer/gamblerBanker 任一项非空且非"0" |
|
||||
| 逾期/名单 | hitHighRiskBankLastTwoYears / hitHighRiskNonBankLastTwoYears / hitCurrentOverdue | JRZQ8A2D.id 下 bank_lost/nbank_lost/bank_overdue/nbank_overdue 等字段值为 "0"(命中) |
|
||||
| 司法 | hitCivilCase / hitCriminalRisk / hitExecutionCase / hitAdministrativeCase / hitPreservationReview / hitBankruptcyAndLiquidation | FLXG7E8F 对应案件类型 **cases 数组非空**(不依 count) |
|
||||
| 借贷意向 | frequentApplicationRecent | 近 d7/d15/m1 银行或非银申请次数 **≥ 10**(JRZQ6F2A) |
|
||||
| | frequentBankApplications / moreFrequentBankApplications | 近 m6+m12 银行申请总次数 **≥ 20** / **≥ 15** |
|
||||
| | frequentNonBankApplications / moreFrequentNonBankApplications | 近 m6+m12 非银申请总次数 **≥ 20** / **≥ 15** |
|
||||
| 租赁申请 | veryFrequentRentalApplications / frequentRentalApplications | JRZQ1D09 近 m3+m6+m12(每期取 id/cell 较大值再累加)总次数 **≥ 20** / **≥ 15** |
|
||||
| 偿债压力 | highDebtPressure | JRZQ5E9F 当前逾期金额或当前逾期机构数非空且非 "0"/"1" |
|
||||
|
||||
---
|
||||
|
||||
## 七、风险取值约定(通用)
|
||||
|
||||
- **0**:未查得 / 无风险 / 无命中(依字段语义)
|
||||
- **1**:高风险 / 命中
|
||||
- **2**:低风险(仅部分 *Flag 使用,如 sjsysFlag、sfzeysFlag、onlineRiskFlag、phoneVailRiskFlag、belongRiskFlag、highRiskFlag、lyjlhyFlag、dkzhktjFlag、tsmdyzFlag、loanEvaluation riskFlag、leasingRiskAssessment riskFlag)
|
||||
|
||||
文档版本:根据当前 `dwbg8b4d_processor.go` 逻辑整理,若有代码变更请同步更新本文档。
|
||||
@@ -24,7 +24,7 @@ func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998"{
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
|
||||
@@ -20,7 +20,7 @@ func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"|| paramsDto.IDCard == "320682198910134998"{
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
|
||||
@@ -20,7 +20,7 @@ func ProcessFLXG7E8FRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"|| paramsDto.IDCard == "320682198910134998" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
|
||||
@@ -20,7 +20,7 @@ func ProcessFLXGCA3DRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"|| paramsDto.IDCard == "320682198910134998" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
|
||||
@@ -25,7 +25,7 @@ func ProcessFLXGDEA9Request(ctx context.Context, params []byte, deps *processors
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"|| paramsDto.IDCard == "320682198910134998"{
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessIVYZ0S0DRequest IVYZ0S0D API处理方法 - 劳动仲裁信息查询(个人版)
|
||||
func ProcessIVYZ0S0DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ0S0DReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"id": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "labor-arbitration-information", "labor-arbitration-information", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessIVYZ1J7HRequest IVYZ1J7H API处理方法 - 行驶证核查v2
|
||||
func ProcessIVYZ1J7HRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ1J7HReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"plate": paramsDto.PlateNo,
|
||||
"plateType": paramsDto.CarPlateType,
|
||||
"name": paramsDto.Name,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-driving-license-v2", "vehicle/driving-license-v2", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -4,13 +4,26 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessIVYZ2A8BRequest IVYZ2A8B API处理方法 - 身份二要素认证
|
||||
// ivyz2a8bShumaiResp 数脉 /v4/id_card/check 返回格式
|
||||
type ivyz2a8bShumaiResp struct {
|
||||
Result float64 `json:"result"`
|
||||
OrderNo string `json:"order_no"`
|
||||
Desc string `json:"desc"`
|
||||
Sex string `json:"sex"`
|
||||
Birthday string `json:"birthday"` // yyyyMMdd
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// ProcessIVYZ2A8BRequest IVYZ2A8B API处理方法 - 身份二要素认证政务版 数脉内部替换
|
||||
func ProcessIVYZ2A8BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ2A8BReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
@@ -20,37 +33,129 @@ func ProcessIVYZ2A8BRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
reqFormData := map[string]interface{}{
|
||||
"idcard": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
|
||||
// 先尝试使用政务接口(app_id2 和 app_secret2)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, true)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI001", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
// 使用实时接口(app_id 和 app_secret)重试
|
||||
respBytes, err = deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, false)
|
||||
// 如果重试后仍然失败,或者原本就是查无记录错误,返回错误
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
// 将数脉返回的新格式映射为原有 API 输出格式
|
||||
oldFormat, err := mapIVYZ2A8BShumaiToOld(respBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
return json.Marshal(oldFormat)
|
||||
}
|
||||
|
||||
func mapIVYZ2A8BShumaiToOld(respBytes []byte) (map[string]interface{}, error) {
|
||||
var r ivyz2a8bShumaiResp
|
||||
if err := json.Unmarshal(respBytes, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// final_auth_result: "0"=一致,"1"=不一致/无记录;按 result:0-一致,1-不一致,2-无记录(预留)
|
||||
finalAuth := "1"
|
||||
switch int(r.Result) {
|
||||
case 0:
|
||||
finalAuth = "0"
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"final_auth_result": finalAuth,
|
||||
"birthday": formatBirthdayOld(r.Birthday),
|
||||
"address": r.Address,
|
||||
"constellation": constellationFromBirthday(r.Birthday),
|
||||
"gender": r.Sex,
|
||||
"age": ageFromBirthday(r.Birthday),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func formatBirthdayOld(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) != 8 {
|
||||
return s
|
||||
}
|
||||
for _, c := range s {
|
||||
if c < '0' || c > '9' {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return s[0:4] + "年" + s[4:6] + "月" + s[6:8] + "日"
|
||||
}
|
||||
|
||||
func constellationFromBirthday(s string) string {
|
||||
if len(s) != 8 {
|
||||
return ""
|
||||
}
|
||||
month, _ := strconv.Atoi(s[4:6])
|
||||
day, _ := strconv.Atoi(s[6:8])
|
||||
if month < 1 || month > 12 || day < 1 || day > 31 {
|
||||
return ""
|
||||
}
|
||||
switch {
|
||||
case (month == 12 && day >= 22) || (month == 1 && day <= 19):
|
||||
return "魔羯座"
|
||||
case (month == 1 && day >= 20) || (month == 2 && day <= 18):
|
||||
return "水瓶座"
|
||||
case (month == 2 && day >= 19) || (month == 3 && day <= 20):
|
||||
return "双鱼座"
|
||||
case (month == 3 && day >= 21) || (month == 4 && day <= 19):
|
||||
return "白羊座"
|
||||
case (month == 4 && day >= 20) || (month == 5 && day <= 20):
|
||||
return "金牛座"
|
||||
case (month == 5 && day >= 21) || (month == 6 && day <= 21):
|
||||
return "双子座"
|
||||
case (month == 6 && day >= 22) || (month == 7 && day <= 22):
|
||||
return "巨蟹座"
|
||||
case (month == 7 && day >= 23) || (month == 8 && day <= 22):
|
||||
return "狮子座"
|
||||
case (month == 8 && day >= 23) || (month == 9 && day <= 22):
|
||||
return "处女座"
|
||||
case (month == 9 && day >= 23) || (month == 10 && day <= 23):
|
||||
return "天秤座"
|
||||
case (month == 10 && day >= 24) || (month == 11 && day <= 22):
|
||||
return "天蝎座"
|
||||
case (month == 11 && day >= 23) || (month == 12 && day <= 21):
|
||||
return "射手座"
|
||||
default:
|
||||
return "魔羯座"
|
||||
}
|
||||
}
|
||||
|
||||
func ageFromBirthday(s string) string {
|
||||
if len(s) < 4 {
|
||||
return ""
|
||||
}
|
||||
y, err := strconv.Atoi(s[0:4])
|
||||
if err != nil || y <= 0 {
|
||||
return ""
|
||||
}
|
||||
age := time.Now().Year() - y
|
||||
if age < 0 {
|
||||
age = 0
|
||||
}
|
||||
return strconv.Itoa(age)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors
|
||||
if returnType == "" {
|
||||
returnType = "1"
|
||||
}
|
||||
paramSign := map[string]interface{}{
|
||||
"returnType": returnType,
|
||||
"realName": encryptedName,
|
||||
"certCode": encryptedCertCode,
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"realName": encryptedName,
|
||||
@@ -43,7 +48,8 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors
|
||||
"returnType": returnType,
|
||||
}
|
||||
|
||||
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", reqData)
|
||||
|
||||
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", "/academic",reqData,paramSign)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, muzi.ErrDatasource):
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessIVYZ81NCRequest IVYZ81NC API处理方法
|
||||
@@ -21,45 +21,75 @@ func ProcessIVYZ81NCRequest(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idcard": encryptedIDCard,
|
||||
},
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"authorized": "1", // 默认值
|
||||
}
|
||||
|
||||
const maxRetries = 5
|
||||
var respBytes []byte
|
||||
|
||||
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||
var err error
|
||||
respBytes, err = deps.WestDexService.CallAPI(ctx, "G09XM02", reqData)
|
||||
if err == nil {
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
// 如果不是数据源异常,直接返回错误
|
||||
if !errors.Is(err, westdex.ErrDatasource) {
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI029", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 如果是最后一次尝试,返回错误
|
||||
if attempt == maxRetries {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
|
||||
// 立即重试,不等待
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
// 解析响应数据,期望格式为 {"state": "1"}
|
||||
var stateResp struct {
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
// 将 respData 转换为 JSON 字节再解析
|
||||
respDataBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(respDataBytes, &stateResp); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 根据 state 值转换为 81nc 格式
|
||||
var opType, opTypeDesc string
|
||||
switch stateResp.State {
|
||||
case "1": // 已婚
|
||||
opType = "IA"
|
||||
opTypeDesc = "结婚"
|
||||
case "2": // 未婚/未在民政局登记
|
||||
opType = "INR"
|
||||
opTypeDesc = "匹配不成功"
|
||||
case "3": // 离异
|
||||
opType = "IB"
|
||||
opTypeDesc = "离婚"
|
||||
default:
|
||||
opType = "INR"
|
||||
opTypeDesc = "匹配不成功"
|
||||
}
|
||||
|
||||
// 构建 81nc 格式响应
|
||||
result := map[string]interface{}{
|
||||
"code": "0",
|
||||
"data": map[string]interface{}{
|
||||
"op_date": "",
|
||||
"op_type": opType,
|
||||
"op_type_desc": opTypeDesc,
|
||||
},
|
||||
"message": "成功",
|
||||
"seqNo": "",
|
||||
}
|
||||
|
||||
// 返回 81nc 格式响应
|
||||
return json.Marshal(result)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessIVYZ9H2MRequest IVYZ9H2M API处理方法 - 极光个人婚姻查询(V2版)
|
||||
func ProcessIVYZ9H2MRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ9H2MReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"id_no": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
// apiCode: marriage-single-v2 (用于请求头)
|
||||
// apiPath: marriage/single-v2 (用于URL路径)
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "marriage-single-v2", "marriage/single-v2", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessIVYZ9K7FRequest IVYZ9K7F 身份证实名认证即时版 API处理方法
|
||||
func ProcessIVYZ9K7FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ9K7FReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
reqFormData := map[string]interface{}{
|
||||
"idcard": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
}
|
||||
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessIVYZA1B3Request IVYZA1B3 公安三要素人脸识别API处理方法
|
||||
func ProcessIVYZA1B3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZA1B3Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
reqFormData := map[string]interface{}{
|
||||
"idcard": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
"image": paramsDto.PhotoData,
|
||||
}
|
||||
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/face_id_card/compare" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
)
|
||||
|
||||
// ProcessIVYZBPQ2Request IVYZBPQ2 人脸比对V2API处理方法
|
||||
func ProcessIVYZBPQ2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZBPQ2Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求数据,使用xingwei服务的正确字段名
|
||||
reqData := map[string]interface{}{
|
||||
"name": paramsDto.Name,
|
||||
"idCardNum": paramsDto.IDCard,
|
||||
"image": paramsDto.PhotoData,
|
||||
}
|
||||
|
||||
// 调用行为数据API,使用指定的project_id
|
||||
projectID := "CDJ-1104321425593790464"
|
||||
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, xingwei.ErrNotFound) {
|
||||
// 查空情况,返回特定的查空错误
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessIVYZN2P8Request IVYZN2P8 身份证实名认证政务版 API处理方法
|
||||
func ProcessIVYZN2P8Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ9K7FReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
reqFormData := map[string]interface{}{
|
||||
"idcard": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
}
|
||||
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
|
||||
// 先尝试使用政务接口(app_id2 和 app_secret2)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, true)
|
||||
if err != nil {
|
||||
// 使用实时接口(app_id 和 app_secret)重试
|
||||
respBytes, err = deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, false)
|
||||
// 如果重试后仍然失败,返回错误
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessIVYZOCR1Request IVYZOCR1 身份证OCR API 处理方法(使用数据宝服务示例)
|
||||
func ProcessIVYZOCR1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZOCR1Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "8782f2a32463f75b53096323461df735",
|
||||
"imageId": paramsDto.PhotoData,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/trade/user/1985"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessIVYZOCR2Request IVYZOCR2 OCR识别API处理方法
|
||||
func ProcessIVYZOCR2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZOCR1Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
if paramsDto.PhotoData == "" && paramsDto.ImageUrl == "" {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, errors.New("photo_data or image_url is required"))
|
||||
}
|
||||
|
||||
// 2选1:有值的用对应 key,空则用另一个
|
||||
reqFormData := make(map[string]interface{})
|
||||
if paramsDto.PhotoData != "" {
|
||||
reqFormData["image"] = paramsDto.PhotoData
|
||||
} else {
|
||||
reqFormData["url"] = paramsDto.ImageUrl
|
||||
}
|
||||
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/idcard/ocr" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessIVYZP2Q6Request IVYZP2Q6 API处理方法 - 身份认证二要素
|
||||
@@ -21,37 +21,64 @@ func ProcessIVYZP2Q6Request(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 加密姓名
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
reqFormData := map[string]interface{}{
|
||||
"idcard": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
}
|
||||
|
||||
// 加密身份证号
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI011", reqData)
|
||||
// 先尝试使用政务接口(app_id2 和 app_secret2)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
// 使用实时接口(app_id 和 app_secret)重试
|
||||
respBytes, err = deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, false)
|
||||
// 如果重试后仍然失败,返回错误
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
// 数据源返回 result(0-一致/1-不一致/2-无记录),映射为 state(1-匹配/2-不匹配/3-异常情况)
|
||||
var dsResp struct {
|
||||
Result int `json:"result"` // 0-一致 1-不一致 2-无记录(预留)
|
||||
}
|
||||
if err := json.Unmarshal(respBytes, &dsResp); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
state := resultToState(dsResp.Result)
|
||||
|
||||
out := map[string]interface{}{
|
||||
"errMsg": "",
|
||||
"state": state,
|
||||
}
|
||||
return json.Marshal(out)
|
||||
}
|
||||
|
||||
// resultToState 将数据源 result 映射为接口 state:1-匹配 2-不匹配 3-异常情况
|
||||
func resultToState(result int) int {
|
||||
switch result {
|
||||
case 0: // 一致 → 匹配
|
||||
return 1
|
||||
case 1: // 不一致 → 不匹配
|
||||
return 2
|
||||
case 2: // 无记录(预留) → 异常情况
|
||||
return 3
|
||||
default:
|
||||
return 3 // 未知/异常 → 异常情况
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
)
|
||||
|
||||
// ProcessIVYZSFELRequest IVYZSFEL 全国自然人人像三要素核验_V1API处理方法
|
||||
func ProcessIVYZSFELRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZSFELReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求数据,使用xingwei服务的正确字段名
|
||||
reqData := map[string]interface{}{
|
||||
"name": paramsDto.Name,
|
||||
"idCardNum": paramsDto.IDCard,
|
||||
"photo": paramsDto.PhotoData,
|
||||
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
|
||||
}
|
||||
|
||||
// 调用行为数据API,使用指定的project_id
|
||||
projectID := "CDJ-1068350101927161856"
|
||||
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, xingwei.ErrNotFound) {
|
||||
// 查空情况,返回特定的查空错误
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
"tyapi-server/internal/shared/logger"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProcessIVYZX5Q2Request IVYZX5Q2 活体识别步骤二API处理方法
|
||||
func ProcessIVYZX5Q2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZX5Q2Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
reqFormData := map[string]interface{}{
|
||||
"token": paramsDto.Token,
|
||||
}
|
||||
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/liveness/h5/v4/result" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// result==2 时手动抛出错误(不通过/无记录,不返回正常响应)
|
||||
var body struct {
|
||||
Result int `json:"result"`
|
||||
}
|
||||
if err := json.Unmarshal(respBytes, &body); err == nil && body.Result == 2 {
|
||||
log := logger.GetGlobalLogger()
|
||||
log.Warn("IVYZX5Q2 活体检测 result=2 无记录或不通过,返回错误",
|
||||
zap.Int("result", body.Result),
|
||||
zap.ByteString("response", respBytes))
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("活体检测 result=2 无记录或不通过"))
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessIVYZx5qzRequest IVYZx5qz 活体识别API处理方法
|
||||
func ProcessIVYZX5QZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZX5QZReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
reqFormData := map[string]interface{}{
|
||||
"returnUrl": paramsDto.ReturnURL,
|
||||
}
|
||||
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v4/liveness/h5/v4/token" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrNotFound) {
|
||||
// 查无记录情况
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
)
|
||||
|
||||
// ProcessIVYZZQT3Request IVYZZQT3 人脸比对V3API处理方法
|
||||
func ProcessIVYZZQT3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZZQT3Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求数据,使用xingwei服务的正确字段名
|
||||
reqData := map[string]interface{}{
|
||||
"name": paramsDto.Name,
|
||||
"idCardNum": paramsDto.IDCard,
|
||||
"image": paramsDto.PhotoData,
|
||||
}
|
||||
|
||||
// 调用行为数据API,使用指定的project_id
|
||||
projectID := "CDJ-1104321430396268544"
|
||||
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, xingwei.ErrNotFound) {
|
||||
// 查空情况,返回特定的查空错误
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||
)
|
||||
|
||||
// ProcessJRZQ1P5GRequest JRZQ1P5G 全国自然人借贷压力指数查询(2) - xingwei service
|
||||
func ProcessJRZQ1P5GRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQ1P5GReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||
reqData := map[string]interface{}{
|
||||
"name": paramsDto.Name,
|
||||
"idCardNum": paramsDto.IDCard,
|
||||
"phoneNumber": paramsDto.MobileNo,
|
||||
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
|
||||
}
|
||||
|
||||
// 调用行为数据API,使用指定的project_id
|
||||
projectID := "CDJ-1068350101704863744"
|
||||
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, xingwei.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessJRZQACABERequest JRZQACAB 银行卡四要素 API 处理方法(使用数据宝服务示例)
|
||||
func ProcessJRZQACABERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQACABReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "7eb69f73a855e41875e22f139b934c3c",
|
||||
"name": paramsDto.Name,
|
||||
"idcard": paramsDto.IDCard,
|
||||
"mobile": paramsDto.MobileNo,
|
||||
"acc_no": paramsDto.BankCard,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/9442"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -58,6 +58,5 @@ func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessJRZQO6L7Request JRZQO6L7 API处理方法 - 全国自然人经济特征评分模型v3 简版
|
||||
func ProcessJRZQO6L7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQO6L7Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
null := ""
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"authorized": paramsDto.Authorized,
|
||||
"province": null,
|
||||
"city": null,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI081", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessJRZQO7L1Request JRZQO7L1 API处理方法 - 全国自然人经济特征评分模型v4 详版
|
||||
func ProcessJRZQO7L1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQO7L1Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
null := ""
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"authorized": paramsDto.Authorized,
|
||||
"entName": paramsDto.EntName,
|
||||
"province": null,
|
||||
"city": null,
|
||||
}
|
||||
|
||||
// 使用 WithSkipCode201Check 不跳过 201 错误检查,当 Code == "201" 时返回错误
|
||||
// ctx = zhicha.WithSkipCode201Check(ctx)
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI080", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shumai"
|
||||
)
|
||||
|
||||
// ProcessJRZQOCREERequest JRZQOCRE 银行卡OCR API 数卖服务示例
|
||||
func ProcessJRZQOCREERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQOCREReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
if paramsDto.PhotoData == "" && paramsDto.ImageUrl == "" {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, errors.New("photo_data or image_url is required"))
|
||||
}
|
||||
|
||||
// 2选1:有值的用对应 key,空则用另一个
|
||||
reqFormData := make(map[string]interface{})
|
||||
if paramsDto.PhotoData != "" {
|
||||
reqFormData["image"] = paramsDto.PhotoData
|
||||
} else {
|
||||
reqFormData["url"] = paramsDto.ImageUrl
|
||||
}
|
||||
// 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
|
||||
apiPath := "/v2/bankcard/ocr" // 接口路径,根据数脉文档填写(如 v4/xxx)
|
||||
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
|
||||
if err != nil {
|
||||
if errors.Is(err, shumai.ErrDatasource) {
|
||||
// 数据源错误
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else if errors.Is(err, shumai.ErrSystem) {
|
||||
// 系统错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
} else {
|
||||
// 其他未知错误
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessJRZQOCRYERequest JRZQOCRY 银行卡OCR API 处理方法(使用数据宝服务示例)
|
||||
func ProcessJRZQOCRYERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQOCRYReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "3ee8e7a7a71870db2c0bf98e7e6b8b5c",
|
||||
"imageId": paramsDto.PhotoData,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/trade/user/1986"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessJRZQS7G0Request JRZQS7G0 API处理方法 -社保综合评分V1
|
||||
func ProcessJRZQS7G0Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQS7G0Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
// 使用 WithSkipCode201Check 不跳过 201 错误检查,当 Code == "201" 时返回错误
|
||||
// ctx = zhicha.WithSkipCode201Check(ctx)
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI082", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, zhicha.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
// 将响应数据转换为JSON字节
|
||||
respBytes, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
package pdfg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/pdfgen"
|
||||
"tyapi-server/internal/shared/logger"
|
||||
"tyapi-server/internal/shared/pdf"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProcessPDFG01GZRequest PDFG01GZ 处理器 - 大数据租赁风险PDF报告
|
||||
func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.PDFG01GZReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 获取全局logger
|
||||
zapLogger := logger.GetGlobalLogger()
|
||||
|
||||
// Debug:记录入口参数(使用 Info 级别便于线上查看)
|
||||
logger.L().Info("PDFG01GZ请求开始",
|
||||
zap.String("name", paramsDto.Name),
|
||||
zap.String("id_card", paramsDto.IDCard),
|
||||
zap.String("mobile_no", paramsDto.MobileNo),
|
||||
zap.String("authorized", paramsDto.Authorized),
|
||||
)
|
||||
|
||||
// 从context获取config(如果存在)
|
||||
var cacheTTL time.Duration = 24 * time.Hour
|
||||
var cacheDir string
|
||||
var apiDomain string
|
||||
if cfg, ok := ctx.Value("config").(*config.Config); ok && cfg != nil {
|
||||
cacheTTL = cfg.PDFGen.Cache.TTL
|
||||
if cacheTTL == 0 {
|
||||
cacheTTL = 24 * time.Hour
|
||||
}
|
||||
cacheDir = cfg.PDFGen.Cache.CacheDir
|
||||
apiDomain = cfg.API.Domain
|
||||
}
|
||||
|
||||
// 获取最大缓存大小
|
||||
var maxSize int64
|
||||
if cfg, ok := ctx.Value("config").(*config.Config); ok && cfg != nil {
|
||||
maxSize = cfg.PDFGen.Cache.MaxSize
|
||||
|
||||
// Debug:记录PDF生成服务配置(使用 Info 级别便于线上查看)
|
||||
logger.L().Info("PDFG01GZ加载配置",
|
||||
zap.String("env", cfg.App.Env),
|
||||
zap.String("pdfgen_production_url", cfg.PDFGen.ProductionURL),
|
||||
zap.String("pdfgen_development_url", cfg.PDFGen.DevelopmentURL),
|
||||
zap.String("pdfgen_api_path", cfg.PDFGen.APIPath),
|
||||
zap.Duration("pdfgen_timeout", cfg.PDFGen.Timeout),
|
||||
zap.String("cache_dir", cacheDir),
|
||||
zap.Duration("cache_ttl", cacheTTL),
|
||||
zap.Int64("cache_max_size", maxSize),
|
||||
)
|
||||
}
|
||||
|
||||
// 创建PDF缓存管理器
|
||||
cacheManager, err := pdf.NewPDFCacheManager(zapLogger, cacheDir, cacheTTL, maxSize)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("创建PDF缓存管理器失败: %w", err))
|
||||
}
|
||||
|
||||
// 从context获取config创建PDF生成服务
|
||||
var pdfGenService *pdfgen.PDFGenService
|
||||
if cfg, ok := ctx.Value("config").(*config.Config); ok && cfg != nil {
|
||||
pdfGenService = pdfgen.NewPDFGenService(cfg, zapLogger)
|
||||
} else {
|
||||
// 如果无法获取config,使用默认配置
|
||||
defaultCfg := &config.Config{
|
||||
App: config.AppConfig{Env: "development"},
|
||||
PDFGen: config.PDFGenConfig{
|
||||
DevelopmentURL: "http://pdfg.tianyuanapi.com",
|
||||
ProductionURL: "http://localhost:15990",
|
||||
APIPath: "/api/v1/generate/guangzhou",
|
||||
Timeout: 120 * time.Second,
|
||||
},
|
||||
}
|
||||
pdfGenService = pdfgen.NewPDFGenService(defaultCfg, zapLogger)
|
||||
}
|
||||
|
||||
// 直接生成PDF,不检查缓存(每次都重新生成)
|
||||
zapLogger.Info("开始生成PDF",
|
||||
zap.String("name", paramsDto.Name),
|
||||
zap.String("id_card", paramsDto.IDCard),
|
||||
)
|
||||
|
||||
// 调用多个处理器获取数据(即使部分失败也继续)
|
||||
apiData := collectAPIData(ctx, paramsDto, deps, zapLogger)
|
||||
|
||||
// 格式化数据为PDF生成服务需要的格式(为缺失的数据提供默认值)
|
||||
formattedData := formatDataForPDF(apiData, paramsDto, zapLogger)
|
||||
|
||||
// 打印完整的apiData(为避免日志过大,这里直接序列化为JSON字符串)
|
||||
apiDataBytes, _ := json.Marshal(apiData)
|
||||
logger.L().Info("PDFG01GZ数据准备完成",
|
||||
zap.Int("api_data_count", len(apiData)),
|
||||
zap.Int("formatted_items", len(formattedData)),
|
||||
zap.ByteString("api_data", apiDataBytes),
|
||||
)
|
||||
|
||||
// 从APPLICANT_BASIC_INFO中提取报告编号(如果存在)
|
||||
var reportNumber string
|
||||
if len(formattedData) > 0 {
|
||||
if basicInfo, ok := formattedData[0]["data"].(map[string]interface{}); ok {
|
||||
if rn, ok := basicInfo["report_number"].(string); ok {
|
||||
reportNumber = rn
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果没有提取到,生成新的报告编号
|
||||
if reportNumber == "" {
|
||||
reportNumber = generateReportNumber()
|
||||
}
|
||||
|
||||
// 构建PDF生成请求
|
||||
pdfReq := &pdfgen.GeneratePDFRequest{
|
||||
Data: formattedData,
|
||||
ReportNumber: reportNumber,
|
||||
GenerateTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
// 调用PDF生成服务
|
||||
// 即使部分子处理器失败,只要有APPLICANT_BASIC_INFO就可以生成PDF
|
||||
logger.L().Info("PDFG01GZ开始调用PDF生成服务",
|
||||
zap.String("report_number", reportNumber),
|
||||
zap.Int("data_items", len(formattedData)),
|
||||
)
|
||||
pdfResp, err := pdfGenService.GenerateGuangzhouPDF(ctx, pdfReq)
|
||||
if err != nil {
|
||||
zapLogger.Error("生成PDF失败",
|
||||
zap.Error(err),
|
||||
zap.Int("data_items", len(formattedData)),
|
||||
)
|
||||
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("生成PDF失败: %w", err))
|
||||
}
|
||||
|
||||
// 生成报告ID(每次请求都生成唯一的ID)
|
||||
reportID := generateReportID()
|
||||
|
||||
// 保存到缓存(基于报告ID,文件名包含时间戳确保唯一性)
|
||||
if err := cacheManager.SetByReportID(reportID, pdfResp.PDFBytes); err != nil {
|
||||
zapLogger.Warn("保存PDF到缓存失败", zap.Error(err))
|
||||
// 不影响返回结果,只记录警告
|
||||
}
|
||||
|
||||
// 生成下载链接(基于报告ID)
|
||||
downloadURL := generateDownloadURL(apiDomain, reportID)
|
||||
expiresAt := time.Now().Add(cacheTTL)
|
||||
|
||||
zapLogger.Info("PDF生成成功",
|
||||
zap.String("name", paramsDto.Name),
|
||||
zap.String("id_card", paramsDto.IDCard),
|
||||
zap.String("report_id", reportID),
|
||||
zap.String("report_number", reportNumber),
|
||||
zap.String("download_url", downloadURL),
|
||||
)
|
||||
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"download_url": downloadURL,
|
||||
"report_id": reportID,
|
||||
"report_number": reportNumber,
|
||||
"expires_at": expiresAt.Format(time.RFC3339),
|
||||
"ttl_seconds": int(cacheTTL.Seconds()),
|
||||
})
|
||||
}
|
||||
|
||||
// collectAPIData 收集所有需要的API数据
|
||||
// 即使部分或全部子处理器失败,也会返回结果(失败的设为nil),确保流程继续
|
||||
func collectAPIData(ctx context.Context, params dto.PDFG01GZReq, deps *processors.ProcessorDependencies, logger *zap.Logger) map[string]interface{} {
|
||||
apiData := make(map[string]interface{})
|
||||
|
||||
// 并发调用多个处理器
|
||||
type processorResult struct {
|
||||
apiCode string
|
||||
data interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
results := make(chan processorResult, 5)
|
||||
|
||||
// 调用JRZQ0L85 - 需要: name, id_card, mobile_no
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("调用JRZQ0L85处理器时发生panic",
|
||||
zap.Any("panic", r),
|
||||
)
|
||||
results <- processorResult{"JRZQ0L85", nil, fmt.Errorf("处理器panic: %v", r)}
|
||||
}
|
||||
}()
|
||||
jrzq0l85Params := map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile_no": params.MobileNo,
|
||||
}
|
||||
paramsBytes, err := json.Marshal(jrzq0l85Params)
|
||||
if err != nil {
|
||||
logger.Warn("序列化JRZQ0L85参数失败", zap.Error(err))
|
||||
results <- processorResult{"JRZQ0L85", nil, err}
|
||||
return
|
||||
}
|
||||
data, err := callProcessor(ctx, "JRZQ0L85", paramsBytes, deps)
|
||||
results <- processorResult{"JRZQ0L85", data, err}
|
||||
}()
|
||||
|
||||
// 调用JRZQ8A2D - 需要: name, id_card, mobile_no, authorized
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("调用JRZQ8A2D处理器时发生panic",
|
||||
zap.Any("panic", r),
|
||||
)
|
||||
results <- processorResult{"JRZQ8A2D", nil, fmt.Errorf("处理器panic: %v", r)}
|
||||
}
|
||||
}()
|
||||
jrzq8a2dParams := map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile_no": params.MobileNo,
|
||||
"authorized": params.Authorized,
|
||||
}
|
||||
paramsBytes, err := json.Marshal(jrzq8a2dParams)
|
||||
if err != nil {
|
||||
logger.Warn("序列化JRZQ8A2D参数失败", zap.Error(err))
|
||||
results <- processorResult{"JRZQ8A2D", nil, err}
|
||||
return
|
||||
}
|
||||
data, err := callProcessor(ctx, "JRZQ8A2D", paramsBytes, deps)
|
||||
results <- processorResult{"JRZQ8A2D", data, err}
|
||||
}()
|
||||
|
||||
// 调用FLXGDEA9 - 需要: name, id_card, authorized
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("调用FLXGDEA9处理器时发生panic",
|
||||
zap.Any("panic", r),
|
||||
)
|
||||
results <- processorResult{"FLXGDEA9", nil, fmt.Errorf("处理器panic: %v", r)}
|
||||
}
|
||||
}()
|
||||
flxgParams := map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"authorized": params.Authorized,
|
||||
}
|
||||
paramsBytes, err := json.Marshal(flxgParams)
|
||||
if err != nil {
|
||||
logger.Warn("序列化FLXGDEA9参数失败", zap.Error(err))
|
||||
results <- processorResult{"FLXGDEA9", nil, err}
|
||||
return
|
||||
}
|
||||
data, err := callProcessor(ctx, "FLXGDEA9", paramsBytes, deps)
|
||||
results <- processorResult{"FLXGDEA9", data, err}
|
||||
}()
|
||||
|
||||
// 调用JRZQ1D09 - 需要: name, id_card, mobile_no, authorized
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("调用JRZQ1D09处理器时发生panic",
|
||||
zap.Any("panic", r),
|
||||
)
|
||||
results <- processorResult{"JRZQ1D09", nil, fmt.Errorf("处理器panic: %v", r)}
|
||||
}
|
||||
}()
|
||||
jrzq1d09Params := map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile_no": params.MobileNo,
|
||||
"authorized": params.Authorized,
|
||||
}
|
||||
paramsBytes, err := json.Marshal(jrzq1d09Params)
|
||||
if err != nil {
|
||||
logger.Warn("序列化JRZQ1D09参数失败", zap.Error(err))
|
||||
results <- processorResult{"JRZQ1D09", nil, err}
|
||||
return
|
||||
}
|
||||
data, err := callProcessor(ctx, "JRZQ1D09", paramsBytes, deps)
|
||||
results <- processorResult{"JRZQ1D09", data, err}
|
||||
}()
|
||||
|
||||
// 调用JRZQ8B3C - 需要: name, id_card, mobile_no (不需要authorized)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("调用JRZQ8B3C处理器时发生panic",
|
||||
zap.Any("panic", r),
|
||||
)
|
||||
results <- processorResult{"JRZQ8B3C", nil, fmt.Errorf("处理器panic: %v", r)}
|
||||
}
|
||||
}()
|
||||
jrzq8b3cParams := map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile_no": params.MobileNo,
|
||||
}
|
||||
paramsBytes, err := json.Marshal(jrzq8b3cParams)
|
||||
if err != nil {
|
||||
logger.Warn("序列化JRZQ8B3C参数失败", zap.Error(err))
|
||||
results <- processorResult{"JRZQ8B3C", nil, err}
|
||||
return
|
||||
}
|
||||
data, err := callProcessor(ctx, "JRZQ8B3C", paramsBytes, deps)
|
||||
results <- processorResult{"JRZQ8B3C", data, err}
|
||||
}()
|
||||
|
||||
// 收集结果,即使所有处理器都失败也继续
|
||||
successCount := 0
|
||||
for i := 0; i < 5; i++ {
|
||||
result := <-results
|
||||
if result.err != nil {
|
||||
// 记录错误但不中断流程,允许部分数据缺失
|
||||
logger.Warn("调用处理器失败,将使用默认值",
|
||||
zap.String("api_code", result.apiCode),
|
||||
zap.Error(result.err),
|
||||
)
|
||||
apiData[result.apiCode] = nil
|
||||
} else {
|
||||
apiData[result.apiCode] = result.data
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("子处理器调用完成",
|
||||
zap.Int("total", 5),
|
||||
zap.Int("success", successCount),
|
||||
zap.Int("failed", 5-successCount),
|
||||
)
|
||||
|
||||
return apiData
|
||||
}
|
||||
|
||||
// callProcessor 调用指定的处理器
|
||||
func callProcessor(ctx context.Context, apiCode string, params []byte, deps *processors.ProcessorDependencies) (interface{}, error) {
|
||||
// 通过CombService获取处理器
|
||||
if combSvc, ok := deps.CombService.(interface {
|
||||
GetProcessor(apiCode string) (processors.ProcessorFunc, bool)
|
||||
}); ok {
|
||||
processor, exists := combSvc.GetProcessor(apiCode)
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("未找到处理器: %s", apiCode)
|
||||
}
|
||||
respBytes, err := processor(ctx, params, deps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data interface{}
|
||||
if err := json.Unmarshal(respBytes, &data); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// 如果无法通过CombService获取,返回错误
|
||||
return nil, fmt.Errorf("无法获取处理器: %s,CombService不支持GetProcessor方法", apiCode)
|
||||
}
|
||||
|
||||
// formatDataForPDF 格式化数据为PDF生成服务需要的格式
|
||||
// 为所有子处理器提供数据,即使失败也提供默认值,确保PDF生成服务能收到完整结构
|
||||
func formatDataForPDF(apiData map[string]interface{}, params dto.PDFG01GZReq, logger *zap.Logger) []map[string]interface{} {
|
||||
result := make([]map[string]interface{}, 0)
|
||||
|
||||
// 1. APPLICANT_BASIC_INFO - 申请人基本信息(始终存在)
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "APPLICANT_BASIC_INFO",
|
||||
"data": map[string]interface{}{
|
||||
"name": params.Name,
|
||||
"id_card": params.IDCard,
|
||||
"mobile": params.MobileNo,
|
||||
"query_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
"report_number": generateReportNumber(),
|
||||
"generate_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
})
|
||||
|
||||
// 2. JRZQ0L85 - 自然人综合风险智能评估模型(替代原IVYZ5A9O)
|
||||
if data, ok := apiData["JRZQ0L85"]; ok && data != nil {
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "JRZQ0L85",
|
||||
"data": data,
|
||||
})
|
||||
} else {
|
||||
// 子处理器失败或无数据时,返回空对象 {}
|
||||
logger.Debug("JRZQ0L85数据缺失,使用空对象")
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "JRZQ0L85",
|
||||
"data": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
// 3. JRZQ8A2D - 特殊名单验证B
|
||||
if data, ok := apiData["JRZQ8A2D"]; ok && data != nil {
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "JRZQ8A2D",
|
||||
"data": data,
|
||||
})
|
||||
} else {
|
||||
logger.Debug("JRZQ8A2D数据缺失,使用空对象")
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "JRZQ8A2D",
|
||||
"data": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
// 4. FLXGDEA9 - 公安不良人员名单
|
||||
if data, ok := apiData["FLXGDEA9"]; ok && data != nil {
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "FLXGDEA9",
|
||||
"data": data,
|
||||
})
|
||||
} else {
|
||||
logger.Debug("FLXGDEA9数据缺失,使用空对象")
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "FLXGDEA9",
|
||||
"data": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
// 5. JRZQ1D09 - 3C租赁申请意向
|
||||
if data, ok := apiData["JRZQ1D09"]; ok && data != nil {
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "JRZQ1D09",
|
||||
"data": data,
|
||||
})
|
||||
} else {
|
||||
logger.Debug("JRZQ1D09数据缺失,使用空对象")
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "JRZQ1D09",
|
||||
"data": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
// 6. JRZQ8B3C - 个人消费能力等级
|
||||
if data, ok := apiData["JRZQ8B3C"]; ok && data != nil {
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "JRZQ8B3C",
|
||||
"data": data,
|
||||
})
|
||||
} else {
|
||||
logger.Debug("JRZQ8B3C数据缺失,使用空对象")
|
||||
result = append(result, map[string]interface{}{
|
||||
"apiID": "JRZQ8B3C",
|
||||
"data": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// generateReportNumber 生成报告编号
|
||||
func generateReportNumber() string {
|
||||
return fmt.Sprintf("RPT%s", time.Now().Format("20060102150405"))
|
||||
}
|
||||
|
||||
// generateReportID 生成对外可见的报告ID
|
||||
// 每次请求都生成唯一的ID,格式:{report_number}-{随机字符串}
|
||||
// 注意:不再包含cacheKey,因为每次请求都会重新生成,不需要通过ID定位缓存文件
|
||||
func generateReportID() string {
|
||||
reportNumber := generateReportNumber()
|
||||
// 生成8字节随机字符串,确保每次请求ID都不同
|
||||
randomBytes := make([]byte, 8)
|
||||
if _, err := rand.Read(randomBytes); err != nil {
|
||||
// 如果随机数生成失败,使用纳秒时间戳作为后备
|
||||
randomBytes = []byte(fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||
}
|
||||
randomStr := hex.EncodeToString(randomBytes)
|
||||
return fmt.Sprintf("%s-%s", reportNumber, randomStr)
|
||||
}
|
||||
|
||||
// generateDownloadURL 生成下载链接(基于报告ID/缓存键)
|
||||
// apiDomain: 外部可访问的API域名,如 api.tianyuanapi.com
|
||||
func generateDownloadURL(apiDomain, reportID string) string {
|
||||
if apiDomain == "" {
|
||||
// 兜底:保留相对路径,方便本地/测试环境使用
|
||||
return fmt.Sprintf("/api/v1/pdfg/download?id=%s", reportID)
|
||||
}
|
||||
// 生成完整链接,例如:https://api.tianyuanapi.com/api/v1/pdfg/download?id=xxx
|
||||
return fmt.Sprintf("https://%s/api/v1/pdfg/download?id=%s", apiDomain, reportID)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessQCXG1H7YRequest QCXG1H7Y API处理方法 - 车辆过户简版查询
|
||||
func ProcessQCXG1H7YRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG1H7YReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"vin": paramsDto.VinCode,
|
||||
"plateNumber": paramsDto.PlateNo,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
// apiCode: car-vin (用于请求头)
|
||||
// apiPath: car/car-vin (用于URL路径)
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-transfer", "vehicle/transfer", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessQCXG1U4URequest QCXG1U4U API处理方法 - 车辆里程记录(混合查询)
|
||||
func ProcessQCXG1U4URequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG1U4UReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"vin": paramsDto.VinCode,
|
||||
"licensePlate": paramsDto.PlateNo,
|
||||
"callbackUrl": paramsDto.ReturnURL,
|
||||
"imageUrl": paramsDto.ImageURL,
|
||||
"regUrl": paramsDto.RegURL,
|
||||
"engine": paramsDto.EngineNumber,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-mileage-b", "vehicle/car-mileage-b", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessQCXG2T6SRequest QCXG2T6S API处理方法 - 车辆里程记录(品牌查询)
|
||||
func ProcessQCXG2T6SRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG2T6SReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"vin": paramsDto.VinCode,
|
||||
"licensePlate": paramsDto.PlateNo,
|
||||
"callbackUrl": paramsDto.ReturnURL,
|
||||
"imageUrl": paramsDto.ImageURL,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-mileage-a", "vehicle/car-mileage-a", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessQCXG3B8ZRequest QCXG3B8Z 疑似运营车辆查询(月度里程)10268 API 处理方法(使用数据宝服务示例)
|
||||
func ProcessQCXG3B8ZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG3B8ZReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "c94605174cfe29bb2a62e2600b7d1596",
|
||||
"carNo": paramsDto.PlateNo,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/10268"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessQCXG3M7ZRequest QCXG3M7Z 人车关系核验(ETC)10093 月更 API 处理方法(使用数据宝服务示例)
|
||||
func ProcessQCXG3M7ZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG3M7ZReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建数据宝入参:姓名、身份证、手机号、银行卡号(sign 外的业务参数可按需 AES 加密后作为 bodyData)
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "a2f32fc54b44ebc85b97a2aaff1734ec",
|
||||
"carNo": paramsDto.PlateNo,
|
||||
"name": paramsDto.Name,
|
||||
"plateColor": paramsDto.PlateColor,
|
||||
}
|
||||
|
||||
// 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197
|
||||
apiPath := "/communication/personal/10093"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessQCXG3Y6BRequest QCXG3Y6B API处理方法 - 车辆维保简版查询
|
||||
func ProcessQCXG3Y6BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG1U4UReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"vin": paramsDto.VinCode,
|
||||
"licensePlate": paramsDto.PlateNo,
|
||||
"notifyUrl": paramsDto.ReturnURL,
|
||||
"imageUrl": paramsDto.ImageURL,
|
||||
"regUrl": paramsDto.RegURL,
|
||||
"engine": paramsDto.EngineNumber,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-maintenance-info-v2", "vehicle/car-maintenance-info-v2", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessQCXG3Z3LRequest QCXG3Z3L API处理方法 - 车辆维保详细版查询
|
||||
func ProcessQCXG3Z3LRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG3Z3LReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"vin": paramsDto.VinCode,
|
||||
"notifyUrl": paramsDto.ReturnURL,
|
||||
"imageUrl": paramsDto.ImageURL,
|
||||
"plateNo": paramsDto.PlateNo,
|
||||
"engine": paramsDto.EngineNumber,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-maintenance-info-v2", "vehicle/car-maintenance-info-v2", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/muzi"
|
||||
)
|
||||
|
||||
// ProcessQCXG4896MRequest QCXG4896 API处理方法 - 网约车风险查询
|
||||
func ProcessQCXG4896Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG4896Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
|
||||
paramSign := map[string]interface{}{
|
||||
"paramName": "licenseNo",
|
||||
"paramValue": paramsDto.PlateNo,
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"paramName": "licenseNo",
|
||||
"paramValue": paramsDto.PlateNo,
|
||||
"startTime": strings.Split(paramsDto.AuthDate, "-")[0],
|
||||
"endTime": strings.Split(paramsDto.AuthDate, "-")[1],
|
||||
}
|
||||
|
||||
respData, err := deps.MuziService.CallAPI(ctx, "PC0031", "/hailingScoreBySearch", reqData,paramSign)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, muzi.ErrDatasource):
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
case errors.Is(err, muzi.ErrSystem):
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
default:
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respData, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessQCXG4D2ERequest QCXG4D2E API处理方法 - 极光名下车辆数量查询
|
||||
func ProcessQCXG4D2ERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG4D2EReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"idNum": paramsDto.IDCard,
|
||||
"userType": paramsDto.UserType,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
// apiCode: vehicle-inquiry-under-name (用于请求头)
|
||||
// apiPath: vehicle/inquiry-under-name (用于URL路径)
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-inquiry-under-name", "vehicle/inquiry-under-name", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessQCXG4I1ZRequest QCXG4I1Z API处理方法 - 车辆过户详版查询
|
||||
func ProcessQCXG4I1ZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG4I1ZReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"vin": paramsDto.VinCode,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
// apiCode: car-vin (用于请求头)
|
||||
// apiPath: car/car-vin (用于URL路径)
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "transfer-information", "vehicle/transfer-information", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||
)
|
||||
|
||||
// ProcessQCXG5F3ARequest QCXG5F3A API处理方法 - 极光名下车辆车牌查询
|
||||
func ProcessQCXG5F3ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG5F3AReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
null := ""
|
||||
// 构建请求参数
|
||||
reqData := map[string]interface{}{
|
||||
"id_card": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
"userType": null,
|
||||
"vehicleType": null,
|
||||
"encryptionType": null,
|
||||
"encryptionContent": null,
|
||||
}
|
||||
|
||||
// 调用极光API
|
||||
// apiCode: vehicle-person-vehicles (用于请求头)
|
||||
// apiPath: vehicle/person-vehicles (用于URL路径)
|
||||
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-person-vehicles", "vehicle/person-vehicles", reqData)
|
||||
if err != nil {
|
||||
// 根据错误类型返回相应的错误
|
||||
if errors.Is(err, jiguang.ErrNotFound) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/shujubao"
|
||||
)
|
||||
|
||||
// ProcessQCXG5U0ZRequest QCXG5U0Z 车辆静态信息查询 10479 API 处理方法(使用数据宝服务)
|
||||
func ProcessQCXG5U0ZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG5U0ZReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
reqParams := map[string]interface{}{
|
||||
"key": "7c8122677476dd2621f574976f1a9fde",
|
||||
"vinList": paramsDto.VinCode,
|
||||
}
|
||||
|
||||
apiPath := "/communication/personal/10479"
|
||||
data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams)
|
||||
if err != nil {
|
||||
if errors.Is(err, shujubao.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
if errors.Is(err, shujubao.ErrQueryEmpty) {
|
||||
return nil, errors.Join(processors.ErrNotFound, err)
|
||||
}
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
respBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func ProcessQCXG7A2BRequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"cardNo": paramsDto.IDCard,
|
||||
"cardNo": paramsDto.PlateNo,
|
||||
}
|
||||
|
||||
respBytes, err := deps.YushanService.CallAPI(ctx, "CAR061", reqData)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user