Compare commits
65 Commits
15d0759cfb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6147878dfe | |||
| be47a0f045 | |||
| 810696e0f0 | |||
| 17dbaf1ccb | |||
| 18f3d10518 | |||
| 0d4953c6d3 | |||
| 3f64600f02 | |||
| 2c89b8cb26 | |||
| 09d7a4f076 | |||
| 83d0fd6587 | |||
| 0fd28054f1 | |||
| ce858983ee | |||
| 9b2bffae15 | |||
| c68ece5bea | |||
| 398d2cee74 | |||
| b6c8d93af5 | |||
| b423aa6be8 | |||
| a47c306c87 | |||
| af88bcc8eb | |||
| 89367fb2ee | |||
| 05b6623e75 | |||
| bfedec249f | |||
| 9f669a9c94 | |||
| 0f5c4f4303 | |||
| d9c2d9f103 | |||
| 7e0d58b295 | |||
| a17ff2140e | |||
| 6a2241bc66 | |||
| e57bef6609 | |||
| 81639a81e6 | |||
| aaf17321ff | |||
| a8a4ff2d37 | |||
| 619deeb456 | |||
| f12c3fb8ad | |||
| 4ce8fe4023 | |||
| 7b45b43a0e | |||
| 752b90b048 | |||
| 68def7e08b | |||
| b0e8974d6c | |||
| b41d41ddf3 | |||
| b08a63fc99 | |||
| 1f06f21faf | |||
| 3f5a126bfa | |||
| 17ff48a642 | |||
| af629e96c2 | |||
| 63252fa30f | |||
| 1cf64e831c | |||
| 577c2bc581 | |||
| 6d73dad88e | |||
| 937c812ea5 | |||
| 63e2fba464 | |||
| 9c776b8bf3 | |||
| 500264e9e5 | |||
| b90935a7c3 | |||
| c404e797f3 | |||
| ce9052f85b | |||
| 11fe48809e | |||
| 785818f73d | |||
| c10fb27b93 | |||
| 4b0ab842f4 | |||
| 4c16e7a333 | |||
| 8d0f1e6aa3 | |||
| 7fc072e608 | |||
| a53727757c | |||
| 90d0324a1a |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -26,6 +26,7 @@ Thumbs.db
|
|||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
console
|
console
|
||||||
|
worker
|
||||||
|
|
||||||
# 依赖目录
|
# 依赖目录
|
||||||
vendor/
|
vendor/
|
||||||
@@ -34,6 +35,11 @@ vendor/
|
|||||||
coverage.out
|
coverage.out
|
||||||
coverage.html
|
coverage.html
|
||||||
|
|
||||||
|
# 字体文件(大文件,不进行版本控制)
|
||||||
|
internal/shared/pdf/fonts/*.ttf
|
||||||
|
internal/shared/pdf/fonts/*.ttc
|
||||||
|
internal/shared/pdf/fonts/*.otf
|
||||||
|
|
||||||
# 其他
|
# 其他
|
||||||
*.exe
|
*.exe
|
||||||
*.dll
|
*.dll
|
||||||
|
|||||||
@@ -50,9 +50,11 @@ WORKDIR /app
|
|||||||
COPY --from=builder /app/tyapi-server .
|
COPY --from=builder /app/tyapi-server .
|
||||||
|
|
||||||
# 复制配置文件
|
# 复制配置文件
|
||||||
COPY --chown=tyapi:tyapi config.yaml .
|
COPY config.yaml .
|
||||||
COPY --chown=tyapi:tyapi configs/ ./configs/
|
COPY configs/ ./configs/
|
||||||
|
|
||||||
|
# 复制资源文件(直接从构建上下文复制,与配置文件一致)
|
||||||
|
COPY resources ./resources
|
||||||
|
|
||||||
# 暴露端口
|
# 暴露端口
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|||||||
BIN
cmd/worker/__debug_bin.exe1068760645
Normal file
BIN
cmd/worker/__debug_bin.exe1068760645
Normal file
Binary file not shown.
BIN
cmd/worker/__debug_bin.exe1835124629
Normal file
BIN
cmd/worker/__debug_bin.exe1835124629
Normal file
Binary file not shown.
BIN
cmd/worker/__debug_bin.exe4056734935
Normal file
BIN
cmd/worker/__debug_bin.exe4056734935
Normal file
Binary file not shown.
BIN
cmd/worker/__debug_bin.exe438186156
Normal file
BIN
cmd/worker/__debug_bin.exe438186156
Normal file
Binary file not shown.
@@ -19,7 +19,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TaskTypeArticlePublish = "article:publish"
|
TaskTypeArticlePublish = "article:publish"
|
||||||
|
TaskTypeAnnouncementPublish = "announcement_publish"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -78,6 +79,9 @@ func main() {
|
|||||||
mux.HandleFunc(TaskTypeArticlePublish, func(ctx context.Context, t *asynq.Task) error {
|
mux.HandleFunc(TaskTypeArticlePublish, func(ctx context.Context, t *asynq.Task) error {
|
||||||
return handleArticlePublish(ctx, t, db, logger)
|
return handleArticlePublish(ctx, t, db, logger)
|
||||||
})
|
})
|
||||||
|
mux.HandleFunc(TaskTypeAnnouncementPublish, func(ctx context.Context, t *asynq.Task) error {
|
||||||
|
return handleAnnouncementPublish(ctx, t, db, logger)
|
||||||
|
})
|
||||||
|
|
||||||
// 启动 Worker
|
// 启动 Worker
|
||||||
go func() {
|
go func() {
|
||||||
@@ -135,3 +139,55 @@ func handleArticlePublish(ctx context.Context, t *asynq.Task, db *gorm.DB, logge
|
|||||||
logger.Info("定时发布文章成功", zap.String("article_id", articleID))
|
logger.Info("定时发布文章成功", zap.String("article_id", articleID))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleAnnouncementPublish 处理公告定时发布任务
|
||||||
|
func handleAnnouncementPublish(ctx context.Context, t *asynq.Task, db *gorm.DB, logger *zap.Logger) error {
|
||||||
|
var payload map[string]interface{}
|
||||||
|
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
|
||||||
|
logger.Error("解析任务载荷失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("解析任务载荷失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
announcementID, ok := payload["announcement_id"].(string)
|
||||||
|
if !ok {
|
||||||
|
logger.Error("任务载荷中缺少公告ID")
|
||||||
|
return fmt.Errorf("任务载荷中缺少公告ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取公告
|
||||||
|
var announcement entities.Announcement
|
||||||
|
if err := db.WithContext(ctx).First(&announcement, "id = ?", announcementID).Error; err != nil {
|
||||||
|
logger.Error("获取公告失败", zap.String("announcement_id", announcementID), zap.Error(err))
|
||||||
|
return fmt.Errorf("获取公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已取消定时发布
|
||||||
|
if !announcement.IsScheduled() {
|
||||||
|
logger.Info("公告定时发布已取消,跳过执行",
|
||||||
|
zap.String("announcement_id", announcementID),
|
||||||
|
zap.String("status", string(announcement.Status)))
|
||||||
|
return nil // 静默返回,不报错
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查定时发布时间是否匹配
|
||||||
|
if announcement.ScheduledAt == nil {
|
||||||
|
logger.Info("公告没有定时发布时间,跳过执行",
|
||||||
|
zap.String("announcement_id", announcementID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发布公告
|
||||||
|
if err := announcement.Publish(); err != nil {
|
||||||
|
logger.Error("发布公告失败", zap.String("announcement_id", announcementID), zap.Error(err))
|
||||||
|
return fmt.Errorf("发布公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存更新
|
||||||
|
if err := db.WithContext(ctx).Save(&announcement).Error; err != nil {
|
||||||
|
logger.Error("保存公告失败", zap.String("announcement_id", announcementID), zap.Error(err))
|
||||||
|
return fmt.Errorf("保存公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("定时发布公告成功", zap.String("announcement_id", announcementID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
28
config.yaml
28
config.yaml
@@ -181,7 +181,7 @@ daily_ratelimit:
|
|||||||
- "0.0.0.0" # 无效IP
|
- "0.0.0.0" # 无效IP
|
||||||
- "255.255.255.255" # 广播IP
|
- "255.255.255.255" # 广播IP
|
||||||
|
|
||||||
enable_user_agent: true # 是否检查User-Agent
|
enable_user_agent: false # 是否检查User-Agent
|
||||||
blocked_user_agents: # 被阻止的User-Agent
|
blocked_user_agents: # 被阻止的User-Agent
|
||||||
- "bot" # 机器人
|
- "bot" # 机器人
|
||||||
- "crawler" # 爬虫
|
- "crawler" # 爬虫
|
||||||
@@ -198,7 +198,7 @@ daily_ratelimit:
|
|||||||
- "https://console.tianyuanapi.com" # 天元API控制台
|
- "https://console.tianyuanapi.com" # 天元API控制台
|
||||||
- "https://consoletest.tianyuanapi.com" # 天元API测试控制台
|
- "https://consoletest.tianyuanapi.com" # 天元API测试控制台
|
||||||
|
|
||||||
enable_proxy_check: true # 是否检查代理
|
enable_proxy_check: false # 是否检查代理
|
||||||
enable_geo_block: false # 是否启用地理位置阻止
|
enable_geo_block: false # 是否启用地理位置阻止
|
||||||
blocked_countries: # 被阻止的国家/地区
|
blocked_countries: # 被阻止的国家/地区
|
||||||
- "XX" # 示例国家代码
|
- "XX" # 示例国家代码
|
||||||
@@ -243,7 +243,7 @@ esign:
|
|||||||
app_id: "7439073138"
|
app_id: "7439073138"
|
||||||
app_secret: "d76e27fdd169b391e09262a0959dac5c"
|
app_secret: "d76e27fdd169b391e09262a0959dac5c"
|
||||||
server_url: "https://smlopenapi.esign.cn"
|
server_url: "https://smlopenapi.esign.cn"
|
||||||
template_id: "1fd7ed9c6d134d1db7b5af9582633d76"
|
template_id: "9f7a3f63cc5a48b085b127ba027d234d"
|
||||||
contract:
|
contract:
|
||||||
name: "天远数据API合作协议"
|
name: "天远数据API合作协议"
|
||||||
expire_days: 7
|
expire_days: 7
|
||||||
@@ -362,6 +362,28 @@ alipay:
|
|||||||
notify_url: "https://console.tianyuanapi.com/api/v1/finance/alipay/callback"
|
notify_url: "https://console.tianyuanapi.com/api/v1/finance/alipay/callback"
|
||||||
return_url: "https://console.tianyuanapi.com/api/v1/finance/alipay/return"
|
return_url: "https://console.tianyuanapi.com/api/v1/finance/alipay/return"
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 💰 微信支付配置
|
||||||
|
# ===========================================
|
||||||
|
Wxpay:
|
||||||
|
app_id: "wxa581992dc74d860e"
|
||||||
|
mch_id: "1683589176"
|
||||||
|
mch_certificate_serial_number: "1F4E8B3C39C60035D4CC154F276D03D9CC2C603D"
|
||||||
|
mch_apiv3_key: "TY8X9nP2qR5tY7uW3zA6bC4dE1flgGJ0"
|
||||||
|
mch_private_key_path: "resources/etc/wxetc_cert/apiclient_key.pem"
|
||||||
|
mch_public_key_id: "PUB_KEY_ID_0116835891762025062600211574000800"
|
||||||
|
mch_public_key_path: "resources/etc/wxetc_cert/pub_key.pem"
|
||||||
|
notify_url: "https://console.tianyuanapi.com/api/v1/pay/wechat/callback"
|
||||||
|
refund_notify_url: "https://console.tianyuanapi.com/api/v1/wechat/refund_callback"
|
||||||
|
|
||||||
|
# 微信小程序配置
|
||||||
|
WechatMini:
|
||||||
|
app_id: "wxa581992dc74d860e"
|
||||||
|
|
||||||
|
# 微信H5配置
|
||||||
|
WechatH5:
|
||||||
|
app_id: "wxa581992dc74d860e"
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# 🔍 天眼查配置
|
# 🔍 天眼查配置
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ esign:
|
|||||||
app_id: "7439073713"
|
app_id: "7439073713"
|
||||||
app_secret: "c7d8cb0d701f7890601d221e9b6edfef"
|
app_secret: "c7d8cb0d701f7890601d221e9b6edfef"
|
||||||
server_url: "https://smlopenapi.esign.cn"
|
server_url: "https://smlopenapi.esign.cn"
|
||||||
template_id: "1fd7ed9c6d134d1db7b5af9582633d76"
|
template_id: "9f7a3f63cc5a48b085b127ba027d234d"
|
||||||
contract:
|
contract:
|
||||||
name: "天远数据API合作协议"
|
name: "天远数据API合作协议"
|
||||||
expire_days: 7
|
expire_days: 7
|
||||||
@@ -67,6 +67,8 @@ westdex:
|
|||||||
key: "121a1e41fc1690dd6b90afbcacd80cf4"
|
key: "121a1e41fc1690dd6b90afbcacd80cf4"
|
||||||
secret_id: "449159"
|
secret_id: "449159"
|
||||||
secret_second_id: "296804"
|
secret_second_id: "296804"
|
||||||
|
yushan:
|
||||||
|
url: https://api2.yushanshuju.com/credit-gw/service
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# 💰 支付宝支付配置
|
# 💰 支付宝支付配置
|
||||||
# ===========================================
|
# ===========================================
|
||||||
@@ -79,6 +81,27 @@ alipay:
|
|||||||
return_url: "http://127.0.0.1:8080/api/v1/finance/alipay/return"
|
return_url: "http://127.0.0.1:8080/api/v1/finance/alipay/return"
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
# 💰 微信支付配置
|
||||||
|
# ===========================================
|
||||||
|
Wxpay:
|
||||||
|
app_id: "wxa581992dc74d860e"
|
||||||
|
mch_id: "1683589176"
|
||||||
|
mch_certificate_serial_number: "1F4E8B3C39C60035D4CC154F276D03D9CC2C603D"
|
||||||
|
mch_apiv3_key: "TY8X9nP2qR5tY7uW3zA6bC4dE1flgGJ0"
|
||||||
|
mch_private_key_path: "resources/etc/wxetc_cert/apiclient_key.pem"
|
||||||
|
mch_public_key_id: "PUB_KEY_ID_0116835891762025062600211574000800"
|
||||||
|
mch_public_key_path: "resources/etc/wxetc_cert/pub_key.pem"
|
||||||
|
notify_url: "https://bx89915628g.vicp.fun/api/v1/pay/wechat/callback"
|
||||||
|
refund_notify_url: "https://bx89915628g.vicp.fun/api/v1/wechat/refund_callback"
|
||||||
|
|
||||||
|
# 微信小程序配置
|
||||||
|
WechatMini:
|
||||||
|
app_id: "wxa581992dc74d860e"
|
||||||
|
|
||||||
|
# 微信H5配置
|
||||||
|
WechatH5:
|
||||||
|
app_id: "wxa581992dc74d860e"
|
||||||
|
# ===========================================
|
||||||
# 💰 钱包配置
|
# 💰 钱包配置
|
||||||
# ===========================================
|
# ===========================================
|
||||||
wallet:
|
wallet:
|
||||||
@@ -112,34 +135,42 @@ development:
|
|||||||
cors_allowed_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
|
cors_allowed_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
|
||||||
cors_allowed_headers: "Origin,Content-Type,Accept,Authorization,X-Requested-With,Access-Id"
|
cors_allowed_headers: "Origin,Content-Type,Accept,Authorization,X-Requested-With,Access-Id"
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 🚦 开发环境全局限流(放宽或近似关闭)
|
||||||
|
# ===========================================
|
||||||
|
ratelimit:
|
||||||
|
requests: 1000000 # 每窗口允许的请求数,足够大,相当于关闭
|
||||||
|
window: 1s # 时间窗口
|
||||||
|
burst: 1000000 # 令牌桶突发容量
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# 🚀 开发环境频率限制配置(放宽限制)
|
# 🚀 开发环境频率限制配置(放宽限制)
|
||||||
# ===========================================
|
# ===========================================
|
||||||
daily_ratelimit:
|
daily_ratelimit:
|
||||||
max_requests_per_day: 1000000 # 开发环境每日最大请求次数
|
max_requests_per_day: 1000000 # 开发环境每日最大请求次数
|
||||||
max_requests_per_ip: 10000000 # 开发环境每个IP每日最大请求次数
|
max_requests_per_ip: 10000000 # 开发环境每个IP每日最大请求次数
|
||||||
max_concurrent: 50 # 开发环境最大并发请求数
|
max_concurrent: 50 # 开发环境最大并发请求数
|
||||||
|
|
||||||
# 排除频率限制的路径
|
# 排除频率限制的路径
|
||||||
exclude_paths:
|
exclude_paths:
|
||||||
- "/health" # 健康检查接口
|
- "/health" # 健康检查接口
|
||||||
- "/metrics" # 监控指标接口
|
- "/metrics" # 监控指标接口
|
||||||
|
|
||||||
# 排除频率限制的域名
|
# 排除频率限制的域名
|
||||||
exclude_domains:
|
exclude_domains:
|
||||||
- "api.*" # API二级域名不受频率限制
|
- "api.*" # API二级域名不受频率限制
|
||||||
- "*.api.*" # 支持多级API域名
|
- "*.api.*" # 支持多级API域名
|
||||||
|
|
||||||
# 开发环境安全配置(放宽限制)
|
# 开发环境安全配置(放宽限制)
|
||||||
enable_ip_whitelist: true # 启用IP白名单
|
enable_ip_whitelist: true # 启用IP白名单
|
||||||
ip_whitelist: # 开发环境IP白名单
|
ip_whitelist: # 开发环境IP白名单
|
||||||
- "127.0.0.1" # 本地回环
|
- "127.0.0.1" # 本地回环
|
||||||
- "localhost" # 本地主机
|
- "localhost" # 本地主机
|
||||||
- "192.168.*" # 内网IP段
|
- "192.168.*" # 内网IP段
|
||||||
- "10.*" # 内网IP段
|
- "10.*" # 内网IP段
|
||||||
- "172.16.*" # 内网IP段
|
- "172.16.*" # 内网IP段
|
||||||
|
|
||||||
enable_ip_blacklist: false # 开发环境禁用IP黑名单
|
enable_ip_blacklist: false # 开发环境禁用IP黑名单
|
||||||
enable_user_agent: false # 开发环境禁用User-Agent检查
|
enable_user_agent: false # 开发环境禁用User-Agent检查
|
||||||
enable_referer: false # 开发环境禁用Referer检查
|
enable_referer: false # 开发环境禁用Referer检查
|
||||||
enable_proxy_check: false # 开发环境禁用代理检查
|
enable_proxy_check: false # 开发环境禁用代理检查
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ esign:
|
|||||||
app_id: "5112008003"
|
app_id: "5112008003"
|
||||||
app_secret: "d487672273e7aa70c800804a1d9499b9"
|
app_secret: "d487672273e7aa70c800804a1d9499b9"
|
||||||
server_url: "https://openapi.esign.cn"
|
server_url: "https://openapi.esign.cn"
|
||||||
template_id: "c82af4df2790430299c81321f309eef3"
|
template_id: "9f7a3f63cc5a48b085b127ba027d234d"
|
||||||
contract:
|
contract:
|
||||||
name: "天远数据API合作协议"
|
name: "天远数据API合作协议"
|
||||||
expire_days: 7
|
expire_days: 7
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
restart: unless-stopped
|
restart:
|
||||||
|
unless-stopped
|
||||||
|
|
||||||
# Jaeger 链路追踪
|
# Jaeger 链路追踪
|
||||||
jaeger:
|
jaeger:
|
||||||
|
|||||||
261
docs/IVYZ9K2L_WestDex_API文档.md
Normal file
261
docs/IVYZ9K2L_WestDex_API文档.md
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
# IVYZ9K2L - 身份认证三要素(人脸图像版) WestDex API 文档
|
||||||
|
|
||||||
|
## 接口信息
|
||||||
|
|
||||||
|
- **接口名称**: 身份认证三要素(人脸图像版)
|
||||||
|
- **接口代码**: IVYZ9K2L
|
||||||
|
- **WestDex API Code**: `idCardThreeElements`
|
||||||
|
- **请求方式**: POST
|
||||||
|
- **Content-Type**: application/json
|
||||||
|
|
||||||
|
## 请求URL
|
||||||
|
|
||||||
|
```
|
||||||
|
https://apimaster.westdex.com.cn/api/invoke/{secret_id}/{api_code}?timestamp={timestamp}
|
||||||
|
```
|
||||||
|
|
||||||
|
### URL 参数说明
|
||||||
|
|
||||||
|
| 参数 | 说明 | 示例值 |
|
||||||
|
|------|------|--------|
|
||||||
|
| secret_id | 西部数据 SecretID(从配置获取) | `449159` |
|
||||||
|
| api_code | API代码 | `idCardThreeElements` |
|
||||||
|
| timestamp | 毫秒级时间戳(URL参数) | `1713421668375` |
|
||||||
|
|
||||||
|
### 完整URL示例
|
||||||
|
|
||||||
|
```
|
||||||
|
https://apimaster.westdex.com.cn/api/invoke/449159/idCardThreeElements?timestamp=1713421668375
|
||||||
|
```
|
||||||
|
|
||||||
|
## 请求头
|
||||||
|
|
||||||
|
```
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 请求体
|
||||||
|
|
||||||
|
### 请求体结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"timeStamp": "1713421668375",
|
||||||
|
"customNumber": "449159",
|
||||||
|
"xM": "fU4B3fR3Dw+UkHNkFsHIjA==",
|
||||||
|
"gMSFZHM": "qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg="
|
||||||
|
},
|
||||||
|
"photoData": "Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 参数说明
|
||||||
|
|
||||||
|
#### data 对象(必填)
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
||||||
|
|--------|------|------|------|--------|
|
||||||
|
| timeStamp | string | 是 | 毫秒级时间戳,与URL参数中的timestamp一致 | `"1713421668375"` |
|
||||||
|
| customNumber | string | 是 | 自定义编号,使用配置中的 secret_id | `"449159"` |
|
||||||
|
| xM | string | 是 | 加密后的姓名(使用AES加密,密钥为配置中的key) | `"fU4B3fR3Dw+UkHNkFsHIjA=="` |
|
||||||
|
| gMSFZHM | string | 是 | 加密后的身份证号(使用AES加密,密钥为配置中的key) | `"qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg="` |
|
||||||
|
|
||||||
|
#### photoData(必填)
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
||||||
|
|--------|------|------|------|--------|
|
||||||
|
| photoData | string | 是 | Base64编码的人脸图片数据,仅支持JPG、BMP、PNG格式 | `"Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..."` |
|
||||||
|
|
||||||
|
## 加密说明
|
||||||
|
|
||||||
|
### 姓名和身份证号加密
|
||||||
|
|
||||||
|
使用 AES-ECB 模式加密,密钥为配置中的 `key`(示例:`121a1e41fc1690dd6b90afbcacd80cf4`)
|
||||||
|
|
||||||
|
**加密步骤**:
|
||||||
|
1. 使用密钥生成 AES 密钥
|
||||||
|
2. 使用 AES-ECB 模式加密原始数据
|
||||||
|
3. 将加密结果进行 Base64 编码
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
- 原始姓名:`"张三"`
|
||||||
|
- 加密后:`"fU4B3fR3Dw+UkHNkFsHIjA=="`
|
||||||
|
|
||||||
|
## 完整请求示例
|
||||||
|
|
||||||
|
### cURL 示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://apimaster.westdex.com.cn/api/invoke/449159/idCardThreeElements?timestamp=1713421668375" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"data": {
|
||||||
|
"timeStamp": "1713421668375",
|
||||||
|
"customNumber": "449159",
|
||||||
|
"xM": "fU4B3fR3Dw+UkHNkFsHIjA==",
|
||||||
|
"gMSFZHM": "qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg="
|
||||||
|
},
|
||||||
|
"photoData": "Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..."
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript 示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const timestamp = Date.now().toString();
|
||||||
|
const url = `https://apimaster.westdex.com.cn/api/invoke/449159/idCardThreeElements?timestamp=${timestamp}`;
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
data: {
|
||||||
|
timeStamp: timestamp,
|
||||||
|
customNumber: "449159",
|
||||||
|
xM: "fU4B3fR3Dw+UkHNkFsHIjA==", // 加密后的姓名
|
||||||
|
gMSFZHM: "qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg=" // 加密后的身份证号
|
||||||
|
},
|
||||||
|
photoData: "Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..." // Base64图片数据
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestBody)
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => console.log(data))
|
||||||
|
.catch(error => console.error('Error:', error));
|
||||||
|
```
|
||||||
|
|
||||||
|
## 响应格式
|
||||||
|
|
||||||
|
### 成功响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "00000",
|
||||||
|
"message": "成功",
|
||||||
|
"data": "加密后的响应数据(需要解密)",
|
||||||
|
"id": "响应ID",
|
||||||
|
"error_code": null,
|
||||||
|
"reason": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "错误码",
|
||||||
|
"message": "错误信息",
|
||||||
|
"data": "加密后的错误数据(需要解密)",
|
||||||
|
"id": "响应ID",
|
||||||
|
"error_code": 错误码,
|
||||||
|
"reason": "错误原因"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应状态码说明
|
||||||
|
|
||||||
|
| 状态码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| `00000` | 成功 |
|
||||||
|
| `200` | 成功 |
|
||||||
|
| `0` | 成功 |
|
||||||
|
| 其他 | 失败 |
|
||||||
|
|
||||||
|
## 响应数据解密
|
||||||
|
|
||||||
|
响应中的 `data` 字段是加密的,需要使用相同的密钥进行解密:
|
||||||
|
|
||||||
|
**解密步骤**:
|
||||||
|
1. 使用配置中的 `key` 作为密钥
|
||||||
|
2. 对 `data` 字段进行 Base64 解码
|
||||||
|
3. 使用 AES-ECB 模式解密
|
||||||
|
4. 得到原始 JSON 字符串
|
||||||
|
|
||||||
|
## Apifox 配置步骤
|
||||||
|
|
||||||
|
### 1. 创建新请求
|
||||||
|
|
||||||
|
- 方法:`POST`
|
||||||
|
- URL:`https://apimaster.westdex.com.cn/api/invoke/449159/idCardThreeElements`
|
||||||
|
|
||||||
|
### 2. 设置URL参数
|
||||||
|
|
||||||
|
在"Params"标签页添加:
|
||||||
|
- `timestamp`: `{{$timestamp}}` (使用Apifox变量生成当前时间戳)
|
||||||
|
|
||||||
|
### 3. 设置请求头
|
||||||
|
|
||||||
|
在"Headers"标签页添加:
|
||||||
|
- `Content-Type`: `application/json`
|
||||||
|
|
||||||
|
### 4. 设置请求体
|
||||||
|
|
||||||
|
在"Body"标签页选择 `raw` 类型,格式选择 `JSON`,内容如下:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"timeStamp": "{{$timestamp}}",
|
||||||
|
"customNumber": "449159",
|
||||||
|
"xM": "fU4B3fR3Dw+UkHNkFsHIjA==",
|
||||||
|
"gMSFZHM": "qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg="
|
||||||
|
},
|
||||||
|
"photoData": "Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 配置环境变量(可选)
|
||||||
|
|
||||||
|
在Apifox中创建环境变量:
|
||||||
|
- `westdex_secret_id`: `449159`
|
||||||
|
- `westdex_key`: `121a1e41fc1690dd6b90afbcacd80cf4`
|
||||||
|
- `westdex_url`: `https://apimaster.westdex.com.cn/api/invoke`
|
||||||
|
|
||||||
|
然后在URL中使用:`{{westdex_url}}/{{westdex_secret_id}}/idCardThreeElements?timestamp={{$timestamp}}`
|
||||||
|
|
||||||
|
### 6. 前置脚本(用于生成时间戳)
|
||||||
|
|
||||||
|
在"前置脚本"中添加:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 生成毫秒级时间戳
|
||||||
|
pm.environment.set("timestamp", Date.now().toString());
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在URL参数和请求体中使用 `{{timestamp}}`
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **时间戳同步**:URL参数中的 `timestamp` 和请求体 `data.timeStamp` 必须一致
|
||||||
|
2. **加密密钥**:姓名和身份证号必须使用配置中的 `key` 进行加密
|
||||||
|
3. **图片格式**:`photoData` 必须是纯Base64字符串(不包含 `data:image/xxx;base64,` 前缀)
|
||||||
|
4. **图片格式限制**:仅支持 JPG、BMP、PNG 三种格式
|
||||||
|
5. **请求超时**:建议设置60秒超时时间
|
||||||
|
6. **响应解密**:成功响应中的 `data` 字段需要解密后才能查看实际内容
|
||||||
|
|
||||||
|
## 配置信息
|
||||||
|
|
||||||
|
根据项目配置文件,当前使用的配置为:
|
||||||
|
|
||||||
|
- **URL**: `https://apimaster.westdex.com.cn/api/invoke`
|
||||||
|
- **Key**: `121a1e41fc1690dd6b90afbcacd80cf4`
|
||||||
|
- **SecretID**: `449159`
|
||||||
|
- **SecretSecondID**: `296804`
|
||||||
|
|
||||||
|
## 测试数据示例
|
||||||
|
|
||||||
|
### 原始数据
|
||||||
|
- 姓名:`张三`
|
||||||
|
- 身份证号:`110101199001011234`
|
||||||
|
- 人脸图片:需要转换为Base64格式
|
||||||
|
|
||||||
|
### 加密后的数据(示例)
|
||||||
|
- 加密姓名:`fU4B3fR3Dw+UkHNkFsHIjA==`
|
||||||
|
- 加密身份证号:`qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg=`
|
||||||
|
|
||||||
|
**注意**:实际加密结果会根据密钥和原始数据不同而变化,以上仅为示例格式。
|
||||||
|
|
||||||
210
docs/PDF缓存优化说明.md
Normal file
210
docs/PDF缓存优化说明.md
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
# PDF接口文档下载缓存优化说明
|
||||||
|
|
||||||
|
## 📋 概述
|
||||||
|
|
||||||
|
本次优化为PDF接口文档下载功能添加了本地文件缓存机制,显著提升了下载性能,减少了重复生成PDF的开销。
|
||||||
|
|
||||||
|
## 🔍 问题分析
|
||||||
|
|
||||||
|
### 原有问题
|
||||||
|
|
||||||
|
1. **性能问题**:
|
||||||
|
- 每次请求都重新生成PDF,没有缓存机制
|
||||||
|
- PDF生成涉及复杂的字体加载、页面构建、表格渲染等操作,耗时较长
|
||||||
|
- 同一产品的PDF被多次下载时,会重复执行相同的生成过程
|
||||||
|
|
||||||
|
2. **资源浪费**:
|
||||||
|
- CPU资源浪费在重复的PDF生成上
|
||||||
|
- 数据库查询重复执行
|
||||||
|
- 没有版本控制,即使产品文档没有变化,也会重新生成
|
||||||
|
|
||||||
|
## ✅ 解决方案
|
||||||
|
|
||||||
|
### 1. PDF缓存管理器 (`PDFCacheManager`)
|
||||||
|
|
||||||
|
创建了专门的PDF缓存管理器,提供以下功能:
|
||||||
|
|
||||||
|
- **本地文件缓存**:将生成的PDF文件保存到本地文件系统
|
||||||
|
- **版本控制**:基于产品ID和文档版本号生成缓存键,确保版本更新时自动失效
|
||||||
|
- **自动过期**:支持TTL(Time To Live)机制,自动清理过期缓存
|
||||||
|
- **大小限制**:支持最大缓存大小限制,防止磁盘空间耗尽
|
||||||
|
- **定期清理**:后台任务每小时自动清理过期文件
|
||||||
|
|
||||||
|
### 2. 缓存键生成策略
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 基于产品ID和文档版本号生成唯一的缓存键
|
||||||
|
cacheKey = MD5(productID + ":" + version)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 当产品文档版本更新时,自动生成新的缓存
|
||||||
|
- 旧版本的缓存会在过期后自动清理
|
||||||
|
|
||||||
|
### 3. 缓存流程
|
||||||
|
|
||||||
|
```
|
||||||
|
请求下载PDF
|
||||||
|
↓
|
||||||
|
检查缓存是否存在且有效
|
||||||
|
↓
|
||||||
|
├─ 缓存命中 → 直接返回缓存的PDF文件
|
||||||
|
└─ 缓存未命中 → 生成PDF → 保存到缓存 → 返回PDF
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 集成到下载接口
|
||||||
|
|
||||||
|
修改了 `DownloadProductDocumentation` 方法:
|
||||||
|
|
||||||
|
- **缓存优先**:首先尝试从缓存获取PDF
|
||||||
|
- **异步保存**:生成新PDF后异步保存到缓存,不阻塞响应
|
||||||
|
- **缓存标识**:响应头中添加 `X-Cache: HIT/MISS` 标识,便于监控
|
||||||
|
|
||||||
|
## 🚀 性能提升
|
||||||
|
|
||||||
|
### 预期效果
|
||||||
|
|
||||||
|
1. **首次下载**:与之前相同,需要生成PDF(约1-3秒)
|
||||||
|
2. **后续下载**:直接从缓存读取(< 100ms),性能提升 **10-30倍**
|
||||||
|
3. **缓存命中率**:对于热门产品,缓存命中率可达 **80-90%**
|
||||||
|
|
||||||
|
### 响应时间对比
|
||||||
|
|
||||||
|
| 场景 | 优化前 | 优化后 | 提升 |
|
||||||
|
|------|--------|--------|------|
|
||||||
|
| 首次下载 | 1-3秒 | 1-3秒 | - |
|
||||||
|
| 缓存命中 | 1-3秒 | < 100ms | **10-30倍** |
|
||||||
|
| 版本更新后首次 | 1-3秒 | 1-3秒 | - |
|
||||||
|
|
||||||
|
## ⚙️ 配置说明
|
||||||
|
|
||||||
|
### 环境变量配置
|
||||||
|
|
||||||
|
可以通过环境变量自定义缓存配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 缓存目录(默认:系统临时目录下的tyapi_pdf_cache)
|
||||||
|
export PDF_CACHE_DIR="/path/to/cache"
|
||||||
|
|
||||||
|
# 缓存过期时间(默认:24小时)
|
||||||
|
export PDF_CACHE_TTL="24h"
|
||||||
|
|
||||||
|
# 最大缓存大小(默认:500MB)
|
||||||
|
export PDF_CACHE_MAX_SIZE="524288000" # 字节
|
||||||
|
```
|
||||||
|
|
||||||
|
### 默认配置
|
||||||
|
|
||||||
|
- **缓存目录**:系统临时目录下的 `tyapi_pdf_cache`
|
||||||
|
- **TTL**:24小时
|
||||||
|
- **最大缓存大小**:500MB
|
||||||
|
|
||||||
|
## 📁 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
tyapi-server/
|
||||||
|
├── internal/
|
||||||
|
│ └── shared/
|
||||||
|
│ └── pdf/
|
||||||
|
│ ├── pdf_cache_manager.go # 新增:PDF缓存管理器
|
||||||
|
│ ├── pdf_generator.go # 原有:PDF生成器
|
||||||
|
│ └── ...
|
||||||
|
├── internal/
|
||||||
|
│ └── infrastructure/
|
||||||
|
│ └── http/
|
||||||
|
│ └── handlers/
|
||||||
|
│ └── product_handler.go # 修改:集成缓存机制
|
||||||
|
└── internal/
|
||||||
|
└── container/
|
||||||
|
└── container.go # 修改:初始化缓存管理器
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 使用示例
|
||||||
|
|
||||||
|
### 基本使用
|
||||||
|
|
||||||
|
缓存机制已自动集成,无需额外代码:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 用户请求下载PDF
|
||||||
|
GET /api/v1/products/{id}/documentation/download
|
||||||
|
|
||||||
|
// 系统自动:
|
||||||
|
// 1. 检查缓存
|
||||||
|
// 2. 缓存命中 → 直接返回
|
||||||
|
// 3. 缓存未命中 → 生成PDF → 保存缓存 → 返回
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动管理缓存
|
||||||
|
|
||||||
|
如果需要手动管理缓存(如产品更新后清除缓存):
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 使特定产品的缓存失效
|
||||||
|
cacheManager.InvalidateByProductID(productID)
|
||||||
|
|
||||||
|
// 使特定版本的缓存失效
|
||||||
|
cacheManager.Invalidate(productID, version)
|
||||||
|
|
||||||
|
// 清空所有缓存
|
||||||
|
cacheManager.Clear()
|
||||||
|
|
||||||
|
// 获取缓存统计信息
|
||||||
|
stats, _ := cacheManager.GetCacheStats()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 监控和日志
|
||||||
|
|
||||||
|
### 日志输出
|
||||||
|
|
||||||
|
系统会记录以下日志:
|
||||||
|
|
||||||
|
- **缓存命中**:`PDF缓存命中` - 包含产品ID、版本、文件大小
|
||||||
|
- **缓存未命中**:`PDF缓存未命中,开始生成PDF`
|
||||||
|
- **缓存保存**:`PDF已缓存` - 包含产品ID、缓存键、文件大小
|
||||||
|
- **缓存清理**:`已清理过期缓存文件` - 包含清理数量和释放空间
|
||||||
|
|
||||||
|
### 响应头标识
|
||||||
|
|
||||||
|
响应头中添加了缓存标识:
|
||||||
|
|
||||||
|
- `X-Cache: HIT` - 缓存命中
|
||||||
|
- `X-Cache: MISS` - 缓存未命中
|
||||||
|
|
||||||
|
## 🔒 安全考虑
|
||||||
|
|
||||||
|
1. **文件权限**:缓存文件权限设置为 `0644`,仅所有者可写
|
||||||
|
2. **目录隔离**:缓存文件存储在独立目录,不影响其他文件
|
||||||
|
3. **自动清理**:过期文件自动清理,防止磁盘空间耗尽
|
||||||
|
|
||||||
|
## 🐛 故障处理
|
||||||
|
|
||||||
|
### 缓存初始化失败
|
||||||
|
|
||||||
|
如果缓存管理器初始化失败,系统会:
|
||||||
|
|
||||||
|
- 记录警告日志
|
||||||
|
- 继续正常运行(禁用缓存功能)
|
||||||
|
- 所有请求都会重新生成PDF
|
||||||
|
|
||||||
|
### 缓存读取失败
|
||||||
|
|
||||||
|
如果缓存读取失败,系统会:
|
||||||
|
|
||||||
|
- 记录警告日志
|
||||||
|
- 自动降级为重新生成PDF
|
||||||
|
- 不影响用户体验
|
||||||
|
|
||||||
|
## 🔄 后续优化建议
|
||||||
|
|
||||||
|
1. **分布式缓存**:考虑使用Redis等分布式缓存,支持多实例部署
|
||||||
|
2. **缓存预热**:在系统启动时预生成热门产品的PDF
|
||||||
|
3. **压缩存储**:对PDF文件进行压缩存储,节省磁盘空间
|
||||||
|
4. **缓存统计**:添加更详细的缓存统计和监控指标
|
||||||
|
5. **智能清理**:基于LRU等算法,优先清理不常用的缓存
|
||||||
|
|
||||||
|
## 📝 更新日志
|
||||||
|
|
||||||
|
- **2024-12-XX**:初始版本,实现本地文件缓存机制
|
||||||
|
- 添加PDF缓存管理器
|
||||||
|
- 集成到下载接口
|
||||||
|
- 支持版本控制和自动过期
|
||||||
242
docs/Ubuntu服务器PDF字体配置指南.md
Normal file
242
docs/Ubuntu服务器PDF字体配置指南.md
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
# Ubuntu服务器PDF字体配置指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本文档说明如何在Ubuntu 24.04 LTS服务器上配置PDF生成功能所需的中文字体。
|
||||||
|
|
||||||
|
## 字体文件位置
|
||||||
|
|
||||||
|
确保字体文件存在于以下任一位置:
|
||||||
|
|
||||||
|
### 推荐路径(按优先级)
|
||||||
|
|
||||||
|
1. **工作目录相对路径**(最常用)
|
||||||
|
```
|
||||||
|
{工作目录}/internal/shared/pdf/fonts/
|
||||||
|
```
|
||||||
|
例如:如果工作目录是 `/www/tyapi-server`,则字体应在:
|
||||||
|
```
|
||||||
|
/www/tyapi-server/internal/shared/pdf/fonts/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **可执行文件相对路径**
|
||||||
|
```
|
||||||
|
{可执行文件所在目录}/internal/shared/pdf/fonts/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **环境变量指定路径**
|
||||||
|
```bash
|
||||||
|
export PDF_FONT_DIR=/path/to/fonts
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **硬编码路径**(后备方案)
|
||||||
|
- `/www/tyapi-server/internal/shared/pdf/fonts` ✅(已配置)
|
||||||
|
- `/app/internal/shared/pdf/fonts`(Docker)
|
||||||
|
- `/usr/local/tyapi-server/internal/shared/pdf/fonts`
|
||||||
|
- `/opt/tyapi-server/internal/shared/pdf/fonts`
|
||||||
|
- `/home/ubuntu/tyapi-server/internal/shared/pdf/fonts`
|
||||||
|
- `/root/tyapi-server/internal/shared/pdf/fonts`
|
||||||
|
- `/var/www/tyapi-server/internal/shared/pdf/fonts`
|
||||||
|
|
||||||
|
## 部署步骤
|
||||||
|
|
||||||
|
### 方法1:直接复制字体文件(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 创建字体目录
|
||||||
|
sudo mkdir -p /www/tyapi-server/internal/shared/pdf/fonts
|
||||||
|
|
||||||
|
# 2. 复制字体文件(从本地或Git仓库)
|
||||||
|
# 需要以下字体文件:
|
||||||
|
# - simhei.ttf (黑体,必需)
|
||||||
|
# - simkai.ttf (楷体,可选)
|
||||||
|
# - simfang.ttf (仿宋,可选)
|
||||||
|
# - YunFengFeiYunTi-2.ttf (水印字体,可选)
|
||||||
|
|
||||||
|
# 3. 设置权限
|
||||||
|
sudo chmod -R 644 /www/tyapi-server/internal/shared/pdf/fonts/*.ttf
|
||||||
|
sudo chmod -R 644 /www/tyapi-server/internal/shared/pdf/fonts/*.ttc
|
||||||
|
|
||||||
|
# 4. 确保运行用户有读取权限
|
||||||
|
sudo chown -R $(whoami):$(whoami) /www/tyapi-server/internal/shared/pdf/fonts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法2:使用环境变量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置字体目录环境变量
|
||||||
|
export PDF_FONT_DIR=/www/tyapi-server/internal/shared/pdf/fonts
|
||||||
|
|
||||||
|
# 或在 systemd 服务文件中添加
|
||||||
|
# Environment="PDF_FONT_DIR=/www/tyapi-server/internal/shared/pdf/fonts"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法3:使用符号链接
|
||||||
|
|
||||||
|
如果字体文件在其他位置,可以创建符号链接:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /www/tyapi-server/internal/shared/pdf/fonts
|
||||||
|
sudo ln -s /path/to/actual/fonts/*.ttf /www/tyapi-server/internal/shared/pdf/fonts/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证字体文件
|
||||||
|
|
||||||
|
### 1. 检查文件是否存在
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -lh /www/tyapi-server/internal/shared/pdf/fonts/
|
||||||
|
```
|
||||||
|
|
||||||
|
应该看到:
|
||||||
|
```
|
||||||
|
-rw-r--r-- 1 user user 9.5M Dec 3 18:00 simhei.ttf
|
||||||
|
-rw-r--r-- 1 user user 8.2M Dec 3 18:00 simkai.ttf
|
||||||
|
-rw-r--r-- 1 user user 7.8M Dec 3 18:00 simfang.ttf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 检查文件权限
|
||||||
|
|
||||||
|
```bash
|
||||||
|
stat /www/tyapi-server/internal/shared/pdf/fonts/simhei.ttf
|
||||||
|
```
|
||||||
|
|
||||||
|
确保有读取权限(至少 `-r--r--r--`)。
|
||||||
|
|
||||||
|
### 3. 检查文件类型
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file /www/tyapi-server/internal/shared/pdf/fonts/simhei.ttf
|
||||||
|
```
|
||||||
|
|
||||||
|
应该显示:`TrueType font data`
|
||||||
|
|
||||||
|
## 验证PDF生成功能
|
||||||
|
|
||||||
|
### 1. 查看日志
|
||||||
|
|
||||||
|
启动服务后,查看日志中是否有以下信息:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"level":"INFO","msg":"找到字体文件","count":3,"paths":["/www/tyapi-server/internal/shared/pdf/fonts/simhei.ttf",...]}
|
||||||
|
{"level":"INFO","msg":"成功加载中文字体","font_path":"/www/tyapi-server/internal/shared/pdf/fonts/simhei.ttf"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 测试PDF生成
|
||||||
|
|
||||||
|
调用PDF下载接口,检查:
|
||||||
|
- PDF文件能正常生成
|
||||||
|
- 中文文字正常显示(不是乱码或空白)
|
||||||
|
- 没有字体相关的错误日志
|
||||||
|
|
||||||
|
### 3. 调试信息
|
||||||
|
|
||||||
|
如果字体未找到,查看日志中的调试信息:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"level":"DEBUG","msg":"查找字体文件","total_paths":20,"paths":[...]}
|
||||||
|
{"level":"DEBUG","msg":"字体文件不存在","font_path":"...","error":"..."}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题1:字体文件找不到
|
||||||
|
|
||||||
|
**症状**:日志显示 `"未找到中文字体文件"`
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
1. 确认字体文件路径是否正确
|
||||||
|
2. 检查文件权限:`chmod 644 *.ttf`
|
||||||
|
3. 检查文件所有者:`chown user:user *.ttf`
|
||||||
|
4. 查看日志中的 `"查找字体文件"` 调试信息,确认尝试的路径
|
||||||
|
|
||||||
|
### 问题2:字体文件无权限读取
|
||||||
|
|
||||||
|
**症状**:日志显示 `"字体文件无读取权限"`
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```bash
|
||||||
|
sudo chmod 644 /www/tyapi-server/internal/shared/pdf/fonts/*.ttf
|
||||||
|
sudo chown -R $(whoami):$(whoami) /www/tyapi-server/internal/shared/pdf/fonts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题3:中文显示为乱码
|
||||||
|
|
||||||
|
**症状**:PDF中中文显示为乱码或空白
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
1. 确认字体文件已成功加载(查看日志)
|
||||||
|
2. 确认字体文件是有效的TTF格式
|
||||||
|
3. 检查字体文件是否损坏:`file *.ttf`
|
||||||
|
|
||||||
|
### 问题4:Docker容器中找不到字体
|
||||||
|
|
||||||
|
**症状**:在Docker容器中运行时找不到字体
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
1. 确保Dockerfile中已复制字体文件:
|
||||||
|
```dockerfile
|
||||||
|
COPY --from=builder /app/internal/shared/pdf/fonts/ ./internal/shared/pdf/fonts/
|
||||||
|
```
|
||||||
|
2. 或使用volume挂载:
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- /www/tyapi-server/internal/shared/pdf/fonts:/app/internal/shared/pdf/fonts:ro
|
||||||
|
```
|
||||||
|
|
||||||
|
## Systemd服务配置示例
|
||||||
|
|
||||||
|
如果使用systemd管理服务,可以在服务文件中设置环境变量:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=TYAPI Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=ubuntu
|
||||||
|
WorkingDirectory=/www/tyapi-server
|
||||||
|
ExecStart=/www/tyapi-server/tyapi-server -env=production
|
||||||
|
Environment="PDF_FONT_DIR=/www/tyapi-server/internal/shared/pdf/fonts"
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
## 字体文件获取
|
||||||
|
|
||||||
|
如果本地没有字体文件,可以从以下来源获取:
|
||||||
|
|
||||||
|
1. **Windows系统字体**(如果服务器是Windows迁移过来的)
|
||||||
|
- `C:\Windows\Fonts\simhei.ttf` → 复制到服务器
|
||||||
|
|
||||||
|
2. **Linux系统字体包**
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt-get install fonts-wqy-zenhei fonts-wqy-microhei
|
||||||
|
# 然后从系统字体目录复制或创建符号链接
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **从项目仓库**
|
||||||
|
- 确保字体文件已提交到Git仓库
|
||||||
|
- 使用 `git pull` 拉取最新代码
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **字体文件大小**:每个TTF文件约8-10MB,确保有足够磁盘空间
|
||||||
|
2. **文件权限**:确保运行服务的用户有读取权限
|
||||||
|
3. **路径一致性**:确保字体路径与代码中的查找路径一致
|
||||||
|
4. **日志级别**:生产环境建议将字体查找日志设为DEBUG级别,避免日志过多
|
||||||
|
|
||||||
|
## 技术支持
|
||||||
|
|
||||||
|
如果遇到问题,请提供以下信息:
|
||||||
|
1. 服务器操作系统版本:`lsb_release -a`
|
||||||
|
2. 字体文件位置和权限:`ls -lh /www/tyapi-server/internal/shared/pdf/fonts/`
|
||||||
|
3. 工作目录:`pwd`(服务运行时)
|
||||||
|
4. 可执行文件位置:`which tyapi-server` 或 `readlink -f $(which tyapi-server)`
|
||||||
|
5. 相关日志:包含 `"查找字体文件"` 和 `"字体文件"` 的日志条目
|
||||||
|
|
||||||
3
go.mod
3
go.mod
@@ -12,11 +12,13 @@ require (
|
|||||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hibiken/asynq v0.25.1
|
github.com/hibiken/asynq v0.25.1
|
||||||
|
github.com/jung-kurt/gofpdf/v2 v2.17.3
|
||||||
github.com/prometheus/client_golang v1.22.0
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/qiniu/go-sdk/v7 v7.25.4
|
github.com/qiniu/go-sdk/v7 v7.25.4
|
||||||
github.com/redis/go-redis/v9 v9.11.0
|
github.com/redis/go-redis/v9 v9.11.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shopspring/decimal v1.4.0
|
github.com/shopspring/decimal v1.4.0
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/smartwalle/alipay/v3 v3.2.25
|
github.com/smartwalle/alipay/v3 v3.2.25
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
@@ -24,6 +26,7 @@ require (
|
|||||||
github.com/swaggo/gin-swagger v1.6.0
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
github.com/swaggo/swag v1.16.4
|
github.com/swaggo/swag v1.16.4
|
||||||
github.com/tidwall/gjson v1.18.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.21
|
||||||
github.com/xuri/excelize/v2 v2.9.1
|
github.com/xuri/excelize/v2 v2.9.1
|
||||||
go.opentelemetry.io/otel v1.37.0
|
go.opentelemetry.io/otel v1.37.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -9,6 +9,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
|
|||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
|
||||||
|
github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
|
||||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
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 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4=
|
||||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
|
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
|
||||||
@@ -133,6 +135,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
|
|||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
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/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
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=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
@@ -206,6 +210,8 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
|
|||||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/smartwalle/alipay/v3 v3.2.25 h1:cRDN+fpDWTVHnuHIF/vsJETskRXS/S+fDOdAkzXmV/Q=
|
github.com/smartwalle/alipay/v3 v3.2.25 h1:cRDN+fpDWTVHnuHIF/vsJETskRXS/S+fDOdAkzXmV/Q=
|
||||||
github.com/smartwalle/alipay/v3 v3.2.25/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
|
github.com/smartwalle/alipay/v3 v3.2.25/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
|
||||||
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
|
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
|
||||||
@@ -260,6 +266,8 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK
|
|||||||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.21 h1:uIyMpzvcaHA33W/QPtHstccw+X52HO1gFdvVL9O6Lfs=
|
||||||
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.21/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
|
||||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||||
github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Qw=
|
github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Qw=
|
||||||
|
|||||||
@@ -245,6 +245,8 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
|||||||
&articleEntities.Category{},
|
&articleEntities.Category{},
|
||||||
&articleEntities.Tag{},
|
&articleEntities.Tag{},
|
||||||
&articleEntities.ScheduledTask{},
|
&articleEntities.ScheduledTask{},
|
||||||
|
// 公告
|
||||||
|
&articleEntities.Announcement{},
|
||||||
|
|
||||||
// 统计域
|
// 统计域
|
||||||
&statisticsEntities.StatisticsMetric{},
|
&statisticsEntities.StatisticsMetric{},
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"tyapi-server/internal/application/api/commands"
|
"tyapi-server/internal/application/api/commands"
|
||||||
"tyapi-server/internal/application/api/dto"
|
"tyapi-server/internal/application/api/dto"
|
||||||
@@ -37,8 +39,8 @@ type ApiApplicationService interface {
|
|||||||
GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error)
|
GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error)
|
||||||
|
|
||||||
// 用户白名单管理
|
// 用户白名单管理
|
||||||
GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error)
|
GetUserWhiteList(ctx context.Context, userID string, remarkKeyword string) (*dto.WhiteListListResponse, error)
|
||||||
AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
AddWhiteListIP(ctx context.Context, userID string, ipAddress string, remark string) error
|
||||||
DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
||||||
|
|
||||||
// 获取用户API调用记录
|
// 获取用户API调用记录
|
||||||
@@ -466,7 +468,7 @@ func (s *ApiApplicationServiceImpl) GetUserApiKeys(ctx context.Context, userID s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUserWhiteList 获取用户白名单列表
|
// GetUserWhiteList 获取用户白名单列表
|
||||||
func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error) {
|
func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID string, remarkKeyword string) (*dto.WhiteListListResponse, error) {
|
||||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -474,28 +476,49 @@ func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID
|
|||||||
|
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = entities.WhiteList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将白名单字符串数组转换为响应格式
|
// 将白名单转换为响应格式
|
||||||
var items []dto.WhiteListResponse
|
var items []dto.WhiteListResponse
|
||||||
for _, ip := range apiUser.WhiteList {
|
for _, item := range apiUser.WhiteList {
|
||||||
|
// 如果提供了备注关键词,进行模糊匹配过滤
|
||||||
|
if remarkKeyword != "" {
|
||||||
|
if !contains(item.Remark, remarkKeyword) {
|
||||||
|
continue // 不匹配则跳过
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
items = append(items, dto.WhiteListResponse{
|
items = append(items, dto.WhiteListResponse{
|
||||||
ID: apiUser.ID, // 使用API用户ID作为标识
|
ID: apiUser.ID, // 使用API用户ID作为标识
|
||||||
UserID: apiUser.UserId,
|
UserID: apiUser.UserId,
|
||||||
IPAddress: ip,
|
IPAddress: item.IPAddress,
|
||||||
CreatedAt: apiUser.CreatedAt, // 使用API用户创建时间
|
Remark: item.Remark, // 备注
|
||||||
|
CreatedAt: item.AddedAt, // 使用每个IP的实际添加时间
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按添加时间降序排序(新的排在前面)
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
return items[i].CreatedAt.After(items[j].CreatedAt)
|
||||||
|
})
|
||||||
|
|
||||||
return &dto.WhiteListListResponse{
|
return &dto.WhiteListListResponse{
|
||||||
Items: items,
|
Items: items,
|
||||||
Total: len(items),
|
Total: len(items),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// contains 检查字符串是否包含子字符串(不区分大小写)
|
||||||
|
func contains(s, substr string) bool {
|
||||||
|
if substr == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||||
|
}
|
||||||
|
|
||||||
// AddWhiteListIP 添加白名单IP
|
// AddWhiteListIP 添加白名单IP
|
||||||
func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error {
|
func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID string, ipAddress string, remark string) error {
|
||||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -503,11 +526,11 @@ func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID s
|
|||||||
|
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = entities.WhiteList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用实体的领域方法添加IP到白名单
|
// 使用实体的领域方法添加IP到白名单(会自动记录添加时间和备注)
|
||||||
err = apiUser.AddToWhiteList(ipAddress)
|
err = apiUser.AddToWhiteList(ipAddress, remark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -530,7 +553,7 @@ func (s *ApiApplicationServiceImpl) DeleteWhiteListIP(ctx context.Context, userI
|
|||||||
|
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = entities.WhiteList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用实体的领域方法删除IP
|
// 使用实体的领域方法删除IP
|
||||||
@@ -1218,9 +1241,9 @@ func (s *ApiApplicationServiceImpl) GetUserBalanceAlertSettings(ctx context.Cont
|
|||||||
|
|
||||||
// 返回预警设置
|
// 返回预警设置
|
||||||
settings := map[string]interface{}{
|
settings := map[string]interface{}{
|
||||||
"enabled": apiUser.BalanceAlertEnabled,
|
"enabled": apiUser.BalanceAlertEnabled,
|
||||||
"threshold": apiUser.BalanceAlertThreshold,
|
"threshold": apiUser.BalanceAlertThreshold,
|
||||||
"alert_phone": apiUser.AlertPhone,
|
"alert_phone": apiUser.AlertPhone,
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings, nil
|
return settings, nil
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ type WhiteListResponse struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
IPAddress string `json:"ip_address"`
|
IPAddress string `json:"ip_address"`
|
||||||
|
Remark string `json:"remark"` // 备注
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WhiteListRequest struct {
|
type WhiteListRequest struct {
|
||||||
IPAddress string `json:"ip_address" binding:"required,ip"`
|
IPAddress string `json:"ip_address" binding:"required,ip"`
|
||||||
|
Remark string `json:"remark"` // 备注(可选)
|
||||||
}
|
}
|
||||||
|
|
||||||
type WhiteListListResponse struct {
|
type WhiteListListResponse struct {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package article
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"tyapi-server/internal/application/article/dto/commands"
|
||||||
|
appQueries "tyapi-server/internal/application/article/dto/queries"
|
||||||
|
"tyapi-server/internal/application/article/dto/responses"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementApplicationService 公告应用服务接口
|
||||||
|
type AnnouncementApplicationService interface {
|
||||||
|
// 公告管理
|
||||||
|
CreateAnnouncement(ctx context.Context, cmd *commands.CreateAnnouncementCommand) error
|
||||||
|
UpdateAnnouncement(ctx context.Context, cmd *commands.UpdateAnnouncementCommand) error
|
||||||
|
DeleteAnnouncement(ctx context.Context, cmd *commands.DeleteAnnouncementCommand) error
|
||||||
|
GetAnnouncementByID(ctx context.Context, query *appQueries.GetAnnouncementQuery) (*responses.AnnouncementInfoResponse, error)
|
||||||
|
ListAnnouncements(ctx context.Context, query *appQueries.ListAnnouncementQuery) (*responses.AnnouncementListResponse, error)
|
||||||
|
|
||||||
|
// 公告状态管理
|
||||||
|
PublishAnnouncement(ctx context.Context, cmd *commands.PublishAnnouncementCommand) error
|
||||||
|
PublishAnnouncementByID(ctx context.Context, announcementID string) error // 通过ID发布公告 (用于定时任务)
|
||||||
|
WithdrawAnnouncement(ctx context.Context, cmd *commands.WithdrawAnnouncementCommand) error
|
||||||
|
ArchiveAnnouncement(ctx context.Context, cmd *commands.ArchiveAnnouncementCommand) error
|
||||||
|
SchedulePublishAnnouncement(ctx context.Context, cmd *commands.SchedulePublishAnnouncementCommand) error
|
||||||
|
UpdateSchedulePublishAnnouncement(ctx context.Context, cmd *commands.UpdateSchedulePublishAnnouncementCommand) error
|
||||||
|
CancelSchedulePublishAnnouncement(ctx context.Context, cmd *commands.CancelSchedulePublishAnnouncementCommand) error
|
||||||
|
|
||||||
|
// 统计信息
|
||||||
|
GetAnnouncementStats(ctx context.Context) (*responses.AnnouncementStatsResponse, error)
|
||||||
|
}
|
||||||
@@ -0,0 +1,484 @@
|
|||||||
|
package article
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"tyapi-server/internal/application/article/dto/commands"
|
||||||
|
appQueries "tyapi-server/internal/application/article/dto/queries"
|
||||||
|
"tyapi-server/internal/application/article/dto/responses"
|
||||||
|
"tyapi-server/internal/domains/article/entities"
|
||||||
|
"tyapi-server/internal/domains/article/repositories"
|
||||||
|
repoQueries "tyapi-server/internal/domains/article/repositories/queries"
|
||||||
|
"tyapi-server/internal/domains/article/services"
|
||||||
|
task_entities "tyapi-server/internal/infrastructure/task/entities"
|
||||||
|
task_interfaces "tyapi-server/internal/infrastructure/task/interfaces"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementApplicationServiceImpl 公告应用服务实现
|
||||||
|
type AnnouncementApplicationServiceImpl struct {
|
||||||
|
announcementRepo repositories.AnnouncementRepository
|
||||||
|
announcementService *services.AnnouncementService
|
||||||
|
taskManager task_interfaces.TaskManager
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnnouncementApplicationService 创建公告应用服务
|
||||||
|
func NewAnnouncementApplicationService(
|
||||||
|
announcementRepo repositories.AnnouncementRepository,
|
||||||
|
announcementService *services.AnnouncementService,
|
||||||
|
taskManager task_interfaces.TaskManager,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) AnnouncementApplicationService {
|
||||||
|
return &AnnouncementApplicationServiceImpl{
|
||||||
|
announcementRepo: announcementRepo,
|
||||||
|
announcementService: announcementService,
|
||||||
|
taskManager: taskManager,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAnnouncement 创建公告
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) CreateAnnouncement(ctx context.Context, cmd *commands.CreateAnnouncementCommand) error {
|
||||||
|
// 1. 创建公告实体
|
||||||
|
announcement := &entities.Announcement{
|
||||||
|
Title: cmd.Title,
|
||||||
|
Content: cmd.Content,
|
||||||
|
Status: entities.AnnouncementStatusDraft,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 调用领域服务验证
|
||||||
|
if err := s.announcementService.ValidateAnnouncement(announcement); err != nil {
|
||||||
|
return fmt.Errorf("业务验证失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 保存公告
|
||||||
|
_, err := s.announcementRepo.Create(ctx, *announcement)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("创建公告失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("创建公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("创建公告成功", zap.String("id", announcement.ID), zap.String("title", announcement.Title))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAnnouncement 更新公告
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) UpdateAnnouncement(ctx context.Context, cmd *commands.UpdateAnnouncementCommand) error {
|
||||||
|
// 1. 获取原公告
|
||||||
|
announcement, err := s.announcementRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否可以编辑
|
||||||
|
if err := s.announcementService.CanEdit(&announcement); err != nil {
|
||||||
|
return fmt.Errorf("公告状态不允许编辑: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 更新字段
|
||||||
|
if cmd.Title != "" {
|
||||||
|
announcement.Title = cmd.Title
|
||||||
|
}
|
||||||
|
if cmd.Content != "" {
|
||||||
|
announcement.Content = cmd.Content
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 验证更新后的公告
|
||||||
|
if err := s.announcementService.ValidateAnnouncement(&announcement); err != nil {
|
||||||
|
return fmt.Errorf("业务验证失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 保存更新
|
||||||
|
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||||||
|
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("更新公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("更新公告成功", zap.String("id", announcement.ID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAnnouncement 删除公告
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) DeleteAnnouncement(ctx context.Context, cmd *commands.DeleteAnnouncementCommand) error {
|
||||||
|
// 1. 检查公告是否存在
|
||||||
|
_, err := s.announcementRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 删除公告
|
||||||
|
if err := s.announcementRepo.Delete(ctx, cmd.ID); err != nil {
|
||||||
|
s.logger.Error("删除公告失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("删除公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("删除公告成功", zap.String("id", cmd.ID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAnnouncementByID 获取公告详情
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) GetAnnouncementByID(ctx context.Context, query *appQueries.GetAnnouncementQuery) (*responses.AnnouncementInfoResponse, error) {
|
||||||
|
// 1. 获取公告
|
||||||
|
announcement, err := s.announcementRepo.GetByID(ctx, query.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", query.ID), zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 转换为响应对象
|
||||||
|
response := responses.FromAnnouncementEntity(&announcement)
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAnnouncements 获取公告列表
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) ListAnnouncements(ctx context.Context, query *appQueries.ListAnnouncementQuery) (*responses.AnnouncementListResponse, error) {
|
||||||
|
// 1. 构建仓储查询
|
||||||
|
repoQuery := &repoQueries.ListAnnouncementQuery{
|
||||||
|
Page: query.Page,
|
||||||
|
PageSize: query.PageSize,
|
||||||
|
Status: query.Status,
|
||||||
|
Title: query.Title,
|
||||||
|
OrderBy: query.OrderBy,
|
||||||
|
OrderDir: query.OrderDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 调用仓储
|
||||||
|
announcements, total, err := s.announcementRepo.ListAnnouncements(ctx, repoQuery)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告列表失败", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("获取公告列表失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换为响应对象
|
||||||
|
items := responses.FromAnnouncementEntityList(announcements)
|
||||||
|
|
||||||
|
response := &responses.AnnouncementListResponse{
|
||||||
|
Total: total,
|
||||||
|
Page: query.Page,
|
||||||
|
Size: query.PageSize,
|
||||||
|
Items: items,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("获取公告列表成功", zap.Int64("total", total))
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishAnnouncement 发布公告
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) PublishAnnouncement(ctx context.Context, cmd *commands.PublishAnnouncementCommand) error {
|
||||||
|
// 1. 获取公告
|
||||||
|
announcement, err := s.announcementRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否可以发布
|
||||||
|
if err := s.announcementService.CanPublish(&announcement); err != nil {
|
||||||
|
return fmt.Errorf("无法发布公告: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 发布公告
|
||||||
|
if err := announcement.Publish(); err != nil {
|
||||||
|
return fmt.Errorf("发布公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 保存更新
|
||||||
|
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||||||
|
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("发布公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("发布公告成功", zap.String("id", announcement.ID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishAnnouncementByID 通过ID发布公告 (用于定时任务)
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) PublishAnnouncementByID(ctx context.Context, announcementID string) error {
|
||||||
|
// 1. 获取公告
|
||||||
|
announcement, err := s.announcementRepo.GetByID(ctx, announcementID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", announcementID), zap.Error(err))
|
||||||
|
return fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否已取消定时发布
|
||||||
|
if !announcement.IsScheduled() {
|
||||||
|
s.logger.Info("公告定时发布已取消,跳过执行",
|
||||||
|
zap.String("id", announcementID),
|
||||||
|
zap.String("status", string(announcement.Status)))
|
||||||
|
return nil // 静默返回,不报错
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查定时发布时间是否匹配
|
||||||
|
if announcement.ScheduledAt == nil {
|
||||||
|
s.logger.Info("公告没有定时发布时间,跳过执行",
|
||||||
|
zap.String("id", announcementID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 发布公告
|
||||||
|
if err := announcement.Publish(); err != nil {
|
||||||
|
return fmt.Errorf("发布公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 保存更新
|
||||||
|
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||||||
|
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("发布公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("定时发布公告成功", zap.String("id", announcement.ID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithdrawAnnouncement 撤回公告
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) WithdrawAnnouncement(ctx context.Context, cmd *commands.WithdrawAnnouncementCommand) error {
|
||||||
|
// 1. 获取公告
|
||||||
|
announcement, err := s.announcementRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否可以撤回
|
||||||
|
if err := s.announcementService.CanWithdraw(&announcement); err != nil {
|
||||||
|
return fmt.Errorf("无法撤回公告: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 撤回公告
|
||||||
|
if err := announcement.Withdraw(); err != nil {
|
||||||
|
return fmt.Errorf("撤回公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 保存更新
|
||||||
|
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||||||
|
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("撤回公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("撤回公告成功", zap.String("id", announcement.ID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveAnnouncement 归档公告
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) ArchiveAnnouncement(ctx context.Context, cmd *commands.ArchiveAnnouncementCommand) error {
|
||||||
|
// 1. 获取公告
|
||||||
|
announcement, err := s.announcementRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否可以归档
|
||||||
|
if err := s.announcementService.CanArchive(&announcement); err != nil {
|
||||||
|
return fmt.Errorf("无法归档公告: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 归档公告
|
||||||
|
announcement.Status = entities.AnnouncementStatusArchived
|
||||||
|
|
||||||
|
// 4. 保存更新
|
||||||
|
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||||||
|
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("归档公告失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("归档公告成功", zap.String("id", announcement.ID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchedulePublishAnnouncement 定时发布公告
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) SchedulePublishAnnouncement(ctx context.Context, cmd *commands.SchedulePublishAnnouncementCommand) error {
|
||||||
|
// 1. 解析定时发布时间
|
||||||
|
scheduledTime, err := cmd.GetScheduledTime()
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("解析定时发布时间失败", zap.String("scheduled_time", cmd.ScheduledTime), zap.Error(err))
|
||||||
|
return fmt.Errorf("定时发布时间格式错误: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取公告
|
||||||
|
announcement, err := s.announcementRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查是否可以定时发布
|
||||||
|
if err := s.announcementService.CanSchedulePublish(&announcement, scheduledTime); err != nil {
|
||||||
|
return fmt.Errorf("无法设置定时发布: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 取消旧任务(如果存在)
|
||||||
|
if err := s.taskManager.CancelTask(ctx, cmd.ID); err != nil {
|
||||||
|
s.logger.Warn("取消旧任务失败", zap.String("announcement_id", cmd.ID), zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 创建任务工厂
|
||||||
|
taskFactory := task_entities.NewTaskFactoryWithManager(s.taskManager)
|
||||||
|
|
||||||
|
// 6. 创建并异步入队公告发布任务
|
||||||
|
if err := taskFactory.CreateAndEnqueueAnnouncementPublishTask(
|
||||||
|
ctx,
|
||||||
|
cmd.ID,
|
||||||
|
scheduledTime,
|
||||||
|
"system", // 暂时使用系统用户ID
|
||||||
|
); err != nil {
|
||||||
|
s.logger.Error("创建并入队公告发布任务失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("创建定时发布任务失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 设置定时发布
|
||||||
|
if err := announcement.SchedulePublish(scheduledTime); err != nil {
|
||||||
|
return fmt.Errorf("设置定时发布失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 保存更新
|
||||||
|
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||||||
|
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("设置定时发布失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("设置定时发布成功", zap.String("id", announcement.ID), zap.Time("scheduled_at", scheduledTime))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSchedulePublishAnnouncement 更新定时发布公告
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) UpdateSchedulePublishAnnouncement(ctx context.Context, cmd *commands.UpdateSchedulePublishAnnouncementCommand) error {
|
||||||
|
// 1. 解析定时发布时间
|
||||||
|
scheduledTime, err := cmd.GetScheduledTime()
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("解析定时发布时间失败", zap.String("scheduled_time", cmd.ScheduledTime), zap.Error(err))
|
||||||
|
return fmt.Errorf("定时发布时间格式错误: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取公告
|
||||||
|
announcement, err := s.announcementRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查是否已设置定时发布
|
||||||
|
if !announcement.IsScheduled() {
|
||||||
|
return fmt.Errorf("公告未设置定时发布,无法修改时间")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 取消旧任务
|
||||||
|
if err := s.taskManager.CancelTask(ctx, cmd.ID); err != nil {
|
||||||
|
s.logger.Warn("取消旧任务失败", zap.String("announcement_id", cmd.ID), zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 创建任务工厂
|
||||||
|
taskFactory := task_entities.NewTaskFactoryWithManager(s.taskManager)
|
||||||
|
|
||||||
|
// 6. 创建并异步入队新的公告发布任务
|
||||||
|
if err := taskFactory.CreateAndEnqueueAnnouncementPublishTask(
|
||||||
|
ctx,
|
||||||
|
cmd.ID,
|
||||||
|
scheduledTime,
|
||||||
|
"system", // 暂时使用系统用户ID
|
||||||
|
); err != nil {
|
||||||
|
s.logger.Error("创建并入队公告发布任务失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("创建定时发布任务失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 更新定时发布时间
|
||||||
|
if err := announcement.UpdateSchedulePublish(scheduledTime); err != nil {
|
||||||
|
return fmt.Errorf("更新定时发布时间失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 保存更新
|
||||||
|
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||||||
|
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("修改定时发布时间失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("修改定时发布时间成功", zap.String("id", announcement.ID), zap.Time("scheduled_at", scheduledTime))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelSchedulePublishAnnouncement 取消定时发布公告
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) CancelSchedulePublishAnnouncement(ctx context.Context, cmd *commands.CancelSchedulePublishAnnouncementCommand) error {
|
||||||
|
// 1. 获取公告
|
||||||
|
announcement, err := s.announcementRepo.GetByID(ctx, cmd.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取公告失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("公告不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否已设置定时发布
|
||||||
|
if !announcement.IsScheduled() {
|
||||||
|
return fmt.Errorf("公告未设置定时发布,无需取消")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 取消任务
|
||||||
|
if err := s.taskManager.CancelTask(ctx, cmd.ID); err != nil {
|
||||||
|
s.logger.Warn("取消任务失败", zap.String("announcement_id", cmd.ID), zap.Error(err))
|
||||||
|
// 继续执行,即使取消任务失败也尝试取消定时发布状态
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 取消定时发布
|
||||||
|
if err := announcement.CancelSchedulePublish(); err != nil {
|
||||||
|
return fmt.Errorf("取消定时发布失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 保存更新
|
||||||
|
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||||||
|
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||||||
|
return fmt.Errorf("取消定时发布失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("取消定时发布成功", zap.String("id", announcement.ID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAnnouncementStats 获取公告统计信息
|
||||||
|
func (s *AnnouncementApplicationServiceImpl) GetAnnouncementStats(ctx context.Context) (*responses.AnnouncementStatsResponse, error) {
|
||||||
|
// 1. 统计总数
|
||||||
|
total, err := s.announcementRepo.CountByStatus(ctx, entities.AnnouncementStatusDraft)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("统计公告总数失败", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 统计各状态数量
|
||||||
|
published, err := s.announcementRepo.CountByStatus(ctx, entities.AnnouncementStatusPublished)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("统计已发布公告数失败", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
draft, err := s.announcementRepo.CountByStatus(ctx, entities.AnnouncementStatusDraft)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("统计草稿公告数失败", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
archived, err := s.announcementRepo.CountByStatus(ctx, entities.AnnouncementStatusArchived)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("统计归档公告数失败", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 统计定时发布数量(需要查询有scheduled_at的草稿)
|
||||||
|
scheduled, err := s.announcementRepo.FindScheduled(ctx)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("统计定时发布公告数失败", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &responses.AnnouncementStatsResponse{
|
||||||
|
TotalAnnouncements: total + published + archived,
|
||||||
|
PublishedAnnouncements: published,
|
||||||
|
DraftAnnouncements: draft,
|
||||||
|
ArchivedAnnouncements: archived,
|
||||||
|
ScheduledAnnouncements: int64(len(scheduled)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateAnnouncementCommand 创建公告命令
|
||||||
|
type CreateAnnouncementCommand struct {
|
||||||
|
Title string `json:"title" binding:"required" comment:"公告标题"`
|
||||||
|
Content string `json:"content" binding:"required" comment:"公告内容"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAnnouncementCommand 更新公告命令
|
||||||
|
type UpdateAnnouncementCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required" comment:"公告ID"`
|
||||||
|
Title string `json:"title" comment:"公告标题"`
|
||||||
|
Content string `json:"content" comment:"公告内容"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAnnouncementCommand 删除公告命令
|
||||||
|
type DeleteAnnouncementCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required" comment:"公告ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishAnnouncementCommand 发布公告命令
|
||||||
|
type PublishAnnouncementCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required" comment:"公告ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithdrawAnnouncementCommand 撤回公告命令
|
||||||
|
type WithdrawAnnouncementCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required" comment:"公告ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveAnnouncementCommand 归档公告命令
|
||||||
|
type ArchiveAnnouncementCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required" comment:"公告ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchedulePublishAnnouncementCommand 定时发布公告命令
|
||||||
|
type SchedulePublishAnnouncementCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required" comment:"公告ID"`
|
||||||
|
ScheduledTime string `json:"scheduled_time" binding:"required" comment:"定时发布时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScheduledTime 获取解析后的定时发布时间
|
||||||
|
func (cmd *SchedulePublishAnnouncementCommand) GetScheduledTime() (time.Time, error) {
|
||||||
|
// 定义中国东八区时区
|
||||||
|
cst := time.FixedZone("CST", 8*3600)
|
||||||
|
|
||||||
|
// 支持多种时间格式
|
||||||
|
formats := []string{
|
||||||
|
"2006-01-02 15:04:05", // "2025-09-02 14:12:01"
|
||||||
|
"2006-01-02T15:04:05", // "2025-09-02T14:12:01"
|
||||||
|
"2006-01-02T15:04:05Z", // "2025-09-02T14:12:01Z"
|
||||||
|
"2006-01-02 15:04", // "2025-09-02 14:12"
|
||||||
|
time.RFC3339, // "2025-09-02T14:12:01+08:00"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, format := range formats {
|
||||||
|
if t, err := time.ParseInLocation(format, cmd.ScheduledTime, cst); err == nil {
|
||||||
|
// 确保返回的时间是东八区时区
|
||||||
|
return t.In(cst), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{}, fmt.Errorf("不支持的时间格式: %s,请使用 YYYY-MM-DD HH:mm:ss 格式", cmd.ScheduledTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSchedulePublishAnnouncementCommand 更新定时发布公告命令
|
||||||
|
type UpdateSchedulePublishAnnouncementCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required" comment:"公告ID"`
|
||||||
|
ScheduledTime string `json:"scheduled_time" binding:"required" comment:"定时发布时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScheduledTime 获取解析后的定时发布时间
|
||||||
|
func (cmd *UpdateSchedulePublishAnnouncementCommand) GetScheduledTime() (time.Time, error) {
|
||||||
|
// 定义中国东八区时区
|
||||||
|
cst := time.FixedZone("CST", 8*3600)
|
||||||
|
|
||||||
|
// 支持多种时间格式
|
||||||
|
formats := []string{
|
||||||
|
"2006-01-02 15:04:05", // "2025-09-02 14:12:01"
|
||||||
|
"2006-01-02T15:04:05", // "2025-09-02T14:12:01"
|
||||||
|
"2006-01-02T15:04:05Z", // "2025-09-02T14:12:01Z"
|
||||||
|
"2006-01-02 15:04", // "2025-09-02 14:12"
|
||||||
|
time.RFC3339, // "2025-09-02T14:12:01+08:00"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, format := range formats {
|
||||||
|
if t, err := time.ParseInLocation(format, cmd.ScheduledTime, cst); err == nil {
|
||||||
|
// 确保返回的时间是东八区时区
|
||||||
|
return t.In(cst), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{}, fmt.Errorf("不支持的时间格式: %s,请使用 YYYY-MM-DD HH:mm:ss 格式", cmd.ScheduledTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelSchedulePublishAnnouncementCommand 取消定时发布公告命令
|
||||||
|
type CancelSchedulePublishAnnouncementCommand struct {
|
||||||
|
ID string `json:"-" uri:"id" binding:"required" comment:"公告ID"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package queries
|
||||||
|
|
||||||
|
import "tyapi-server/internal/domains/article/entities"
|
||||||
|
|
||||||
|
// ListAnnouncementQuery 公告列表查询
|
||||||
|
type ListAnnouncementQuery struct {
|
||||||
|
Page int `form:"page" binding:"min=1" comment:"页码"`
|
||||||
|
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||||
|
Status entities.AnnouncementStatus `form:"status" comment:"公告状态"`
|
||||||
|
Title string `form:"title" comment:"标题关键词"`
|
||||||
|
OrderBy string `form:"order_by" comment:"排序字段"`
|
||||||
|
OrderDir string `form:"order_dir" comment:"排序方向"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAnnouncementQuery 获取公告详情查询
|
||||||
|
type GetAnnouncementQuery struct {
|
||||||
|
ID string `uri:"id" binding:"required" comment:"公告ID"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package responses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"tyapi-server/internal/domains/article/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementInfoResponse 公告详情响应
|
||||||
|
type AnnouncementInfoResponse struct {
|
||||||
|
ID string `json:"id" comment:"公告ID"`
|
||||||
|
Title string `json:"title" comment:"公告标题"`
|
||||||
|
Content string `json:"content" comment:"公告内容"`
|
||||||
|
Status string `json:"status" comment:"公告状态"`
|
||||||
|
ScheduledAt *time.Time `json:"scheduled_at" comment:"定时发布时间"`
|
||||||
|
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementListItemResponse 公告列表项响应
|
||||||
|
type AnnouncementListItemResponse struct {
|
||||||
|
ID string `json:"id" comment:"公告ID"`
|
||||||
|
Title string `json:"title" comment:"公告标题"`
|
||||||
|
Content string `json:"content" comment:"公告内容"`
|
||||||
|
Status string `json:"status" comment:"公告状态"`
|
||||||
|
ScheduledAt *time.Time `json:"scheduled_at" comment:"定时发布时间"`
|
||||||
|
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementListResponse 公告列表响应
|
||||||
|
type AnnouncementListResponse struct {
|
||||||
|
Total int64 `json:"total" comment:"总数"`
|
||||||
|
Page int `json:"page" comment:"页码"`
|
||||||
|
Size int `json:"size" comment:"每页数量"`
|
||||||
|
Items []AnnouncementListItemResponse `json:"items" comment:"公告列表"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementStatsResponse 公告统计响应
|
||||||
|
type AnnouncementStatsResponse struct {
|
||||||
|
TotalAnnouncements int64 `json:"total_announcements" comment:"公告总数"`
|
||||||
|
PublishedAnnouncements int64 `json:"published_announcements" comment:"已发布公告数"`
|
||||||
|
DraftAnnouncements int64 `json:"draft_announcements" comment:"草稿公告数"`
|
||||||
|
ArchivedAnnouncements int64 `json:"archived_announcements" comment:"归档公告数"`
|
||||||
|
ScheduledAnnouncements int64 `json:"scheduled_announcements" comment:"定时发布公告数"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromAnnouncementEntity 从公告实体转换为响应对象
|
||||||
|
func FromAnnouncementEntity(announcement *entities.Announcement) *AnnouncementInfoResponse {
|
||||||
|
if announcement == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AnnouncementInfoResponse{
|
||||||
|
ID: announcement.ID,
|
||||||
|
Title: announcement.Title,
|
||||||
|
Content: announcement.Content,
|
||||||
|
Status: string(announcement.Status),
|
||||||
|
ScheduledAt: announcement.ScheduledAt,
|
||||||
|
CreatedAt: announcement.CreatedAt,
|
||||||
|
UpdatedAt: announcement.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromAnnouncementEntityList 从公告实体列表转换为列表项响应
|
||||||
|
func FromAnnouncementEntityList(announcements []*entities.Announcement) []AnnouncementListItemResponse {
|
||||||
|
items := make([]AnnouncementListItemResponse, 0, len(announcements))
|
||||||
|
for _, announcement := range announcements {
|
||||||
|
items = append(items, AnnouncementListItemResponse{
|
||||||
|
ID: announcement.ID,
|
||||||
|
Title: announcement.Title,
|
||||||
|
Content: announcement.Content,
|
||||||
|
Status: string(announcement.Status),
|
||||||
|
ScheduledAt: announcement.ScheduledAt,
|
||||||
|
CreatedAt: announcement.CreatedAt,
|
||||||
|
UpdatedAt: announcement.UpdatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
@@ -156,17 +156,15 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("企业信息验证失败: %s", err.Error())
|
return nil, fmt.Errorf("企业信息验证失败: %s", err.Error())
|
||||||
}
|
}
|
||||||
if cmd.UserID != "3fbd6917-bb13-40b3-bab0-de0d44c0afca" {
|
err = s.enterpriseInfoSubmitRecordService.ValidateWithWestdex(ctx, enterpriseInfo)
|
||||||
err = s.enterpriseInfoSubmitRecordService.ValidateWithWestdex(ctx, enterpriseInfo)
|
if err != nil {
|
||||||
if err != nil {
|
s.logger.Error("企业信息验证失败", zap.Error(err))
|
||||||
s.logger.Error("企业信息验证失败", zap.Error(err))
|
record.MarkAsFailed(err.Error())
|
||||||
record.MarkAsFailed(err.Error())
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
||||||
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
if saveErr != nil {
|
||||||
if saveErr != nil {
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
||||||
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("企业信息验证失败, %s", err.Error())
|
|
||||||
}
|
}
|
||||||
|
return nil, fmt.Errorf("企业信息验证失败, %s", err.Error())
|
||||||
}
|
}
|
||||||
record.MarkAsVerified()
|
record.MarkAsVerified()
|
||||||
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ type CreateWalletCommand struct {
|
|||||||
UserID string `json:"user_id" binding:"required,uuid"`
|
UserID string `json:"user_id" binding:"required,uuid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TransferRechargeCommand 对公转账充值命令
|
// TransferRechargeCommand 对公转账充值命令
|
||||||
type TransferRechargeCommand struct {
|
type TransferRechargeCommand struct {
|
||||||
UserID string `json:"user_id" binding:"required,uuid"`
|
UserID string `json:"user_id" binding:"required,uuid"`
|
||||||
@@ -16,16 +15,24 @@ type TransferRechargeCommand struct {
|
|||||||
|
|
||||||
// GiftRechargeCommand 赠送充值命令
|
// GiftRechargeCommand 赠送充值命令
|
||||||
type GiftRechargeCommand struct {
|
type GiftRechargeCommand struct {
|
||||||
UserID string `json:"user_id" binding:"required,uuid"`
|
UserID string `json:"user_id" binding:"required,uuid"`
|
||||||
Amount string `json:"amount" binding:"required"`
|
Amount string `json:"amount" binding:"required"`
|
||||||
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
|
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// CreateAlipayRechargeCommand 创建支付宝充值订单命令
|
// CreateAlipayRechargeCommand 创建支付宝充值订单命令
|
||||||
type CreateAlipayRechargeCommand struct {
|
type CreateAlipayRechargeCommand struct {
|
||||||
UserID string `json:"-"` // 用户ID(从token获取)
|
UserID string `json:"-"` // 用户ID(从token获取)
|
||||||
Amount string `json:"amount" binding:"required"` // 充值金额
|
Amount string `json:"amount" binding:"required"` // 充值金额
|
||||||
Subject string `json:"-"` // 订单标题
|
Subject string `json:"-"` // 订单标题
|
||||||
Platform string `json:"platform" binding:"required,oneof=app h5 pc"` // 支付平台:app/h5/pc
|
Platform string `json:"platform" binding:"required,oneof=app h5 pc"` // 支付平台:app/h5/pc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateWechatRechargeCommand 创建微信充值订单命令
|
||||||
|
type CreateWechatRechargeCommand struct {
|
||||||
|
UserID string `json:"-"` // 用户ID(从token获取)
|
||||||
|
Amount string `json:"amount" binding:"required"` // 充值金额
|
||||||
|
Subject string `json:"-"` // 订单标题
|
||||||
|
Platform string `json:"platform" binding:"required,oneof=wx_native native wx_h5 h5"` // 仅支持微信Native扫码,兼容传入native/wx_h5/h5
|
||||||
|
OpenID string `json:"openid" binding:"omitempty"` // 前端可直接传入的 openid(用于小程序/H5)
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ import (
|
|||||||
|
|
||||||
// WalletResponse 钱包响应
|
// WalletResponse 钱包响应
|
||||||
type WalletResponse struct {
|
type WalletResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
Balance decimal.Decimal `json:"balance"`
|
Balance decimal.Decimal `json:"balance"`
|
||||||
BalanceStatus string `json:"balance_status"` // normal, low, arrears
|
BalanceStatus string `json:"balance_status"` // normal, low, arrears
|
||||||
IsArrears bool `json:"is_arrears"` // 是否欠费
|
IsArrears bool `json:"is_arrears"` // 是否欠费
|
||||||
IsLowBalance bool `json:"is_low_balance"` // 是否余额较低
|
IsLowBalance bool `json:"is_low_balance"` // 是否余额较低
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionResponse 交易响应
|
// TransactionResponse 交易响应
|
||||||
@@ -49,34 +49,36 @@ type WalletStatsResponse struct {
|
|||||||
|
|
||||||
// RechargeRecordResponse 充值记录响应
|
// RechargeRecordResponse 充值记录响应
|
||||||
type RechargeRecordResponse struct {
|
type RechargeRecordResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
Amount decimal.Decimal `json:"amount"`
|
Amount decimal.Decimal `json:"amount"`
|
||||||
RechargeType string `json:"recharge_type"`
|
RechargeType string `json:"recharge_type"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
AlipayOrderID string `json:"alipay_order_id,omitempty"`
|
AlipayOrderID string `json:"alipay_order_id,omitempty"`
|
||||||
TransferOrderID string `json:"transfer_order_id,omitempty"`
|
WechatOrderID string `json:"wechat_order_id,omitempty"`
|
||||||
Notes string `json:"notes,omitempty"`
|
TransferOrderID string `json:"transfer_order_id,omitempty"`
|
||||||
OperatorID string `json:"operator_id,omitempty"`
|
Platform string `json:"platform,omitempty"` // 支付平台:pc/wx_native等
|
||||||
CompanyName string `json:"company_name,omitempty"`
|
Notes string `json:"notes,omitempty"`
|
||||||
User *UserSimpleResponse `json:"user,omitempty"`
|
OperatorID string `json:"operator_id,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CompanyName string `json:"company_name,omitempty"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
User *UserSimpleResponse `json:"user,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WalletTransactionResponse 钱包交易记录响应
|
// WalletTransactionResponse 钱包交易记录响应
|
||||||
type WalletTransactionResponse struct {
|
type WalletTransactionResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
ApiCallID string `json:"api_call_id"`
|
ApiCallID string `json:"api_call_id"`
|
||||||
TransactionID string `json:"transaction_id"`
|
TransactionID string `json:"transaction_id"`
|
||||||
ProductID string `json:"product_id"`
|
ProductID string `json:"product_id"`
|
||||||
ProductName string `json:"product_name"`
|
ProductName string `json:"product_name"`
|
||||||
Amount decimal.Decimal `json:"amount"`
|
Amount decimal.Decimal `json:"amount"`
|
||||||
CompanyName string `json:"company_name,omitempty"`
|
CompanyName string `json:"company_name,omitempty"`
|
||||||
User *UserSimpleResponse `json:"user,omitempty"`
|
User *UserSimpleResponse `json:"user,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WalletTransactionListResponse 钱包交易记录列表响应
|
// WalletTransactionListResponse 钱包交易记录列表响应
|
||||||
@@ -97,17 +99,17 @@ type RechargeRecordListResponse struct {
|
|||||||
|
|
||||||
// AlipayRechargeOrderResponse 支付宝充值订单响应
|
// AlipayRechargeOrderResponse 支付宝充值订单响应
|
||||||
type AlipayRechargeOrderResponse struct {
|
type AlipayRechargeOrderResponse struct {
|
||||||
PayURL string `json:"pay_url"` // 支付链接
|
PayURL string `json:"pay_url"` // 支付链接
|
||||||
OutTradeNo string `json:"out_trade_no"` // 商户订单号
|
OutTradeNo string `json:"out_trade_no"` // 商户订单号
|
||||||
Amount decimal.Decimal `json:"amount"` // 充值金额
|
Amount decimal.Decimal `json:"amount"` // 充值金额
|
||||||
Platform string `json:"platform"` // 支付平台
|
Platform string `json:"platform"` // 支付平台
|
||||||
Subject string `json:"subject"` // 订单标题
|
Subject string `json:"subject"` // 订单标题
|
||||||
}
|
}
|
||||||
|
|
||||||
// RechargeConfigResponse 充值配置响应
|
// RechargeConfigResponse 充值配置响应
|
||||||
type RechargeConfigResponse struct {
|
type RechargeConfigResponse struct {
|
||||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||||
AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
|
AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package responses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WechatOrderStatusResponse 微信订单状态响应
|
||||||
|
type WechatOrderStatusResponse struct {
|
||||||
|
OutTradeNo string `json:"out_trade_no"` // 商户订单号
|
||||||
|
TransactionID *string `json:"transaction_id"` // 微信支付交易号
|
||||||
|
Status string `json:"status"` // 订单状态
|
||||||
|
Amount decimal.Decimal `json:"amount"` // 订单金额
|
||||||
|
Subject string `json:"subject"` // 订单标题
|
||||||
|
Platform string `json:"platform"` // 支付平台
|
||||||
|
CreatedAt time.Time `json:"created_at"` // 创建时间
|
||||||
|
UpdatedAt time.Time `json:"updated_at"` // 更新时间
|
||||||
|
NotifyTime *time.Time `json:"notify_time"` // 异步通知时间
|
||||||
|
ReturnTime *time.Time `json:"return_time"` // 同步返回时间
|
||||||
|
ErrorCode *string `json:"error_code"` // 错误码
|
||||||
|
ErrorMessage *string `json:"error_message"` // 错误信息
|
||||||
|
IsProcessing bool `json:"is_processing"` // 是否处理中
|
||||||
|
CanRetry bool `json:"can_retry"` // 是否可以重试
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package responses
|
||||||
|
|
||||||
|
import "github.com/shopspring/decimal"
|
||||||
|
|
||||||
|
// WechatRechargeOrderResponse 微信充值下单响应
|
||||||
|
type WechatRechargeOrderResponse struct {
|
||||||
|
OutTradeNo string `json:"out_trade_no"` // 商户订单号
|
||||||
|
Amount decimal.Decimal `json:"amount"` // 充值金额
|
||||||
|
Platform string `json:"platform"` // 支付平台
|
||||||
|
Subject string `json:"subject"` // 订单标题
|
||||||
|
PrepayData interface{} `json:"prepay_data"` // 预支付数据(APP预支付ID或JSAPI参数)
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ type FinanceApplicationService interface {
|
|||||||
|
|
||||||
// 充值管理
|
// 充值管理
|
||||||
CreateAlipayRechargeOrder(ctx context.Context, cmd *commands.CreateAlipayRechargeCommand) (*responses.AlipayRechargeOrderResponse, error)
|
CreateAlipayRechargeOrder(ctx context.Context, cmd *commands.CreateAlipayRechargeCommand) (*responses.AlipayRechargeOrderResponse, error)
|
||||||
|
CreateWechatRechargeOrder(ctx context.Context, cmd *commands.CreateWechatRechargeCommand) (*responses.WechatRechargeOrderResponse, error)
|
||||||
TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error)
|
TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error)
|
||||||
GiftRecharge(ctx context.Context, cmd *commands.GiftRechargeCommand) (*responses.RechargeRecordResponse, error)
|
GiftRecharge(ctx context.Context, cmd *commands.GiftRechargeCommand) (*responses.RechargeRecordResponse, error)
|
||||||
|
|
||||||
@@ -33,12 +34,15 @@ type FinanceApplicationService interface {
|
|||||||
HandleAlipayReturn(ctx context.Context, outTradeNo string) (string, error)
|
HandleAlipayReturn(ctx context.Context, outTradeNo string) (string, error)
|
||||||
GetAlipayOrderStatus(ctx context.Context, outTradeNo string) (*responses.AlipayOrderStatusResponse, error)
|
GetAlipayOrderStatus(ctx context.Context, outTradeNo string) (*responses.AlipayOrderStatusResponse, error)
|
||||||
|
|
||||||
|
// 微信支付回调处理
|
||||||
|
HandleWechatPayCallback(ctx context.Context, r *http.Request) error
|
||||||
|
HandleWechatRefundCallback(ctx context.Context, r *http.Request) error
|
||||||
|
GetWechatOrderStatus(ctx context.Context, outTradeNo string) (*responses.WechatOrderStatusResponse, error)
|
||||||
|
|
||||||
// 充值记录
|
// 充值记录
|
||||||
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
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)
|
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
||||||
|
|
||||||
// 获取充值配置
|
// 获取充值配置
|
||||||
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
|
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ package finance
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
"github.com/smartwalle/alipay/v3"
|
||||||
|
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
|
||||||
|
"go.uber.org/zap"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
"tyapi-server/internal/application/finance/dto/commands"
|
"tyapi-server/internal/application/finance/dto/commands"
|
||||||
"tyapi-server/internal/application/finance/dto/queries"
|
"tyapi-server/internal/application/finance/dto/queries"
|
||||||
"tyapi-server/internal/application/finance/dto/responses"
|
"tyapi-server/internal/application/finance/dto/responses"
|
||||||
@@ -16,19 +21,18 @@ import (
|
|||||||
"tyapi-server/internal/shared/export"
|
"tyapi-server/internal/shared/export"
|
||||||
"tyapi-server/internal/shared/interfaces"
|
"tyapi-server/internal/shared/interfaces"
|
||||||
"tyapi-server/internal/shared/payment"
|
"tyapi-server/internal/shared/payment"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
"github.com/smartwalle/alipay/v3"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FinanceApplicationServiceImpl 财务应用服务实现
|
// FinanceApplicationServiceImpl 财务应用服务实现
|
||||||
type FinanceApplicationServiceImpl struct {
|
type FinanceApplicationServiceImpl struct {
|
||||||
aliPayClient *payment.AliPayService
|
aliPayClient *payment.AliPayService
|
||||||
|
wechatPayService *payment.WechatPayService
|
||||||
walletService finance_services.WalletAggregateService
|
walletService finance_services.WalletAggregateService
|
||||||
rechargeRecordService finance_services.RechargeRecordService
|
rechargeRecordService finance_services.RechargeRecordService
|
||||||
walletTransactionRepository finance_repositories.WalletTransactionRepository
|
walletTransactionRepository finance_repositories.WalletTransactionRepository
|
||||||
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
||||||
|
wechatOrderRepo finance_repositories.WechatOrderRepository
|
||||||
|
rechargeRecordRepo finance_repositories.RechargeRecordRepository
|
||||||
userRepo user_repositories.UserRepository
|
userRepo user_repositories.UserRepository
|
||||||
txManager *database.TransactionManager
|
txManager *database.TransactionManager
|
||||||
exportManager *export.ExportManager
|
exportManager *export.ExportManager
|
||||||
@@ -39,10 +43,13 @@ type FinanceApplicationServiceImpl struct {
|
|||||||
// NewFinanceApplicationService 创建财务应用服务
|
// NewFinanceApplicationService 创建财务应用服务
|
||||||
func NewFinanceApplicationService(
|
func NewFinanceApplicationService(
|
||||||
aliPayClient *payment.AliPayService,
|
aliPayClient *payment.AliPayService,
|
||||||
|
wechatPayService *payment.WechatPayService,
|
||||||
walletService finance_services.WalletAggregateService,
|
walletService finance_services.WalletAggregateService,
|
||||||
rechargeRecordService finance_services.RechargeRecordService,
|
rechargeRecordService finance_services.RechargeRecordService,
|
||||||
walletTransactionRepository finance_repositories.WalletTransactionRepository,
|
walletTransactionRepository finance_repositories.WalletTransactionRepository,
|
||||||
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
||||||
|
wechatOrderRepo finance_repositories.WechatOrderRepository,
|
||||||
|
rechargeRecordRepo finance_repositories.RechargeRecordRepository,
|
||||||
userRepo user_repositories.UserRepository,
|
userRepo user_repositories.UserRepository,
|
||||||
txManager *database.TransactionManager,
|
txManager *database.TransactionManager,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
@@ -51,10 +58,13 @@ func NewFinanceApplicationService(
|
|||||||
) FinanceApplicationService {
|
) FinanceApplicationService {
|
||||||
return &FinanceApplicationServiceImpl{
|
return &FinanceApplicationServiceImpl{
|
||||||
aliPayClient: aliPayClient,
|
aliPayClient: aliPayClient,
|
||||||
|
wechatPayService: wechatPayService,
|
||||||
walletService: walletService,
|
walletService: walletService,
|
||||||
rechargeRecordService: rechargeRecordService,
|
rechargeRecordService: rechargeRecordService,
|
||||||
walletTransactionRepository: walletTransactionRepository,
|
walletTransactionRepository: walletTransactionRepository,
|
||||||
alipayOrderRepo: alipayOrderRepo,
|
alipayOrderRepo: alipayOrderRepo,
|
||||||
|
wechatOrderRepo: wechatOrderRepo,
|
||||||
|
rechargeRecordRepo: rechargeRecordRepo,
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
txManager: txManager,
|
txManager: txManager,
|
||||||
exportManager: exportManager,
|
exportManager: exportManager,
|
||||||
@@ -100,8 +110,9 @@ func (s *FinanceApplicationServiceImpl) GetWallet(ctx context.Context, query *qu
|
|||||||
BalanceStatus: wallet.GetBalanceStatus(),
|
BalanceStatus: wallet.GetBalanceStatus(),
|
||||||
IsArrears: wallet.IsArrears(),
|
IsArrears: wallet.IsArrears(),
|
||||||
IsLowBalance: wallet.IsLowBalance(),
|
IsLowBalance: wallet.IsLowBalance(),
|
||||||
CreatedAt: wallet.CreatedAt,
|
|
||||||
UpdatedAt: wallet.UpdatedAt,
|
CreatedAt: wallet.CreatedAt,
|
||||||
|
UpdatedAt: wallet.UpdatedAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +199,168 @@ func (s *FinanceApplicationServiceImpl) CreateAlipayRechargeOrder(ctx context.Co
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateWechatRechargeOrder 创建微信充值订单(完整流程编排)
|
||||||
|
func (s *FinanceApplicationServiceImpl) CreateWechatRechargeOrder(ctx context.Context, cmd *commands.CreateWechatRechargeCommand) (*responses.WechatRechargeOrderResponse, error) {
|
||||||
|
cmd.Subject = "天远数据API充值"
|
||||||
|
amount, err := decimal.NewFromString(cmd.Amount)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("金额格式错误: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if amount.LessThanOrEqual(decimal.Zero) {
|
||||||
|
return nil, fmt.Errorf("充值金额必须大于0")
|
||||||
|
}
|
||||||
|
|
||||||
|
minAmount, err := decimal.NewFromString(s.config.Wallet.MinAmount)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("配置中的最低充值金额格式错误", zap.String("min_amount", s.config.Wallet.MinAmount), zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("系统配置错误: %w", err)
|
||||||
|
}
|
||||||
|
maxAmount, err := decimal.NewFromString(s.config.Wallet.MaxAmount)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("配置中的最高充值金额格式错误", zap.String("max_amount", s.config.Wallet.MaxAmount), zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("系统配置错误: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if amount.LessThan(minAmount) {
|
||||||
|
return nil, fmt.Errorf("充值金额不能少于%s元", minAmount.String())
|
||||||
|
}
|
||||||
|
if amount.GreaterThan(maxAmount) {
|
||||||
|
return nil, fmt.Errorf("单次充值金额不能超过%s元", maxAmount.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
platform := normalizeWechatPlatform(cmd.Platform)
|
||||||
|
if platform != payment.PlatformWxNative && platform != payment.PlatformWxH5 {
|
||||||
|
return nil, fmt.Errorf("不支持的支付平台: %s", cmd.Platform)
|
||||||
|
}
|
||||||
|
if s.wechatPayService == nil {
|
||||||
|
return nil, fmt.Errorf("微信支付服务未初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
outTradeNo := s.wechatPayService.GenerateOutTradeNo()
|
||||||
|
|
||||||
|
s.logger.Info("开始创建微信充值订单",
|
||||||
|
zap.String("user_id", cmd.UserID),
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("amount", amount.String()),
|
||||||
|
zap.String("platform", cmd.Platform),
|
||||||
|
zap.String("subject", cmd.Subject),
|
||||||
|
)
|
||||||
|
|
||||||
|
var prepayData interface{}
|
||||||
|
|
||||||
|
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||||
|
// 创建微信充值记录
|
||||||
|
rechargeRecord := finance_entities.NewWechatRechargeRecord(cmd.UserID, amount, outTradeNo)
|
||||||
|
createdRecord, createErr := s.rechargeRecordRepo.Create(txCtx, *rechargeRecord)
|
||||||
|
if createErr != nil {
|
||||||
|
s.logger.Error("创建微信充值记录失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("user_id", cmd.UserID),
|
||||||
|
zap.String("amount", amount.String()),
|
||||||
|
zap.Error(createErr),
|
||||||
|
)
|
||||||
|
return fmt.Errorf("创建微信充值记录失败: %w", createErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("创建微信充值记录成功",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("recharge_id", createdRecord.ID),
|
||||||
|
zap.String("user_id", cmd.UserID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 创建微信订单本地记录
|
||||||
|
wechatOrder := finance_entities.NewWechatOrder(createdRecord.ID, outTradeNo, cmd.Subject, amount, platform)
|
||||||
|
createdOrder, orderErr := s.wechatOrderRepo.Create(txCtx, *wechatOrder)
|
||||||
|
if orderErr != nil {
|
||||||
|
s.logger.Error("创建微信订单记录失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("recharge_id", createdRecord.ID),
|
||||||
|
zap.Error(orderErr),
|
||||||
|
)
|
||||||
|
return fmt.Errorf("创建微信订单记录失败: %w", orderErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("创建微信订单记录成功",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("order_id", createdOrder.ID),
|
||||||
|
zap.String("recharge_id", createdRecord.ID),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
payCtx := context.WithValue(ctx, "platform", platform)
|
||||||
|
payCtx = context.WithValue(payCtx, "user_id", cmd.UserID)
|
||||||
|
|
||||||
|
s.logger.Info("调用微信支付接口创建订单",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("platform", platform),
|
||||||
|
)
|
||||||
|
|
||||||
|
prepayData, err = s.wechatPayService.CreateWechatOrder(payCtx, amount.InexactFloat64(), cmd.Subject, outTradeNo)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("微信下单失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("user_id", cmd.UserID),
|
||||||
|
zap.String("amount", amount.String()),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 回写失败状态
|
||||||
|
_ = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||||
|
order, getErr := s.wechatOrderRepo.GetByOutTradeNo(txCtx, outTradeNo)
|
||||||
|
if getErr == nil && order != nil {
|
||||||
|
order.MarkFailed("create_failed", err.Error())
|
||||||
|
updateErr := s.wechatOrderRepo.Update(txCtx, *order)
|
||||||
|
if updateErr != nil {
|
||||||
|
s.logger.Error("回写微信订单失败状态失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.Error(updateErr),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
s.logger.Info("回写微信订单失败状态成功",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("创建微信支付订单失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("微信充值订单创建成功",
|
||||||
|
zap.String("user_id", cmd.UserID),
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("amount", amount.String()),
|
||||||
|
zap.String("platform", cmd.Platform),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &responses.WechatRechargeOrderResponse{
|
||||||
|
OutTradeNo: outTradeNo,
|
||||||
|
Amount: amount,
|
||||||
|
Platform: platform,
|
||||||
|
Subject: cmd.Subject,
|
||||||
|
PrepayData: prepayData,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeWechatPlatform 将兼容写法(h5/mini)转换为系统内使用的wx_h5/wx_mini
|
||||||
|
func normalizeWechatPlatform(p string) string {
|
||||||
|
switch p {
|
||||||
|
case "h5", payment.PlatformWxH5:
|
||||||
|
return payment.PlatformWxNative
|
||||||
|
case "native":
|
||||||
|
return payment.PlatformWxNative
|
||||||
|
default:
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TransferRecharge 对公转账充值
|
// TransferRecharge 对公转账充值
|
||||||
func (s *FinanceApplicationServiceImpl) TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error) {
|
func (s *FinanceApplicationServiceImpl) TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error) {
|
||||||
// 将字符串金额转换为 decimal.Decimal
|
// 将字符串金额转换为 decimal.Decimal
|
||||||
@@ -507,8 +680,8 @@ func (s *FinanceApplicationServiceImpl) ExportAdminRechargeRecords(ctx context.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 准备导出数据
|
// 准备导出数据
|
||||||
headers := []string{"企业名称", "充值金额", "充值类型", "状态", "支付宝订单号", "转账订单号", "备注", "充值时间"}
|
headers := []string{"企业名称", "充值金额", "充值类型", "状态", "支付宝订单号", "微信订单号", "转账订单号", "备注", "充值时间"}
|
||||||
columnWidths := []float64{25, 15, 15, 10, 20, 20, 20, 20}
|
columnWidths := []float64{25, 15, 15, 10, 20, 20, 20, 20, 20}
|
||||||
|
|
||||||
data := make([][]interface{}, len(allRecords))
|
data := make([][]interface{}, len(allRecords))
|
||||||
for i, record := range allRecords {
|
for i, record := range allRecords {
|
||||||
@@ -523,6 +696,10 @@ func (s *FinanceApplicationServiceImpl) ExportAdminRechargeRecords(ctx context.C
|
|||||||
if record.AlipayOrderID != nil && *record.AlipayOrderID != "" {
|
if record.AlipayOrderID != nil && *record.AlipayOrderID != "" {
|
||||||
alipayOrderID = *record.AlipayOrderID
|
alipayOrderID = *record.AlipayOrderID
|
||||||
}
|
}
|
||||||
|
wechatOrderID := ""
|
||||||
|
if record.WechatOrderID != nil && *record.WechatOrderID != "" {
|
||||||
|
wechatOrderID = *record.WechatOrderID
|
||||||
|
}
|
||||||
transferOrderID := ""
|
transferOrderID := ""
|
||||||
if record.TransferOrderID != nil && *record.TransferOrderID != "" {
|
if record.TransferOrderID != nil && *record.TransferOrderID != "" {
|
||||||
transferOrderID = *record.TransferOrderID
|
transferOrderID = *record.TransferOrderID
|
||||||
@@ -543,6 +720,7 @@ func (s *FinanceApplicationServiceImpl) ExportAdminRechargeRecords(ctx context.C
|
|||||||
translateRechargeType(record.RechargeType),
|
translateRechargeType(record.RechargeType),
|
||||||
translateRechargeStatus(record.Status),
|
translateRechargeStatus(record.Status),
|
||||||
alipayOrderID,
|
alipayOrderID,
|
||||||
|
wechatOrderID,
|
||||||
transferOrderID,
|
transferOrderID,
|
||||||
notes,
|
notes,
|
||||||
createdAt,
|
createdAt,
|
||||||
@@ -566,6 +744,8 @@ func translateRechargeType(rechargeType finance_entities.RechargeType) string {
|
|||||||
switch rechargeType {
|
switch rechargeType {
|
||||||
case finance_entities.RechargeTypeAlipay:
|
case finance_entities.RechargeTypeAlipay:
|
||||||
return "支付宝充值"
|
return "支付宝充值"
|
||||||
|
case finance_entities.RechargeTypeWechat:
|
||||||
|
return "微信充值"
|
||||||
case finance_entities.RechargeTypeTransfer:
|
case finance_entities.RechargeTypeTransfer:
|
||||||
return "对公转账"
|
return "对公转账"
|
||||||
case finance_entities.RechargeTypeGift:
|
case finance_entities.RechargeTypeGift:
|
||||||
@@ -890,15 +1070,27 @@ func (s *FinanceApplicationServiceImpl) GetAlipayOrderStatus(ctx context.Context
|
|||||||
|
|
||||||
// GetUserRechargeRecords 获取用户充值记录
|
// GetUserRechargeRecords 获取用户充值记录
|
||||||
func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
|
func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
|
||||||
// 查询用户充值记录
|
// 确保 filters 不为 nil
|
||||||
records, err := s.rechargeRecordService.GetByUserID(ctx, userID)
|
if filters == nil {
|
||||||
|
filters = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 user_id 筛选条件,确保只能查询当前用户的记录
|
||||||
|
filters["user_id"] = userID
|
||||||
|
|
||||||
|
// 查询用户充值记录(使用筛选和分页功能)
|
||||||
|
records, err := s.rechargeRecordService.GetAll(ctx, filters, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("查询用户充值记录失败", zap.Error(err), zap.String("userID", userID))
|
s.logger.Error("查询用户充值记录失败", zap.Error(err), zap.String("userID", userID))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算总数
|
// 获取总数(使用筛选条件)
|
||||||
total := int64(len(records))
|
total, err := s.rechargeRecordService.Count(ctx, filters)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("统计用户充值记录失败", zap.Error(err), zap.String("userID", userID))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// 转换为响应DTO
|
// 转换为响应DTO
|
||||||
var items []responses.RechargeRecordResponse
|
var items []responses.RechargeRecordResponse
|
||||||
@@ -914,9 +1106,20 @@ func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Conte
|
|||||||
UpdatedAt: record.UpdatedAt,
|
UpdatedAt: record.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据充值类型设置相应的订单号
|
// 根据充值类型设置相应的订单号和平台信息
|
||||||
if record.AlipayOrderID != nil {
|
if record.AlipayOrderID != nil {
|
||||||
item.AlipayOrderID = *record.AlipayOrderID
|
item.AlipayOrderID = *record.AlipayOrderID
|
||||||
|
// 通过订单号获取平台信息
|
||||||
|
if alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, *record.AlipayOrderID); err == nil && alipayOrder != nil {
|
||||||
|
item.Platform = alipayOrder.Platform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if record.WechatOrderID != nil {
|
||||||
|
item.WechatOrderID = *record.WechatOrderID
|
||||||
|
// 通过订单号获取平台信息
|
||||||
|
if wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, *record.WechatOrderID); err == nil && wechatOrder != nil {
|
||||||
|
item.Platform = wechatOrder.Platform
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if record.TransferOrderID != nil {
|
if record.TransferOrderID != nil {
|
||||||
item.TransferOrderID = *record.TransferOrderID
|
item.TransferOrderID = *record.TransferOrderID
|
||||||
@@ -963,9 +1166,20 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
|
|||||||
UpdatedAt: record.UpdatedAt,
|
UpdatedAt: record.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据充值类型设置相应的订单号
|
// 根据充值类型设置相应的订单号和平台信息
|
||||||
if record.AlipayOrderID != nil {
|
if record.AlipayOrderID != nil {
|
||||||
item.AlipayOrderID = *record.AlipayOrderID
|
item.AlipayOrderID = *record.AlipayOrderID
|
||||||
|
// 通过订单号获取平台信息
|
||||||
|
if alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, *record.AlipayOrderID); err == nil && alipayOrder != nil {
|
||||||
|
item.Platform = alipayOrder.Platform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if record.WechatOrderID != nil {
|
||||||
|
item.WechatOrderID = *record.WechatOrderID
|
||||||
|
// 通过订单号获取平台信息
|
||||||
|
if wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, *record.WechatOrderID); err == nil && wechatOrder != nil {
|
||||||
|
item.Platform = wechatOrder.Platform
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if record.TransferOrderID != nil {
|
if record.TransferOrderID != nil {
|
||||||
item.TransferOrderID = *record.TransferOrderID
|
item.TransferOrderID = *record.TransferOrderID
|
||||||
@@ -1012,3 +1226,445 @@ func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (
|
|||||||
AlipayRechargeBonus: bonus,
|
AlipayRechargeBonus: bonus,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWechatOrderStatus 获取微信订单状态
|
||||||
|
func (s *FinanceApplicationServiceImpl) GetWechatOrderStatus(ctx context.Context, outTradeNo string) (*responses.WechatOrderStatusResponse, error) {
|
||||||
|
if outTradeNo == "" {
|
||||||
|
return nil, fmt.Errorf("缺少商户订单号")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找微信订单
|
||||||
|
wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("查找微信订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("查找微信订单失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wechatOrder == nil {
|
||||||
|
s.logger.Error("微信订单不存在", zap.String("out_trade_no", outTradeNo))
|
||||||
|
return nil, fmt.Errorf("微信订单不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果订单状态为pending,主动查询微信订单状态
|
||||||
|
if wechatOrder.Status == finance_entities.WechatOrderStatusPending {
|
||||||
|
s.logger.Info("订单状态为pending,主动查询微信订单状态",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 调用微信查询接口
|
||||||
|
transaction, err := s.wechatPayService.QueryOrderStatus(ctx, outTradeNo)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("查询微信订单状态失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
// 查询失败不影响返回,继续使用数据库中的状态
|
||||||
|
} else {
|
||||||
|
// 解析微信返回的状态
|
||||||
|
tradeState := ""
|
||||||
|
transactionID := ""
|
||||||
|
if transaction.TradeState != nil {
|
||||||
|
tradeState = *transaction.TradeState
|
||||||
|
}
|
||||||
|
if transaction.TransactionId != nil {
|
||||||
|
transactionID = *transaction.TransactionId
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("微信查询订单状态返回",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("trade_state", tradeState),
|
||||||
|
zap.String("transaction_id", transactionID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 使用公共方法更新订单状态
|
||||||
|
err = s.updateWechatOrderStatus(ctx, outTradeNo, tradeState, transaction)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("更新微信订单状态失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("trade_state", tradeState),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新获取更新后的订单信息
|
||||||
|
updatedOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||||
|
if err == nil && updatedOrder != nil {
|
||||||
|
wechatOrder = updatedOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否处理中
|
||||||
|
isProcessing := wechatOrder.Status == finance_entities.WechatOrderStatusPending
|
||||||
|
|
||||||
|
// 判断是否可以重试(失败状态可以重试)
|
||||||
|
canRetry := wechatOrder.Status == finance_entities.WechatOrderStatusFailed
|
||||||
|
|
||||||
|
// 转换为响应DTO
|
||||||
|
response := &responses.WechatOrderStatusResponse{
|
||||||
|
OutTradeNo: wechatOrder.OutTradeNo,
|
||||||
|
TransactionID: wechatOrder.TradeNo,
|
||||||
|
Status: string(wechatOrder.Status),
|
||||||
|
Amount: wechatOrder.Amount,
|
||||||
|
Subject: wechatOrder.Subject,
|
||||||
|
Platform: wechatOrder.Platform,
|
||||||
|
CreatedAt: wechatOrder.CreatedAt,
|
||||||
|
UpdatedAt: wechatOrder.UpdatedAt,
|
||||||
|
NotifyTime: wechatOrder.NotifyTime,
|
||||||
|
ReturnTime: wechatOrder.ReturnTime,
|
||||||
|
ErrorCode: &wechatOrder.ErrorCode,
|
||||||
|
ErrorMessage: &wechatOrder.ErrorMessage,
|
||||||
|
IsProcessing: isProcessing,
|
||||||
|
CanRetry: canRetry,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果错误码为空,设置为nil
|
||||||
|
if wechatOrder.ErrorCode == "" {
|
||||||
|
response.ErrorCode = nil
|
||||||
|
}
|
||||||
|
if wechatOrder.ErrorMessage == "" {
|
||||||
|
response.ErrorMessage = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("查询微信订单状态完成",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("status", string(wechatOrder.Status)),
|
||||||
|
zap.Bool("is_processing", isProcessing),
|
||||||
|
zap.Bool("can_retry", canRetry),
|
||||||
|
)
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateWechatOrderStatus 根据微信状态更新本地订单状态
|
||||||
|
func (s *FinanceApplicationServiceImpl) updateWechatOrderStatus(ctx context.Context, outTradeNo string, tradeState string, transaction *payments.Transaction) error {
|
||||||
|
// 查找微信订单
|
||||||
|
wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("查找微信订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||||
|
return fmt.Errorf("查找微信订单失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wechatOrder == nil {
|
||||||
|
s.logger.Error("微信订单不存在", zap.String("out_trade_no", outTradeNo))
|
||||||
|
return fmt.Errorf("微信订单不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tradeState {
|
||||||
|
case payment.TradeStateSuccess:
|
||||||
|
// 支付成功,调用公共处理逻辑
|
||||||
|
transactionID := ""
|
||||||
|
if transaction.TransactionId != nil {
|
||||||
|
transactionID = *transaction.TransactionId
|
||||||
|
}
|
||||||
|
payAmount := decimal.Zero
|
||||||
|
if transaction.Amount != nil && transaction.Amount.Total != nil {
|
||||||
|
// 将分转换为元
|
||||||
|
payAmount = decimal.NewFromInt(*transaction.Amount.Total).Div(decimal.NewFromInt(100))
|
||||||
|
}
|
||||||
|
return s.processWechatPaymentSuccess(ctx, outTradeNo, transactionID, payAmount)
|
||||||
|
case payment.TradeStateClosed:
|
||||||
|
// 交易关闭
|
||||||
|
s.logger.Info("微信订单交易关闭",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
)
|
||||||
|
wechatOrder.MarkClosed()
|
||||||
|
err = s.wechatOrderRepo.Update(ctx, *wechatOrder)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("更新微信订单关闭状态失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Info("微信订单关闭状态更新成功",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
)
|
||||||
|
case payment.TradeStateNotPay:
|
||||||
|
// 未支付,保持pending状态
|
||||||
|
s.logger.Info("微信订单未支付",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
// 其他状态,记录日志
|
||||||
|
s.logger.Info("微信订单其他状态",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("trade_state", tradeState),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleWechatPayCallback 处理微信支付回调
|
||||||
|
func (s *FinanceApplicationServiceImpl) HandleWechatPayCallback(ctx context.Context, r *http.Request) error {
|
||||||
|
if s.wechatPayService == nil {
|
||||||
|
s.logger.Error("微信支付服务未初始化")
|
||||||
|
return fmt.Errorf("微信支付服务未初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析并验证微信支付回调通知
|
||||||
|
transaction, err := s.wechatPayService.HandleWechatPayNotification(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("微信支付回调验证失败", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取回调数据
|
||||||
|
outTradeNo := ""
|
||||||
|
if transaction.OutTradeNo != nil {
|
||||||
|
outTradeNo = *transaction.OutTradeNo
|
||||||
|
}
|
||||||
|
transactionID := ""
|
||||||
|
if transaction.TransactionId != nil {
|
||||||
|
transactionID = *transaction.TransactionId
|
||||||
|
}
|
||||||
|
tradeState := ""
|
||||||
|
if transaction.TradeState != nil {
|
||||||
|
tradeState = *transaction.TradeState
|
||||||
|
}
|
||||||
|
totalAmount := decimal.Zero
|
||||||
|
if transaction.Amount != nil && transaction.Amount.Total != nil {
|
||||||
|
// 将分转换为元
|
||||||
|
totalAmount = decimal.NewFromInt(*transaction.Amount.Total).Div(decimal.NewFromInt(100))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录回调数据
|
||||||
|
s.logger.Info("微信支付回调数据",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("transaction_id", transactionID),
|
||||||
|
zap.String("trade_state", tradeState),
|
||||||
|
zap.String("total_amount", totalAmount.String()),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 检查交易状态
|
||||||
|
if tradeState != payment.TradeStateSuccess {
|
||||||
|
s.logger.Warn("微信支付交易未成功",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("trade_state", tradeState),
|
||||||
|
)
|
||||||
|
return nil // 不返回错误,因为这是正常的业务状态
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理支付成功逻辑
|
||||||
|
err = s.processWechatPaymentSuccess(ctx, outTradeNo, transactionID, totalAmount)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("处理微信支付成功失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("transaction_id", transactionID),
|
||||||
|
zap.String("amount", totalAmount.String()),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processWechatPaymentSuccess 处理微信支付成功的公共逻辑
|
||||||
|
func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.Context, outTradeNo, transactionID string, amount decimal.Decimal) error {
|
||||||
|
// 查找微信订单
|
||||||
|
wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("查找微信订单失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return fmt.Errorf("查找微信订单失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wechatOrder == nil {
|
||||||
|
s.logger.Error("微信订单不存在",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
)
|
||||||
|
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 wechatOrder.Status == finance_entities.WechatOrderStatusSuccess && rechargeRecord.Status == finance_entities.RechargeStatusSuccess {
|
||||||
|
s.logger.Info("微信支付订单已处理成功,跳过重复处理",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("transaction_id", transactionID),
|
||||||
|
zap.String("order_id", wechatOrder.ID),
|
||||||
|
zap.String("recharge_id", rechargeRecord.ID),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算充值赠送金额(复用支付宝的赠送逻辑)
|
||||||
|
bonusAmount := decimal.Zero
|
||||||
|
if 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)) {
|
||||||
|
bonusAmount = decimal.NewFromFloat(rule.BonusAmount)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录开始处理支付成功
|
||||||
|
s.logger.Info("开始处理微信支付成功",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("transaction_id", transactionID),
|
||||||
|
zap.String("amount", amount.String()),
|
||||||
|
zap.String("user_id", rechargeRecord.UserID),
|
||||||
|
zap.String("bonus_amount", bonusAmount.String()),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 在事务中处理支付成功逻辑
|
||||||
|
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||||
|
// 更新微信订单状态
|
||||||
|
wechatOrder.MarkSuccess(transactionID, "", "", amount, amount)
|
||||||
|
now := time.Now()
|
||||||
|
wechatOrder.NotifyTime = &now
|
||||||
|
err := s.wechatOrderRepo.Update(txCtx, *wechatOrder)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("更新微信订单状态失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新充值记录状态为成功
|
||||||
|
rechargeRecord.MarkSuccess()
|
||||||
|
err = s.rechargeRecordRepo.Update(txCtx, *rechargeRecord)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("更新充值记录状态失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("recharge_id", rechargeRecord.ID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有赠送金额,创建赠送充值记录
|
||||||
|
if bonusAmount.GreaterThan(decimal.Zero) {
|
||||||
|
giftRechargeRecord := finance_entities.NewGiftRechargeRecord(rechargeRecord.UserID, bonusAmount, "充值活动赠送")
|
||||||
|
createdGift, err := s.rechargeRecordRepo.Create(txCtx, *giftRechargeRecord)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("创建赠送充值记录失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("user_id", rechargeRecord.UserID),
|
||||||
|
zap.String("bonus_amount", bonusAmount.String()),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Info("创建赠送充值记录成功",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("gift_recharge_id", createdGift.ID),
|
||||||
|
zap.String("bonus_amount", bonusAmount.String()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 充值到钱包(包含赠送金额)
|
||||||
|
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 nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("处理微信支付成功失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("transaction_id", transactionID),
|
||||||
|
zap.String("amount", amount.String()),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleWechatRefundCallback 处理微信退款回调
|
||||||
|
func (s *FinanceApplicationServiceImpl) HandleWechatRefundCallback(ctx context.Context, r *http.Request) error {
|
||||||
|
if s.wechatPayService == nil {
|
||||||
|
s.logger.Error("微信支付服务未初始化")
|
||||||
|
return fmt.Errorf("微信支付服务未初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析并验证微信退款回调通知
|
||||||
|
refund, err := s.wechatPayService.HandleRefundNotification(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("微信退款回调验证失败", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录回调数据
|
||||||
|
s.logger.Info("微信退款回调数据",
|
||||||
|
zap.String("out_trade_no", func() string {
|
||||||
|
if refund.OutTradeNo != nil {
|
||||||
|
return *refund.OutTradeNo
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}()),
|
||||||
|
zap.String("out_refund_no", func() string {
|
||||||
|
if refund.OutRefundNo != nil {
|
||||||
|
return *refund.OutRefundNo
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}()),
|
||||||
|
zap.String("refund_id", func() string {
|
||||||
|
if refund.RefundId != nil {
|
||||||
|
return *refund.RefundId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}()),
|
||||||
|
zap.Any("status", func() interface{} {
|
||||||
|
if refund.Status != nil {
|
||||||
|
return *refund.Status
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 处理退款逻辑
|
||||||
|
// 这里可以根据实际业务需求实现退款处理逻辑
|
||||||
|
s.logger.Info("微信退款回调处理完成",
|
||||||
|
zap.String("out_trade_no", func() string {
|
||||||
|
if refund.OutTradeNo != nil {
|
||||||
|
return *refund.OutTradeNo
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}()),
|
||||||
|
zap.String("refund_id", func() string {
|
||||||
|
if refund.RefundId != nil {
|
||||||
|
return *refund.RefundId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 获取真实充值金额(支付宝充值+对公转账)和总赠送金额
|
// 3. 获取真实充值金额(支付宝充值+微信充值+对公转账)和总赠送金额
|
||||||
realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID)
|
realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -408,7 +408,7 @@ func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context,
|
|||||||
// 5. 构建响应DTO
|
// 5. 构建响应DTO
|
||||||
return &dto.AvailableAmountResponse{
|
return &dto.AvailableAmountResponse{
|
||||||
AvailableAmount: availableAmount,
|
AvailableAmount: availableAmount,
|
||||||
TotalRecharged: realRecharged, // 使用真实充值金额(支付宝充值+对公转账)
|
TotalRecharged: realRecharged, // 使用真实充值金额(支付宝充值+微信充值+对公转账)
|
||||||
TotalGifted: totalGifted,
|
TotalGifted: totalGifted,
|
||||||
TotalInvoiced: totalInvoiced,
|
TotalInvoiced: totalInvoiced,
|
||||||
PendingApplications: pendingAmount,
|
PendingApplications: pendingAmount,
|
||||||
@@ -417,7 +417,7 @@ func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context,
|
|||||||
|
|
||||||
// calculateAvailableAmount 计算可开票金额(私有方法)
|
// calculateAvailableAmount 计算可开票金额(私有方法)
|
||||||
func (s *InvoiceApplicationServiceImpl) calculateAvailableAmount(ctx context.Context, userID string) (decimal.Decimal, error) {
|
func (s *InvoiceApplicationServiceImpl) calculateAvailableAmount(ctx context.Context, userID string) (decimal.Decimal, error) {
|
||||||
// 1. 获取真实充值金额(支付宝充值+对公转账)和总赠送金额
|
// 1. 获取真实充值金额(支付宝充值+微信充值+对公转账)和总赠送金额
|
||||||
realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID)
|
realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return decimal.Zero, err
|
return decimal.Zero, err
|
||||||
@@ -433,7 +433,7 @@ func (s *InvoiceApplicationServiceImpl) calculateAvailableAmount(ctx context.Con
|
|||||||
fmt.Println("totalInvoiced", totalInvoiced)
|
fmt.Println("totalInvoiced", totalInvoiced)
|
||||||
fmt.Println("pendingAmount", pendingAmount)
|
fmt.Println("pendingAmount", pendingAmount)
|
||||||
// 3. 计算可开票金额:真实充值金额 - 已开票 - 待处理申请
|
// 3. 计算可开票金额:真实充值金额 - 已开票 - 待处理申请
|
||||||
// 可开票金额 = 真实充值金额(支付宝充值+对公转账) - 已开票金额 - 待处理申请金额
|
// 可开票金额 = 真实充值金额(支付宝充值+微信充值+对公转账) - 已开票金额 - 待处理申请金额
|
||||||
availableAmount := realRecharged.Sub(totalInvoiced).Sub(pendingAmount)
|
availableAmount := realRecharged.Sub(totalInvoiced).Sub(pendingAmount)
|
||||||
fmt.Println("availableAmount", availableAmount)
|
fmt.Println("availableAmount", availableAmount)
|
||||||
// 确保可开票金额不为负数
|
// 确保可开票金额不为负数
|
||||||
@@ -452,16 +452,16 @@ func (s *InvoiceApplicationServiceImpl) getAmountSummary(ctx context.Context, us
|
|||||||
return decimal.Zero, decimal.Zero, decimal.Zero, fmt.Errorf("获取充值记录失败: %w", err)
|
return decimal.Zero, decimal.Zero, decimal.Zero, fmt.Errorf("获取充值记录失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 计算真实充值金额(支付宝充值 + 对公转账)和总赠送金额
|
// 2. 计算真实充值金额(支付宝充值 + 微信充值 + 对公转账)和总赠送金额
|
||||||
var realRecharged decimal.Decimal // 真实充值金额:支付宝充值 + 对公转账
|
var realRecharged decimal.Decimal // 真实充值金额:支付宝充值 + 微信充值 + 对公转账
|
||||||
var totalGifted decimal.Decimal // 总赠送金额
|
var totalGifted decimal.Decimal // 总赠送金额
|
||||||
for _, record := range rechargeRecords {
|
for _, record := range rechargeRecords {
|
||||||
if record.IsSuccess() {
|
if record.IsSuccess() {
|
||||||
if record.RechargeType == entities.RechargeTypeGift {
|
if record.RechargeType == entities.RechargeTypeGift {
|
||||||
// 赠送金额不计入可开票金额
|
// 赠送金额不计入可开票金额
|
||||||
totalGifted = totalGifted.Add(record.Amount)
|
totalGifted = totalGifted.Add(record.Amount)
|
||||||
} else if record.RechargeType == entities.RechargeTypeAlipay || record.RechargeType == entities.RechargeTypeTransfer {
|
} else if record.RechargeType == entities.RechargeTypeAlipay || record.RechargeType == entities.RechargeTypeWechat || record.RechargeType == entities.RechargeTypeTransfer {
|
||||||
// 只有支付宝充值和对公转账计入可开票金额
|
// 支付宝充值、微信充值和对公转账计入可开票金额
|
||||||
realRecharged = realRecharged.Add(record.Amount)
|
realRecharged = realRecharged.Add(record.Amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package product
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"tyapi-server/internal/application/product/dto/commands"
|
"tyapi-server/internal/application/product/dto/commands"
|
||||||
"tyapi-server/internal/application/product/dto/responses"
|
"tyapi-server/internal/application/product/dto/responses"
|
||||||
@@ -28,6 +30,9 @@ type DocumentationApplicationServiceInterface interface {
|
|||||||
|
|
||||||
// GetDocumentationsByProductIDs 批量获取文档
|
// GetDocumentationsByProductIDs 批量获取文档
|
||||||
GetDocumentationsByProductIDs(ctx context.Context, productIDs []string) ([]responses.DocumentationResponse, error)
|
GetDocumentationsByProductIDs(ctx context.Context, productIDs []string) ([]responses.DocumentationResponse, error)
|
||||||
|
|
||||||
|
// GenerateFullDocumentation 生成完整的接口文档(Markdown格式)
|
||||||
|
GenerateFullDocumentation(ctx context.Context, productID string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DocumentationApplicationService 文档应用服务
|
// DocumentationApplicationService 文档应用服务
|
||||||
@@ -53,6 +58,7 @@ func (s *DocumentationApplicationService) CreateDocumentation(ctx context.Contex
|
|||||||
ResponseFields: cmd.ResponseFields,
|
ResponseFields: cmd.ResponseFields,
|
||||||
ResponseExample: cmd.ResponseExample,
|
ResponseExample: cmd.ResponseExample,
|
||||||
ErrorCodes: cmd.ErrorCodes,
|
ErrorCodes: cmd.ErrorCodes,
|
||||||
|
PDFFilePath: cmd.PDFFilePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用领域服务创建文档
|
// 调用领域服务创建文档
|
||||||
@@ -88,6 +94,20 @@ func (s *DocumentationApplicationService) UpdateDocumentation(ctx context.Contex
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新PDF文件路径(如果提供)
|
||||||
|
if cmd.PDFFilePath != "" {
|
||||||
|
doc.PDFFilePath = cmd.PDFFilePath
|
||||||
|
err = s.docService.UpdateDocumentationEntity(ctx, doc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("更新PDF文件路径失败: %w", err)
|
||||||
|
}
|
||||||
|
// 重新获取更新后的文档以确保获取最新数据
|
||||||
|
doc, err = s.docService.GetDocumentation(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 返回响应
|
// 返回响应
|
||||||
resp := responses.NewDocumentationResponse(doc)
|
resp := responses.NewDocumentationResponse(doc)
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
@@ -136,3 +156,93 @@ func (s *DocumentationApplicationService) GetDocumentationsByProductIDs(ctx cont
|
|||||||
|
|
||||||
return docResponses, nil
|
return docResponses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateFullDocumentation 生成完整的接口文档(Markdown格式)
|
||||||
|
func (s *DocumentationApplicationService) GenerateFullDocumentation(ctx context.Context, productID string) (string, error) {
|
||||||
|
// 通过产品ID获取文档
|
||||||
|
doc, err := s.docService.GetDocumentationByProductID(ctx, productID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("获取文档失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文档时已经包含了产品信息(通过GetDocumentationWithProduct)
|
||||||
|
// 如果没有产品信息,通过文档ID获取
|
||||||
|
if doc.Product == nil && doc.ID != "" {
|
||||||
|
docWithProduct, err := s.docService.GetDocumentationWithProduct(ctx, doc.ID)
|
||||||
|
if err == nil && docWithProduct != nil {
|
||||||
|
doc = docWithProduct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var markdown strings.Builder
|
||||||
|
|
||||||
|
// 添加文档标题
|
||||||
|
productName := "产品"
|
||||||
|
if doc.Product != nil {
|
||||||
|
productName = doc.Product.Name
|
||||||
|
}
|
||||||
|
markdown.WriteString(fmt.Sprintf("# %s 接口文档\n\n", productName))
|
||||||
|
|
||||||
|
// 添加产品基本信息
|
||||||
|
if doc.Product != nil {
|
||||||
|
markdown.WriteString("## 产品信息\n\n")
|
||||||
|
markdown.WriteString(fmt.Sprintf("- **产品名称**: %s\n", doc.Product.Name))
|
||||||
|
markdown.WriteString(fmt.Sprintf("- **产品编号**: %s\n", doc.Product.Code))
|
||||||
|
if doc.Product.Description != "" {
|
||||||
|
markdown.WriteString(fmt.Sprintf("- **产品描述**: %s\n", doc.Product.Description))
|
||||||
|
}
|
||||||
|
markdown.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加请求方式
|
||||||
|
markdown.WriteString("## 请求方式\n\n")
|
||||||
|
if doc.RequestURL != "" {
|
||||||
|
markdown.WriteString(fmt.Sprintf("- **请求方法**: %s\n", doc.RequestMethod))
|
||||||
|
markdown.WriteString(fmt.Sprintf("- **请求地址**: %s\n", doc.RequestURL))
|
||||||
|
markdown.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加请求方式详细说明
|
||||||
|
if doc.BasicInfo != "" {
|
||||||
|
markdown.WriteString("### 请求方式说明\n\n")
|
||||||
|
markdown.WriteString(doc.BasicInfo)
|
||||||
|
markdown.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加请求参数
|
||||||
|
if doc.RequestParams != "" {
|
||||||
|
markdown.WriteString("## 请求参数\n\n")
|
||||||
|
markdown.WriteString(doc.RequestParams)
|
||||||
|
markdown.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加返回字段说明
|
||||||
|
if doc.ResponseFields != "" {
|
||||||
|
markdown.WriteString("## 返回字段说明\n\n")
|
||||||
|
markdown.WriteString(doc.ResponseFields)
|
||||||
|
markdown.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加响应示例
|
||||||
|
if doc.ResponseExample != "" {
|
||||||
|
markdown.WriteString("## 响应示例\n\n")
|
||||||
|
markdown.WriteString(doc.ResponseExample)
|
||||||
|
markdown.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加错误代码
|
||||||
|
if doc.ErrorCodes != "" {
|
||||||
|
markdown.WriteString("## 错误代码\n\n")
|
||||||
|
markdown.WriteString(doc.ErrorCodes)
|
||||||
|
markdown.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文档版本信息
|
||||||
|
markdown.WriteString("---\n\n")
|
||||||
|
markdown.WriteString(fmt.Sprintf("**文档版本**: %s\n\n", doc.Version))
|
||||||
|
if doc.UpdatedAt.Year() > 1900 {
|
||||||
|
markdown.WriteString(fmt.Sprintf("**更新时间**: %s\n", doc.UpdatedAt.Format("2006-01-02 15:04:05")))
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdown.String(), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type CreateDocumentationCommand struct {
|
|||||||
ResponseFields string `json:"response_fields"`
|
ResponseFields string `json:"response_fields"`
|
||||||
ResponseExample string `json:"response_example"`
|
ResponseExample string `json:"response_example"`
|
||||||
ErrorCodes string `json:"error_codes"`
|
ErrorCodes string `json:"error_codes"`
|
||||||
|
PDFFilePath string `json:"pdf_file_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDocumentationCommand 更新文档命令
|
// UpdateDocumentationCommand 更新文档命令
|
||||||
@@ -21,4 +22,5 @@ type UpdateDocumentationCommand struct {
|
|||||||
ResponseFields string `json:"response_fields"`
|
ResponseFields string `json:"response_fields"`
|
||||||
ResponseExample string `json:"response_example"`
|
ResponseExample string `json:"response_example"`
|
||||||
ErrorCodes string `json:"error_codes"`
|
ErrorCodes string `json:"error_codes"`
|
||||||
|
PDFFilePath string `json:"pdf_file_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type DocumentationResponse struct {
|
|||||||
ResponseExample string `json:"response_example"`
|
ResponseExample string `json:"response_example"`
|
||||||
ErrorCodes string `json:"error_codes"`
|
ErrorCodes string `json:"error_codes"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
PDFFilePath string `json:"pdf_file_path,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
@@ -35,6 +36,7 @@ func NewDocumentationResponse(doc *entities.ProductDocumentation) DocumentationR
|
|||||||
ResponseExample: doc.ResponseExample,
|
ResponseExample: doc.ResponseExample,
|
||||||
ErrorCodes: doc.ErrorCodes,
|
ErrorCodes: doc.ErrorCodes,
|
||||||
Version: doc.Version,
|
Version: doc.Version,
|
||||||
|
PDFFilePath: doc.PDFFilePath,
|
||||||
CreatedAt: doc.CreatedAt,
|
CreatedAt: doc.CreatedAt,
|
||||||
UpdatedAt: doc.UpdatedAt,
|
UpdatedAt: doc.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import (
|
|||||||
// ProductApplicationService 产品应用服务接口
|
// ProductApplicationService 产品应用服务接口
|
||||||
type ProductApplicationService interface {
|
type ProductApplicationService interface {
|
||||||
// 产品管理
|
// 产品管理
|
||||||
CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) error
|
CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error)
|
||||||
|
|
||||||
UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error
|
UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error
|
||||||
DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error
|
DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error
|
||||||
|
|
||||||
@@ -46,6 +47,4 @@ type ProductApplicationService interface {
|
|||||||
CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error
|
CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error
|
||||||
UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error
|
UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error
|
||||||
DeleteProductApiConfig(ctx context.Context, configID string) error
|
DeleteProductApiConfig(ctx context.Context, configID string) error
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func NewProductApplicationService(
|
|||||||
|
|
||||||
// CreateProduct 创建产品
|
// CreateProduct 创建产品
|
||||||
// 业务流程<E6B581>?. 构建产品实体 2. 创建产品
|
// 业务流程<E6B581>?. 构建产品实体 2. 创建产品
|
||||||
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) error {
|
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
|
||||||
// 1. 构建产品实体
|
// 1. 构建产品实体
|
||||||
product := &entities.Product{
|
product := &entities.Product{
|
||||||
Name: cmd.Name,
|
Name: cmd.Name,
|
||||||
@@ -71,8 +71,13 @@ func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 创建产品
|
// 2. 创建产品
|
||||||
_, err := s.productManagementService.CreateProduct(ctx, product)
|
createdProduct, err := s.productManagementService.CreateProduct(ctx, product)
|
||||||
return err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换为响应对象
|
||||||
|
return s.convertToProductAdminInfoResponse(createdProduct), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateProduct 更新产品
|
// UpdateProduct 更新产品
|
||||||
@@ -965,7 +970,7 @@ func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} {
|
|||||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||||
"JRZQDBCE": &dto.JRZQDBCEReq{},
|
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
||||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||||
@@ -982,7 +987,7 @@ func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} {
|
|||||||
"YYSY4B21": &dto.YYSY4B21Req{},
|
"YYSY4B21": &dto.YYSY4B21Req{},
|
||||||
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
||||||
"YYSY09CD": &dto.YYSY09CDReq{},
|
"YYSY09CD": &dto.YYSY09CDReq{},
|
||||||
"IVYZ0b03": &dto.IVYZ0b03Req{},
|
"IVYZ0B03": &dto.IVYZ0B03Req{},
|
||||||
"YYSYBE08": &dto.YYSYBE08Req{},
|
"YYSYBE08": &dto.YYSYBE08Req{},
|
||||||
"YYSYD50F": &dto.YYSYD50FReq{},
|
"YYSYD50F": &dto.YYSYD50FReq{},
|
||||||
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type SubscriptionApplicationService interface {
|
|||||||
// 我的订阅(用户专用)
|
// 我的订阅(用户专用)
|
||||||
ListMySubscriptions(ctx context.Context, userID string, query *queries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error)
|
ListMySubscriptions(ctx context.Context, userID string, query *queries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error)
|
||||||
GetMySubscriptionStats(ctx context.Context, userID string) (*responses.SubscriptionStatsResponse, error)
|
GetMySubscriptionStats(ctx context.Context, userID string) (*responses.SubscriptionStatsResponse, error)
|
||||||
|
CancelMySubscription(ctx context.Context, userID string, subscriptionID string) error
|
||||||
|
|
||||||
// 业务查询
|
// 业务查询
|
||||||
GetUserSubscriptions(ctx context.Context, query *queries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error)
|
GetUserSubscriptions(ctx context.Context, query *queries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"tyapi-server/internal/application/product/dto/commands"
|
"tyapi-server/internal/application/product/dto/commands"
|
||||||
appQueries "tyapi-server/internal/application/product/dto/queries"
|
appQueries "tyapi-server/internal/application/product/dto/queries"
|
||||||
"tyapi-server/internal/application/product/dto/responses"
|
"tyapi-server/internal/application/product/dto/responses"
|
||||||
|
domain_api_repo "tyapi-server/internal/domains/api/repositories"
|
||||||
"tyapi-server/internal/domains/product/entities"
|
"tyapi-server/internal/domains/product/entities"
|
||||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||||
product_service "tyapi-server/internal/domains/product/services"
|
product_service "tyapi-server/internal/domains/product/services"
|
||||||
@@ -21,6 +22,7 @@ import (
|
|||||||
type SubscriptionApplicationServiceImpl struct {
|
type SubscriptionApplicationServiceImpl struct {
|
||||||
productSubscriptionService *product_service.ProductSubscriptionService
|
productSubscriptionService *product_service.ProductSubscriptionService
|
||||||
userRepo user_repositories.UserRepository
|
userRepo user_repositories.UserRepository
|
||||||
|
apiCallRepository domain_api_repo.ApiCallRepository
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,11 +30,13 @@ type SubscriptionApplicationServiceImpl struct {
|
|||||||
func NewSubscriptionApplicationService(
|
func NewSubscriptionApplicationService(
|
||||||
productSubscriptionService *product_service.ProductSubscriptionService,
|
productSubscriptionService *product_service.ProductSubscriptionService,
|
||||||
userRepo user_repositories.UserRepository,
|
userRepo user_repositories.UserRepository,
|
||||||
|
apiCallRepository domain_api_repo.ApiCallRepository,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) SubscriptionApplicationService {
|
) SubscriptionApplicationService {
|
||||||
return &SubscriptionApplicationServiceImpl{
|
return &SubscriptionApplicationServiceImpl{
|
||||||
productSubscriptionService: productSubscriptionService,
|
productSubscriptionService: productSubscriptionService,
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
|
apiCallRepository: apiCallRepository,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,17 +266,30 @@ func (s *SubscriptionApplicationServiceImpl) GetProductSubscriptions(ctx context
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetSubscriptionUsage 获取订阅使用情况
|
// GetSubscriptionUsage 获取订阅使用情况
|
||||||
// 业务流程:1. 获取订阅使用情况 2. 构建响应数据
|
// 业务流程:1. 获取订阅信息 2. 根据产品ID和用户ID统计API调用次数 3. 构建响应数据
|
||||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) {
|
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) {
|
||||||
|
// 获取订阅信息
|
||||||
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
|
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据用户ID和产品ID统计API调用次数
|
||||||
|
apiCallCount, err := s.apiCallRepository.CountByUserIdAndProductId(ctx, subscription.UserID, subscription.ProductID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("统计API调用次数失败,使用订阅记录中的值",
|
||||||
|
zap.String("subscription_id", subscriptionID),
|
||||||
|
zap.String("user_id", subscription.UserID),
|
||||||
|
zap.String("product_id", subscription.ProductID),
|
||||||
|
zap.Error(err))
|
||||||
|
// 如果统计失败,使用订阅实体中的APIUsed字段作为备选
|
||||||
|
apiCallCount = subscription.APIUsed
|
||||||
|
}
|
||||||
|
|
||||||
return &responses.SubscriptionUsageResponse{
|
return &responses.SubscriptionUsageResponse{
|
||||||
ID: subscription.ID,
|
ID: subscription.ID,
|
||||||
ProductID: subscription.ProductID,
|
ProductID: subscription.ProductID,
|
||||||
APIUsed: subscription.APIUsed,
|
APIUsed: apiCallCount,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,6 +321,38 @@ func (s *SubscriptionApplicationServiceImpl) GetMySubscriptionStats(ctx context.
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CancelMySubscription 取消我的订阅
|
||||||
|
// 业务流程:1. 验证订阅是否属于当前用户 2. 取消订阅
|
||||||
|
func (s *SubscriptionApplicationServiceImpl) CancelMySubscription(ctx context.Context, userID string, subscriptionID string) error {
|
||||||
|
// 1. 获取订阅信息
|
||||||
|
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取订阅信息失败", zap.String("subscription_id", subscriptionID), zap.Error(err))
|
||||||
|
return fmt.Errorf("订阅不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 验证订阅是否属于当前用户
|
||||||
|
if subscription.UserID != userID {
|
||||||
|
s.logger.Warn("用户尝试取消不属于自己的订阅",
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.String("subscription_id", subscriptionID),
|
||||||
|
zap.String("subscription_user_id", subscription.UserID))
|
||||||
|
return fmt.Errorf("无权取消此订阅")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 取消订阅(软删除)
|
||||||
|
if err := s.productSubscriptionService.CancelSubscription(ctx, subscriptionID); err != nil {
|
||||||
|
s.logger.Error("取消订阅失败", zap.String("subscription_id", subscriptionID), zap.Error(err))
|
||||||
|
return fmt.Errorf("取消订阅失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("用户取消订阅成功",
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.String("subscription_id", subscriptionID))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// convertToSubscriptionInfoResponse 转换为订阅信息响应
|
// convertToSubscriptionInfoResponse 转换为订阅信息响应
|
||||||
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
|
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
|
||||||
// 查询用户信息
|
// 查询用户信息
|
||||||
|
|||||||
@@ -1301,8 +1301,10 @@ func (s *StatisticsApplicationServiceImpl) getUserApiCallsStats(ctx context.Cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取今日调用次数
|
// 获取今日调用次数
|
||||||
today := time.Now().Truncate(24 * time.Hour)
|
loc, _ := time.LoadLocation("Asia/Shanghai") // 东八时区
|
||||||
tomorrow := today.Add(24 * time.Hour)
|
now := time.Now().In(loc)
|
||||||
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) // 当天0点
|
||||||
|
tomorrow := today.AddDate(0, 0, 1) // 次日0点
|
||||||
todayCalls, err := s.getApiCallsCountByDateRange(ctx, userID, today, tomorrow)
|
todayCalls, err := s.getApiCallsCountByDateRange(ctx, userID, today, tomorrow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取今日API调用次数失败", zap.String("user_id", userID), zap.Error(err))
|
s.logger.Error("获取今日API调用次数失败", zap.String("user_id", userID), zap.Error(err))
|
||||||
@@ -1356,8 +1358,10 @@ func (s *StatisticsApplicationServiceImpl) getUserConsumptionStats(ctx context.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取今日消费金额
|
// 获取今日消费金额
|
||||||
today := time.Now().Truncate(24 * time.Hour)
|
loc, _ := time.LoadLocation("Asia/Shanghai") // 东八时区
|
||||||
tomorrow := today.Add(24 * time.Hour)
|
now := time.Now().In(loc)
|
||||||
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) // 当天0点
|
||||||
|
tomorrow := today.AddDate(0, 0, 1) // 次日0点
|
||||||
todayAmount, err := s.getWalletTransactionsByDateRange(ctx, userID, today, tomorrow)
|
todayAmount, err := s.getWalletTransactionsByDateRange(ctx, userID, today, tomorrow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取今日消费金额失败", zap.String("user_id", userID), zap.Error(err))
|
s.logger.Error("获取今日消费金额失败", zap.String("user_id", userID), zap.Error(err))
|
||||||
@@ -1411,8 +1415,10 @@ func (s *StatisticsApplicationServiceImpl) getUserRechargeStats(ctx context.Cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取今日充值金额
|
// 获取今日充值金额
|
||||||
today := time.Now().Truncate(24 * time.Hour)
|
loc, _ := time.LoadLocation("Asia/Shanghai") // 东八时区
|
||||||
tomorrow := today.Add(24 * time.Hour)
|
now := time.Now().In(loc)
|
||||||
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) // 当天0点
|
||||||
|
tomorrow := today.AddDate(0, 0, 1) // 次日0点
|
||||||
todayAmount, err := s.getRechargeRecordsByDateRange(ctx, userID, today, tomorrow)
|
todayAmount, err := s.getRechargeRecordsByDateRange(ctx, userID, today, tomorrow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取今日充值金额失败", zap.String("user_id", userID), zap.Error(err))
|
s.logger.Error("获取今日充值金额失败", zap.String("user_id", userID), zap.Error(err))
|
||||||
@@ -1682,13 +1688,13 @@ func (s *StatisticsApplicationServiceImpl) getCertificationStats(ctx context.Con
|
|||||||
successRate = float64(userStats.CertifiedUsers) / float64(userStats.TotalUsers)
|
successRate = float64(userStats.CertifiedUsers) / float64(userStats.TotalUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据时间范围获取趋势数据
|
// 根据时间范围获取认证趋势数据(基于is_certified字段)
|
||||||
var trendData []map[string]interface{}
|
var trendData []map[string]interface{}
|
||||||
if !startTime.IsZero() && !endTime.IsZero() {
|
if !startTime.IsZero() && !endTime.IsZero() {
|
||||||
if period == "day" {
|
if period == "day" {
|
||||||
trendData, err = s.userRepo.GetSystemDailyUserStats(ctx, startTime, endTime)
|
trendData, err = s.userRepo.GetSystemDailyCertificationStats(ctx, startTime, endTime)
|
||||||
} else if period == "month" {
|
} else if period == "month" {
|
||||||
trendData, err = s.userRepo.GetSystemMonthlyUserStats(ctx, startTime, endTime)
|
trendData, err = s.userRepo.GetSystemMonthlyCertificationStats(ctx, startTime, endTime)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取认证趋势数据失败", zap.Error(err))
|
s.logger.Error("获取认证趋势数据失败", zap.Error(err))
|
||||||
@@ -1698,16 +1704,35 @@ func (s *StatisticsApplicationServiceImpl) getCertificationStats(ctx context.Con
|
|||||||
// 默认获取最近7天的数据
|
// 默认获取最近7天的数据
|
||||||
endDate := time.Now()
|
endDate := time.Now()
|
||||||
startDate := endDate.AddDate(0, 0, -7)
|
startDate := endDate.AddDate(0, 0, -7)
|
||||||
trendData, err = s.userRepo.GetSystemDailyUserStats(ctx, startDate, endDate)
|
trendData, err = s.userRepo.GetSystemDailyCertificationStats(ctx, startDate, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取认证每日趋势失败", zap.Error(err))
|
s.logger.Error("获取认证每日趋势失败", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取今日认证用户数(基于is_certified字段,东八时区)
|
||||||
|
loc, _ := time.LoadLocation("Asia/Shanghai") // 东八时区
|
||||||
|
now := time.Now().In(loc)
|
||||||
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) // 当天0点
|
||||||
|
tomorrow := today.AddDate(0, 0, 1) // 次日0点
|
||||||
|
|
||||||
|
var certifiedToday int64
|
||||||
|
todayCertStats, err := s.userRepo.GetSystemDailyCertificationStats(ctx, today, tomorrow)
|
||||||
|
if err == nil && len(todayCertStats) > 0 {
|
||||||
|
// 累加今日所有认证用户数
|
||||||
|
for _, stat := range todayCertStats {
|
||||||
|
if count, ok := stat["count"].(int64); ok {
|
||||||
|
certifiedToday += count
|
||||||
|
} else if count, ok := stat["count"].(int); ok {
|
||||||
|
certifiedToday += int64(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stats := map[string]interface{}{
|
stats := map[string]interface{}{
|
||||||
"total_certified": userStats.CertifiedUsers,
|
"total_certified": userStats.CertifiedUsers,
|
||||||
"certified_today": userStats.TodayRegistrations, // 今日注册的用户
|
"certified_today": certifiedToday, // 今日认证的用户数(基于is_certified字段)
|
||||||
"success_rate": successRate,
|
"success_rate": successRate,
|
||||||
"daily_trend": trendData,
|
"daily_trend": trendData,
|
||||||
}
|
}
|
||||||
@@ -1723,9 +1748,11 @@ func (s *StatisticsApplicationServiceImpl) getSystemApiCallStats(ctx context.Con
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取今日API调用次数
|
// 获取今日API调用次数(东八时区)
|
||||||
today := time.Now().Truncate(24 * time.Hour)
|
loc, _ := time.LoadLocation("Asia/Shanghai") // 东八时区
|
||||||
tomorrow := today.Add(24 * time.Hour)
|
now := time.Now().In(loc)
|
||||||
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) // 当天0点
|
||||||
|
tomorrow := today.AddDate(0, 0, 1) // 次日0点
|
||||||
todayCalls, err := s.apiCallRepo.GetSystemCallsByDateRange(ctx, today, tomorrow)
|
todayCalls, err := s.apiCallRepo.GetSystemCallsByDateRange(ctx, today, tomorrow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取今日API调用次数失败", zap.Error(err))
|
s.logger.Error("获取今日API调用次数失败", zap.Error(err))
|
||||||
@@ -1780,8 +1807,11 @@ func (s *StatisticsApplicationServiceImpl) getSystemFinanceStats(ctx context.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取今日消费金额
|
// 获取今日消费金额
|
||||||
today := time.Now().Truncate(24 * time.Hour)
|
loc, _ := time.LoadLocation("Asia/Shanghai") // 东八时区
|
||||||
tomorrow := today.Add(24 * time.Hour)
|
now := time.Now().In(loc)
|
||||||
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) // 当天0点
|
||||||
|
tomorrow := today.AddDate(0, 0, 1) // 次日0点
|
||||||
|
|
||||||
todayConsumption, err := s.walletTransactionRepo.GetSystemAmountByDateRange(ctx, today, tomorrow)
|
todayConsumption, err := s.walletTransactionRepo.GetSystemAmountByDateRange(ctx, today, tomorrow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取今日消费金额失败", zap.Error(err))
|
s.logger.Error("获取今日消费金额失败", zap.Error(err))
|
||||||
@@ -2275,6 +2305,10 @@ func (s *StatisticsApplicationServiceImpl) AdminGetApiDomainStatistics(ctx conte
|
|||||||
s.logger.Error("解析开始日期失败", zap.Error(err))
|
s.logger.Error("解析开始日期失败", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// 如果是月统计,将开始日期调整为当月1号00:00:00
|
||||||
|
if period == "month" {
|
||||||
|
startTime = time.Date(startTime.Year(), startTime.Month(), 1, 0, 0, 0, 0, startTime.Location())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if endDate != "" {
|
if endDate != "" {
|
||||||
endTime, err = time.Parse("2006-01-02", endDate)
|
endTime, err = time.Parse("2006-01-02", endDate)
|
||||||
@@ -2282,6 +2316,14 @@ func (s *StatisticsApplicationServiceImpl) AdminGetApiDomainStatistics(ctx conte
|
|||||||
s.logger.Error("解析结束日期失败", zap.Error(err))
|
s.logger.Error("解析结束日期失败", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if period == "month" {
|
||||||
|
// 如果是月统计,将结束日期调整为下个月1号00:00:00
|
||||||
|
// 这样在查询时使用 created_at < endTime 可以包含整个月份的数据(到本月最后一天23:59:59.999)
|
||||||
|
endTime = time.Date(endTime.Year(), endTime.Month()+1, 1, 0, 0, 0, 0, endTime.Location())
|
||||||
|
} else {
|
||||||
|
// 日统计:将结束日期设置为次日00:00:00,这样在查询时使用 created_at < endTime 可以包含当天的所有数据
|
||||||
|
endTime = endTime.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取API调用统计数据
|
// 获取API调用统计数据
|
||||||
@@ -2318,6 +2360,10 @@ func (s *StatisticsApplicationServiceImpl) AdminGetConsumptionDomainStatistics(c
|
|||||||
s.logger.Error("解析开始日期失败", zap.Error(err))
|
s.logger.Error("解析开始日期失败", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// 如果是月统计,将开始日期调整为当月1号00:00:00
|
||||||
|
if period == "month" {
|
||||||
|
startTime = time.Date(startTime.Year(), startTime.Month(), 1, 0, 0, 0, 0, startTime.Location())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if endDate != "" {
|
if endDate != "" {
|
||||||
endTime, err = time.Parse("2006-01-02", endDate)
|
endTime, err = time.Parse("2006-01-02", endDate)
|
||||||
@@ -2325,6 +2371,14 @@ func (s *StatisticsApplicationServiceImpl) AdminGetConsumptionDomainStatistics(c
|
|||||||
s.logger.Error("解析结束日期失败", zap.Error(err))
|
s.logger.Error("解析结束日期失败", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if period == "month" {
|
||||||
|
// 如果是月统计,将结束日期调整为下个月1号00:00:00
|
||||||
|
// 这样在查询时使用 created_at < endTime 可以包含整个月份的数据(到本月最后一天23:59:59.999)
|
||||||
|
endTime = time.Date(endTime.Year(), endTime.Month()+1, 1, 0, 0, 0, 0, endTime.Location())
|
||||||
|
} else {
|
||||||
|
// 日统计:将结束日期设置为次日00:00:00,这样在查询时使用 created_at < endTime 可以包含当天的所有数据
|
||||||
|
endTime = endTime.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取消费统计数据
|
// 获取消费统计数据
|
||||||
@@ -2335,8 +2389,10 @@ func (s *StatisticsApplicationServiceImpl) AdminGetConsumptionDomainStatistics(c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取今日消费金额
|
// 获取今日消费金额
|
||||||
today := time.Now().Truncate(24 * time.Hour)
|
loc, _ := time.LoadLocation("Asia/Shanghai") // 东八时区
|
||||||
tomorrow := today.Add(24 * time.Hour)
|
now := time.Now().In(loc)
|
||||||
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) // 当天0点
|
||||||
|
tomorrow := today.AddDate(0, 0, 1) // 次日0点
|
||||||
todayConsumption, err := s.walletTransactionRepo.GetSystemAmountByDateRange(ctx, today, tomorrow)
|
todayConsumption, err := s.walletTransactionRepo.GetSystemAmountByDateRange(ctx, today, tomorrow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取今日消费金额失败", zap.Error(err))
|
s.logger.Error("获取今日消费金额失败", zap.Error(err))
|
||||||
@@ -2406,6 +2462,10 @@ func (s *StatisticsApplicationServiceImpl) AdminGetRechargeDomainStatistics(ctx
|
|||||||
s.logger.Error("解析开始日期失败", zap.Error(err))
|
s.logger.Error("解析开始日期失败", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// 如果是月统计,将开始日期调整为当月1号00:00:00
|
||||||
|
if period == "month" {
|
||||||
|
startTime = time.Date(startTime.Year(), startTime.Month(), 1, 0, 0, 0, 0, startTime.Location())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if endDate != "" {
|
if endDate != "" {
|
||||||
endTime, err = time.Parse("2006-01-02", endDate)
|
endTime, err = time.Parse("2006-01-02", endDate)
|
||||||
@@ -2413,6 +2473,14 @@ func (s *StatisticsApplicationServiceImpl) AdminGetRechargeDomainStatistics(ctx
|
|||||||
s.logger.Error("解析结束日期失败", zap.Error(err))
|
s.logger.Error("解析结束日期失败", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if period == "month" {
|
||||||
|
// 如果是月统计,将结束日期调整为下个月1号00:00:00
|
||||||
|
// 这样在查询时使用 created_at < endTime 可以包含整个月份的数据(到本月最后一天23:59:59.999)
|
||||||
|
endTime = time.Date(endTime.Year(), endTime.Month()+1, 1, 0, 0, 0, 0, endTime.Location())
|
||||||
|
} else {
|
||||||
|
// 日统计:将结束日期设置为次日00:00:00,这样在查询时使用 created_at < endTime 可以包含当天的所有数据
|
||||||
|
endTime = endTime.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取充值统计数据
|
// 获取充值统计数据
|
||||||
@@ -2423,8 +2491,10 @@ func (s *StatisticsApplicationServiceImpl) AdminGetRechargeDomainStatistics(ctx
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取今日充值金额
|
// 获取今日充值金额
|
||||||
today := time.Now().Truncate(24 * time.Hour)
|
loc, _ := time.LoadLocation("Asia/Shanghai") // 东八时区
|
||||||
tomorrow := today.Add(24 * time.Hour)
|
now := time.Now().In(loc)
|
||||||
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) // 当天0点
|
||||||
|
tomorrow := today.AddDate(0, 0, 1) // 次日0点
|
||||||
todayRecharge, err := s.rechargeRecordRepo.GetSystemAmountByDateRange(ctx, today, tomorrow)
|
todayRecharge, err := s.rechargeRecordRepo.GetSystemAmountByDateRange(ctx, today, tomorrow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取今日充值金额失败", zap.Error(err))
|
s.logger.Error("获取今日充值金额失败", zap.Error(err))
|
||||||
@@ -2716,15 +2786,15 @@ func (s *StatisticsApplicationServiceImpl) AdminGetTodayCertifiedEnterprises(ctx
|
|||||||
}
|
}
|
||||||
|
|
||||||
enterprise := map[string]interface{}{
|
enterprise := map[string]interface{}{
|
||||||
"id": cert.ID,
|
"id": cert.ID,
|
||||||
"user_id": cert.UserID,
|
"user_id": cert.UserID,
|
||||||
"username": user.Username,
|
"username": user.Username,
|
||||||
"enterprise_name": enterpriseInfo.CompanyName,
|
"enterprise_name": enterpriseInfo.CompanyName,
|
||||||
"legal_person_name": enterpriseInfo.LegalPersonName,
|
"legal_person_name": enterpriseInfo.LegalPersonName,
|
||||||
"legal_person_phone": enterpriseInfo.LegalPersonPhone,
|
"legal_person_phone": enterpriseInfo.LegalPersonPhone,
|
||||||
"unified_social_code": enterpriseInfo.UnifiedSocialCode,
|
"unified_social_code": enterpriseInfo.UnifiedSocialCode,
|
||||||
"enterprise_address": enterpriseInfo.EnterpriseAddress,
|
"enterprise_address": enterpriseInfo.EnterpriseAddress,
|
||||||
"certified_at": cert.CompletedAt.Format(time.RFC3339),
|
"certified_at": cert.CompletedAt.Format(time.RFC3339),
|
||||||
}
|
}
|
||||||
enterprises = append(enterprises, enterprise)
|
enterprises = append(enterprises, enterprise)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ type Config struct {
|
|||||||
Zhicha ZhichaConfig `mapstructure:"zhicha"`
|
Zhicha ZhichaConfig `mapstructure:"zhicha"`
|
||||||
Muzi MuziConfig `mapstructure:"muzi"`
|
Muzi MuziConfig `mapstructure:"muzi"`
|
||||||
AliPay AliPayConfig `mapstructure:"alipay"`
|
AliPay AliPayConfig `mapstructure:"alipay"`
|
||||||
|
Wxpay WxpayConfig `mapstructure:"wxpay"`
|
||||||
|
WechatMini WechatMiniConfig `mapstructure:"wechat_mini"`
|
||||||
|
WechatH5 WechatH5Config `mapstructure:"wechat_h5"`
|
||||||
Yushan YushanConfig `mapstructure:"yushan"`
|
Yushan YushanConfig `mapstructure:"yushan"`
|
||||||
TianYanCha TianYanChaConfig `mapstructure:"tianyancha"`
|
TianYanCha TianYanChaConfig `mapstructure:"tianyancha"`
|
||||||
Alicloud AlicloudConfig `mapstructure:"alicloud"`
|
Alicloud AlicloudConfig `mapstructure:"alicloud"`
|
||||||
@@ -429,6 +432,29 @@ type AliPayConfig struct {
|
|||||||
ReturnURL string `mapstructure:"return_url"`
|
ReturnURL string `mapstructure:"return_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WxpayConfig 微信支付配置
|
||||||
|
type WxpayConfig struct {
|
||||||
|
AppID string `mapstructure:"app_id"`
|
||||||
|
MchID string `mapstructure:"mch_id"`
|
||||||
|
MchCertificateSerialNumber string `mapstructure:"mch_certificate_serial_number"`
|
||||||
|
MchApiv3Key string `mapstructure:"mch_apiv3_key"`
|
||||||
|
MchPrivateKeyPath string `mapstructure:"mch_private_key_path"`
|
||||||
|
MchPublicKeyID string `mapstructure:"mch_public_key_id"`
|
||||||
|
MchPublicKeyPath string `mapstructure:"mch_public_key_path"`
|
||||||
|
NotifyUrl string `mapstructure:"notify_url"`
|
||||||
|
RefundNotifyUrl string `mapstructure:"refund_notify_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WechatMiniConfig 微信小程序配置
|
||||||
|
type WechatMiniConfig struct {
|
||||||
|
AppID string `mapstructure:"app_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WechatH5Config 微信H5配置
|
||||||
|
type WechatH5Config struct {
|
||||||
|
AppID string `mapstructure:"app_id"`
|
||||||
|
}
|
||||||
|
|
||||||
// YushanConfig 羽山配置
|
// YushanConfig 羽山配置
|
||||||
type YushanConfig struct {
|
type YushanConfig struct {
|
||||||
URL string `mapstructure:"url"`
|
URL string `mapstructure:"url"`
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package container
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
@@ -66,6 +68,7 @@ import (
|
|||||||
"tyapi-server/internal/shared/middleware"
|
"tyapi-server/internal/shared/middleware"
|
||||||
sharedOCR "tyapi-server/internal/shared/ocr"
|
sharedOCR "tyapi-server/internal/shared/ocr"
|
||||||
"tyapi-server/internal/shared/payment"
|
"tyapi-server/internal/shared/payment"
|
||||||
|
"tyapi-server/internal/shared/pdf"
|
||||||
"tyapi-server/internal/shared/resilience"
|
"tyapi-server/internal/shared/resilience"
|
||||||
"tyapi-server/internal/shared/saga"
|
"tyapi-server/internal/shared/saga"
|
||||||
"tyapi-server/internal/shared/tracing"
|
"tyapi-server/internal/shared/tracing"
|
||||||
@@ -304,6 +307,16 @@ func NewContainer() *Container {
|
|||||||
}
|
}
|
||||||
return payment.NewAliPayService(config)
|
return payment.NewAliPayService(config)
|
||||||
},
|
},
|
||||||
|
// 微信支付服务
|
||||||
|
func(cfg *config.Config, logger *zap.Logger) *payment.WechatPayService {
|
||||||
|
// 根据配置选择初始化方式,默认使用平台证书方式
|
||||||
|
initType := payment.InitTypePlatformCert
|
||||||
|
// 如果配置了公钥ID,使用公钥方式
|
||||||
|
if cfg.Wxpay.MchPublicKeyID != "" {
|
||||||
|
initType = payment.InitTypeWxPayPubKey
|
||||||
|
}
|
||||||
|
return payment.NewWechatPayService(*cfg, initType, logger)
|
||||||
|
},
|
||||||
// 导出管理器
|
// 导出管理器
|
||||||
func(logger *zap.Logger) *export.ExportManager {
|
func(logger *zap.Logger) *export.ExportManager {
|
||||||
return export.NewExportManager(logger)
|
return export.NewExportManager(logger)
|
||||||
@@ -509,6 +522,11 @@ func NewContainer() *Container {
|
|||||||
finance_repo.NewGormAlipayOrderRepository,
|
finance_repo.NewGormAlipayOrderRepository,
|
||||||
fx.As(new(domain_finance_repo.AlipayOrderRepository)),
|
fx.As(new(domain_finance_repo.AlipayOrderRepository)),
|
||||||
),
|
),
|
||||||
|
// 微信订单仓储
|
||||||
|
fx.Annotate(
|
||||||
|
finance_repo.NewGormWechatOrderRepository,
|
||||||
|
fx.As(new(domain_finance_repo.WechatOrderRepository)),
|
||||||
|
),
|
||||||
// 发票申请仓储
|
// 发票申请仓储
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
finance_repo.NewGormInvoiceApplicationRepository,
|
finance_repo.NewGormInvoiceApplicationRepository,
|
||||||
@@ -571,6 +589,11 @@ func NewContainer() *Container {
|
|||||||
article_repo.NewGormScheduledTaskRepository,
|
article_repo.NewGormScheduledTaskRepository,
|
||||||
fx.As(new(domain_article_repo.ScheduledTaskRepository)),
|
fx.As(new(domain_article_repo.ScheduledTaskRepository)),
|
||||||
),
|
),
|
||||||
|
// 公告仓储 - 同时注册具体类型和接口类型
|
||||||
|
fx.Annotate(
|
||||||
|
article_repo.NewGormAnnouncementRepository,
|
||||||
|
fx.As(new(domain_article_repo.AnnouncementRepository)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// API域仓储层
|
// API域仓储层
|
||||||
@@ -675,6 +698,8 @@ func NewContainer() *Container {
|
|||||||
certification_service.NewEnterpriseInfoSubmitRecordService,
|
certification_service.NewEnterpriseInfoSubmitRecordService,
|
||||||
// 文章领域服务
|
// 文章领域服务
|
||||||
article_service.NewArticleService,
|
article_service.NewArticleService,
|
||||||
|
// 公告领域服务
|
||||||
|
article_service.NewAnnouncementService,
|
||||||
// 统计领域服务
|
// 统计领域服务
|
||||||
statistics_service.NewStatisticsAggregateService,
|
statistics_service.NewStatisticsAggregateService,
|
||||||
statistics_service.NewStatisticsCalculationService,
|
statistics_service.NewStatisticsCalculationService,
|
||||||
@@ -775,6 +800,7 @@ func NewContainer() *Container {
|
|||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
articleApplicationService article.ArticleApplicationService,
|
articleApplicationService article.ArticleApplicationService,
|
||||||
|
announcementApplicationService article.AnnouncementApplicationService,
|
||||||
apiApplicationService api_app.ApiApplicationService,
|
apiApplicationService api_app.ApiApplicationService,
|
||||||
walletService finance_services.WalletAggregateService,
|
walletService finance_services.WalletAggregateService,
|
||||||
subscriptionService *product_services.ProductSubscriptionService,
|
subscriptionService *product_services.ProductSubscriptionService,
|
||||||
@@ -785,6 +811,7 @@ func NewContainer() *Container {
|
|||||||
redisAddr,
|
redisAddr,
|
||||||
logger,
|
logger,
|
||||||
articleApplicationService,
|
articleApplicationService,
|
||||||
|
announcementApplicationService,
|
||||||
apiApplicationService,
|
apiApplicationService,
|
||||||
walletService,
|
walletService,
|
||||||
subscriptionService,
|
subscriptionService,
|
||||||
@@ -843,10 +870,13 @@ func NewContainer() *Container {
|
|||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
func(
|
func(
|
||||||
aliPayClient *payment.AliPayService,
|
aliPayClient *payment.AliPayService,
|
||||||
|
wechatPayService *payment.WechatPayService,
|
||||||
walletService finance_services.WalletAggregateService,
|
walletService finance_services.WalletAggregateService,
|
||||||
rechargeRecordService finance_services.RechargeRecordService,
|
rechargeRecordService finance_services.RechargeRecordService,
|
||||||
walletTransactionRepo domain_finance_repo.WalletTransactionRepository,
|
walletTransactionRepo domain_finance_repo.WalletTransactionRepository,
|
||||||
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
||||||
|
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
||||||
|
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
||||||
userRepo domain_user_repo.UserRepository,
|
userRepo domain_user_repo.UserRepository,
|
||||||
txManager *shared_database.TransactionManager,
|
txManager *shared_database.TransactionManager,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
@@ -855,10 +885,13 @@ func NewContainer() *Container {
|
|||||||
) finance.FinanceApplicationService {
|
) finance.FinanceApplicationService {
|
||||||
return finance.NewFinanceApplicationService(
|
return finance.NewFinanceApplicationService(
|
||||||
aliPayClient,
|
aliPayClient,
|
||||||
|
wechatPayService,
|
||||||
walletService,
|
walletService,
|
||||||
rechargeRecordService,
|
rechargeRecordService,
|
||||||
walletTransactionRepo,
|
walletTransactionRepo,
|
||||||
alipayOrderRepo,
|
alipayOrderRepo,
|
||||||
|
wechatOrderRepo,
|
||||||
|
rechargeRecordRepo,
|
||||||
userRepo,
|
userRepo,
|
||||||
txManager,
|
txManager,
|
||||||
logger,
|
logger,
|
||||||
@@ -941,6 +974,23 @@ func NewContainer() *Container {
|
|||||||
},
|
},
|
||||||
fx.As(new(article.ArticleApplicationService)),
|
fx.As(new(article.ArticleApplicationService)),
|
||||||
),
|
),
|
||||||
|
// 公告应用服务 - 绑定到接口
|
||||||
|
fx.Annotate(
|
||||||
|
func(
|
||||||
|
announcementRepo domain_article_repo.AnnouncementRepository,
|
||||||
|
announcementService *article_service.AnnouncementService,
|
||||||
|
taskManager task_interfaces.TaskManager,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) article.AnnouncementApplicationService {
|
||||||
|
return article.NewAnnouncementApplicationService(
|
||||||
|
announcementRepo,
|
||||||
|
announcementService,
|
||||||
|
taskManager,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
fx.As(new(article.AnnouncementApplicationService)),
|
||||||
|
),
|
||||||
// 统计应用服务 - 绑定到接口
|
// 统计应用服务 - 绑定到接口
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
func(
|
func(
|
||||||
@@ -980,6 +1030,62 @@ func NewContainer() *Container {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// PDF查找服务
|
||||||
|
fx.Provide(
|
||||||
|
func(logger *zap.Logger) (*pdf.PDFFinder, error) {
|
||||||
|
docDir, err := pdf.GetDocumentationDir()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("未找到接口文档文件夹,PDF自动查找功能将不可用", zap.Error(err))
|
||||||
|
return nil, nil // 返回nil,handler中会检查
|
||||||
|
}
|
||||||
|
logger.Info("PDF查找服务已初始化", zap.String("documentation_dir", docDir))
|
||||||
|
return pdf.NewPDFFinder(docDir, logger), nil
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// PDF生成器
|
||||||
|
fx.Provide(
|
||||||
|
func(logger *zap.Logger) *pdf.PDFGenerator {
|
||||||
|
return pdf.NewPDFGenerator(logger)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// PDF缓存管理器
|
||||||
|
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
|
||||||
|
|
||||||
|
// 可以通过环境变量覆盖
|
||||||
|
if envCacheDir := os.Getenv("PDF_CACHE_DIR"); envCacheDir != "" {
|
||||||
|
cacheDir = envCacheDir
|
||||||
|
}
|
||||||
|
if envTTL := os.Getenv("PDF_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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheManager, err := pdf.NewPDFCacheManager(logger, cacheDir, ttl, maxSize)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("PDF缓存管理器初始化失败,将禁用缓存功能", zap.Error(err))
|
||||||
|
return nil, nil // 返回nil,handler中会检查
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("PDF缓存管理器已初始化",
|
||||||
|
zap.String("cache_dir", cacheDir),
|
||||||
|
zap.Duration("ttl", ttl),
|
||||||
|
zap.Int64("max_size", maxSize),
|
||||||
|
)
|
||||||
|
|
||||||
|
return cacheManager, nil
|
||||||
|
},
|
||||||
|
),
|
||||||
// HTTP处理器
|
// HTTP处理器
|
||||||
fx.Provide(
|
fx.Provide(
|
||||||
// 用户HTTP处理器
|
// 用户HTTP处理器
|
||||||
@@ -1005,6 +1111,15 @@ func NewContainer() *Container {
|
|||||||
) *handlers.ArticleHandler {
|
) *handlers.ArticleHandler {
|
||||||
return handlers.NewArticleHandler(appService, responseBuilder, validator, logger)
|
return handlers.NewArticleHandler(appService, responseBuilder, validator, logger)
|
||||||
},
|
},
|
||||||
|
// 公告HTTP处理器
|
||||||
|
func(
|
||||||
|
appService article.AnnouncementApplicationService,
|
||||||
|
responseBuilder interfaces.ResponseBuilder,
|
||||||
|
validator interfaces.RequestValidator,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) *handlers.AnnouncementHandler {
|
||||||
|
return handlers.NewAnnouncementHandler(appService, responseBuilder, validator, logger)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
// 路由注册
|
// 路由注册
|
||||||
@@ -1021,6 +1136,8 @@ func NewContainer() *Container {
|
|||||||
routes.NewProductAdminRoutes,
|
routes.NewProductAdminRoutes,
|
||||||
// 文章路由
|
// 文章路由
|
||||||
routes.NewArticleRoutes,
|
routes.NewArticleRoutes,
|
||||||
|
// 公告路由
|
||||||
|
routes.NewAnnouncementRoutes,
|
||||||
// API路由
|
// API路由
|
||||||
routes.NewApiRoutes,
|
routes.NewApiRoutes,
|
||||||
// 统计路由
|
// 统计路由
|
||||||
@@ -1132,6 +1249,7 @@ func RegisterRoutes(
|
|||||||
productRoutes *routes.ProductRoutes,
|
productRoutes *routes.ProductRoutes,
|
||||||
productAdminRoutes *routes.ProductAdminRoutes,
|
productAdminRoutes *routes.ProductAdminRoutes,
|
||||||
articleRoutes *routes.ArticleRoutes,
|
articleRoutes *routes.ArticleRoutes,
|
||||||
|
announcementRoutes *routes.AnnouncementRoutes,
|
||||||
apiRoutes *routes.ApiRoutes,
|
apiRoutes *routes.ApiRoutes,
|
||||||
statisticsRoutes *routes.StatisticsRoutes,
|
statisticsRoutes *routes.StatisticsRoutes,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
@@ -1149,6 +1267,7 @@ func RegisterRoutes(
|
|||||||
productRoutes.Register(router)
|
productRoutes.Register(router)
|
||||||
productAdminRoutes.Register(router)
|
productAdminRoutes.Register(router)
|
||||||
articleRoutes.Register(router)
|
articleRoutes.Register(router)
|
||||||
|
announcementRoutes.Register(router)
|
||||||
statisticsRoutes.Register(router)
|
statisticsRoutes.Register(router)
|
||||||
|
|
||||||
// 打印注册的路由信息
|
// 打印注册的路由信息
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ type JRZQ8203Req struct {
|
|||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
}
|
}
|
||||||
type JRZQDBCEReq struct {
|
type JRZQDCBEReq struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||||
@@ -133,6 +133,13 @@ type QYGL23T7Req struct {
|
|||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QYGL5CMPReq struct {
|
||||||
|
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||||
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
|
LegalPerson string `json:"legal_person" 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"`
|
||||||
|
}
|
||||||
type YYSY4B37Req struct {
|
type YYSY4B37Req struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
}
|
}
|
||||||
@@ -151,7 +158,7 @@ type YYSY09CDReq struct {
|
|||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
}
|
}
|
||||||
type IVYZ0b03Req struct {
|
type IVYZ0B03Req struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
}
|
}
|
||||||
@@ -195,6 +202,18 @@ type IVYZGZ08Req struct {
|
|||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IVYZ2B2TReq struct {
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
QueryReasonId int64 `json:"query_reason_id" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IVYZ5A9OReq struct {
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type FLXG8A3FReq struct {
|
type FLXG8A3FReq struct {
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
@@ -299,12 +318,35 @@ type IVYZ3A7FReq struct {
|
|||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IVYZ9K2LReq 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 IVYZP2Q6Req struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JRZQ1W4XReq 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"`
|
||||||
|
}
|
||||||
|
|
||||||
type IVYZ9D2EReq struct {
|
type IVYZ9D2EReq struct {
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
UseScenario string `json:"use_scenario" validate:"required,oneof=1 2 3 4 99"`
|
UseScenario string `json:"use_scenario" validate:"required,oneof=1 2 3 4 99"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IVYZ2C1PReq struct {
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
// DWBG7F3AReq 行为数据查询请求参数
|
// DWBG7F3AReq 行为数据查询请求参数
|
||||||
type DWBG7F3AReq struct {
|
type DWBG7F3AReq struct {
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
@@ -315,26 +357,26 @@ type DWBG7F3AReq struct {
|
|||||||
// 新增的QYGL处理器DTO
|
// 新增的QYGL处理器DTO
|
||||||
type QYGL5A3CReq struct {
|
type QYGL5A3CReq struct {
|
||||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
PageSize int `json:"page_size" validate:"omitempty,min=1,max=100"`
|
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||||
PageNum int `json:"page_num" validate:"omitempty,min=1"`
|
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type QYGL8B4DReq struct {
|
type QYGL8B4DReq struct {
|
||||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
PageSize int `json:"page_size" validate:"omitempty,min=1,max=100"`
|
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||||
PageNum int `json:"page_num" validate:"omitempty,min=1"`
|
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type QYGL9E2FReq struct {
|
type QYGL9E2FReq struct {
|
||||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
PageSize int `json:"page_size" validate:"omitempty,min=1,max=100"`
|
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||||
PageNum int `json:"page_num" validate:"omitempty,min=1"`
|
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type QYGL7C1AReq struct {
|
type QYGL7C1AReq struct {
|
||||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
PageSize int `json:"page_size" validate:"omitempty,min=1,max=100"`
|
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||||
PageNum int `json:"page_num" validate:"omitempty,min=1"`
|
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type QYGL3F8EReq struct {
|
type QYGL3F8EReq struct {
|
||||||
@@ -348,6 +390,15 @@ type YYSY4F2EReq struct {
|
|||||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type YYSY9F1BReq struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
|
}
|
||||||
|
type YYSY6F2BReq struct {
|
||||||
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
|
}
|
||||||
|
|
||||||
type YYSY8B1CReq struct {
|
type YYSY8B1CReq struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
}
|
}
|
||||||
@@ -375,6 +426,31 @@ type FLXG9C1DReq struct {
|
|||||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 法院被执行人限高版
|
||||||
|
type FLXG3A9BReq 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"`
|
||||||
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 法院被执行人高级版
|
||||||
|
type FLXGK5D2Req 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"`
|
||||||
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 综合多头
|
||||||
|
|
||||||
|
type JRZQ8F7CReq 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"`
|
||||||
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
|
}
|
||||||
|
|
||||||
type FLXG2E8FReq struct {
|
type FLXG2E8FReq struct {
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
@@ -388,7 +464,27 @@ type JRZQ3C7BReq struct {
|
|||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
}
|
}
|
||||||
|
type JRZQ3C9RReq 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"`
|
||||||
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JRZQ3P01Req struct {
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JRZQ3AG6Req JRZQ3AG6 轻松查公积API处理方法
|
||||||
|
type JRZQ3AG6Req 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"`
|
||||||
|
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||||
|
AuthorizationURL string `json:"authorization_url" validate:"required,authorization_url"`
|
||||||
|
}
|
||||||
type JRZQ8A2DReq struct {
|
type JRZQ8A2DReq struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
@@ -398,9 +494,32 @@ type JRZQ8A2DReq struct {
|
|||||||
|
|
||||||
// YYSY8F3AReq 行为数据查询请求参数
|
// YYSY8F3AReq 行为数据查询请求参数
|
||||||
type YYSY8F3AReq struct {
|
type YYSY8F3AReq struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
IDCard string `json:"cardNo" validate:"required,validIDCard"`
|
||||||
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
|
CardId string `json:"cardId" validate:"required,validIDCard"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 銀行卡黑名單
|
||||||
|
type JRZQ0B6YReq struct {
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
|
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 银行卡鉴权
|
||||||
|
type JRZQ9A1WReq struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
||||||
|
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业管理董监高司法综合信息核验
|
||||||
|
type QYGL6S1BReq struct {
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JRZQ5E9FReq struct {
|
type JRZQ5E9FReq struct {
|
||||||
@@ -454,20 +573,45 @@ type QCXG9P1CReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type QCXG8A3DReq struct {
|
type QCXG8A3DReq struct {
|
||||||
PlateNo string `json:"plate_no" validate:"required"`
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
PlateType string `json:"plate_type" validate:"omitempty,oneof=01 02"`
|
PlateType string `json:"plate_type" validate:"omitempty,oneof=01 02"`
|
||||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type QCXG6B4EReq struct {
|
type QCXG6B4EReq struct {
|
||||||
VINCode string `json:"vin_code" validate:"required"`
|
VINCode string `json:"vin_code" validate:"required"`
|
||||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type QYGL2B5CReq struct {
|
type QYGL2B5CReq struct {
|
||||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全国企业借贷意向验证查询_V1
|
||||||
|
type QYGL9T1QReq struct {
|
||||||
|
OwnerType string `json:"owner_type" validate:"required,oneof=1 2 3 4 5"`
|
||||||
|
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"`
|
||||||
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
|
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全国企业各类工商风险统计数量查询
|
||||||
|
type QYGL5A9TReq struct {
|
||||||
|
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||||
|
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 失信被执行企业或个人查询
|
||||||
|
type QYGL2S0WReq struct {
|
||||||
|
Type string `json:"type" validate:"required,oneof=per ent"`
|
||||||
|
Name string `json:"name" validate:"omitempty,min=1,validName"`
|
||||||
|
EntName string `json:"ent_name" validate:"omitempty,min=1,validName"`
|
||||||
|
IDCard string `json:"id_card" validate:"omitempty,validIDCard"`
|
||||||
|
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JRZQ2F8AReq struct {
|
type JRZQ2F8AReq struct {
|
||||||
@@ -538,9 +682,7 @@ type FLXG7E8FReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type QYGL5F6AReq struct {
|
type QYGL5F6AReq struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
|
||||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type IVYZ6G7HReq struct {
|
type IVYZ6G7HReq struct {
|
||||||
@@ -554,6 +696,11 @@ type IVYZ8I9JReq struct {
|
|||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IVYZ6M8PReq struct {
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
}
|
||||||
|
|
||||||
type YYSY9E4AReq struct {
|
type YYSY9E4AReq struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package entities
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"database/sql/driver"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -18,14 +20,86 @@ const (
|
|||||||
ApiUserStatusFrozen = "frozen"
|
ApiUserStatusFrozen = "frozen"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WhiteListItem 白名单项,包含IP地址、添加时间和备注
|
||||||
|
type WhiteListItem struct {
|
||||||
|
IPAddress string `json:"ip_address"` // IP地址
|
||||||
|
AddedAt time.Time `json:"added_at"` // 添加时间
|
||||||
|
Remark string `json:"remark"` // 备注
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhiteList 白名单类型,支持向后兼容(旧的字符串数组格式)
|
||||||
|
type WhiteList []WhiteListItem
|
||||||
|
|
||||||
|
// Value 实现 driver.Valuer 接口,用于数据库写入
|
||||||
|
func (w WhiteList) Value() (driver.Value, error) {
|
||||||
|
if w == nil {
|
||||||
|
return "[]", nil
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(w)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan 实现 sql.Scanner 接口,用于数据库读取(支持向后兼容)
|
||||||
|
func (w *WhiteList) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
*w = WhiteList{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes []byte
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
bytes = v
|
||||||
|
case string:
|
||||||
|
bytes = []byte(v)
|
||||||
|
default:
|
||||||
|
return errors.New("无法扫描 WhiteList 类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bytes) == 0 || string(bytes) == "[]" || string(bytes) == "null" {
|
||||||
|
*w = WhiteList{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首先尝试解析为新格式(结构体数组)
|
||||||
|
var items []WhiteListItem
|
||||||
|
if err := json.Unmarshal(bytes, &items); err == nil {
|
||||||
|
// 成功解析为新格式
|
||||||
|
*w = WhiteList(items)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果失败,尝试解析为旧格式(字符串数组)
|
||||||
|
var oldFormat []string
|
||||||
|
if err := json.Unmarshal(bytes, &oldFormat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将旧格式转换为新格式
|
||||||
|
now := time.Now()
|
||||||
|
items = make([]WhiteListItem, 0, len(oldFormat))
|
||||||
|
for _, ip := range oldFormat {
|
||||||
|
items = append(items, WhiteListItem{
|
||||||
|
IPAddress: ip,
|
||||||
|
AddedAt: now, // 使用当前时间作为添加时间(因为旧数据没有时间信息)
|
||||||
|
Remark: "", // 旧数据没有备注信息
|
||||||
|
})
|
||||||
|
}
|
||||||
|
*w = WhiteList(items)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ApiUser API用户(聚合根)
|
// ApiUser API用户(聚合根)
|
||||||
type ApiUser struct {
|
type ApiUser struct {
|
||||||
ID string `gorm:"primaryKey;type:varchar(64)" json:"id"`
|
ID string `gorm:"primaryKey;type:varchar(64)" json:"id"`
|
||||||
UserId string `gorm:"type:varchar(36);not null;uniqueIndex" json:"user_id"`
|
UserId string `gorm:"type:varchar(36);not null;uniqueIndex" json:"user_id"`
|
||||||
AccessId string `gorm:"type:varchar(64);not null;uniqueIndex" json:"access_id"`
|
AccessId string `gorm:"type:varchar(64);not null;uniqueIndex" json:"access_id"`
|
||||||
SecretKey string `gorm:"type:varchar(128);not null" json:"secret_key"`
|
SecretKey string `gorm:"type:varchar(128);not null" json:"secret_key"`
|
||||||
Status string `gorm:"type:varchar(20);not null;default:'normal'" json:"status"`
|
Status string `gorm:"type:varchar(20);not null;default:'normal'" json:"status"`
|
||||||
WhiteList []string `gorm:"type:json;serializer:json;default:'[]'" json:"white_list"` // 支持多个白名单
|
WhiteList WhiteList `gorm:"type:json;default:'[]'" json:"white_list"` // 支持多个白名单,包含IP和添加时间,支持向后兼容
|
||||||
|
|
||||||
// 余额预警配置
|
// 余额预警配置
|
||||||
BalanceAlertEnabled bool `gorm:"default:true" json:"balance_alert_enabled" comment:"是否启用余额预警"`
|
BalanceAlertEnabled bool `gorm:"default:true" json:"balance_alert_enabled" comment:"是否启用余额预警"`
|
||||||
@@ -41,7 +115,7 @@ type ApiUser struct {
|
|||||||
// IsWhiteListed 校验IP/域名是否在白名单
|
// IsWhiteListed 校验IP/域名是否在白名单
|
||||||
func (u *ApiUser) IsWhiteListed(target string) bool {
|
func (u *ApiUser) IsWhiteListed(target string) bool {
|
||||||
for _, w := range u.WhiteList {
|
for _, w := range u.WhiteList {
|
||||||
if w == target {
|
if w.IPAddress == target {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +151,7 @@ func NewApiUser(userId string, defaultAlertEnabled bool, defaultAlertThreshold f
|
|||||||
AccessId: accessId,
|
AccessId: accessId,
|
||||||
SecretKey: secretKey,
|
SecretKey: secretKey,
|
||||||
Status: ApiUserStatusNormal,
|
Status: ApiUserStatusNormal,
|
||||||
WhiteList: []string{},
|
WhiteList: WhiteList{},
|
||||||
BalanceAlertEnabled: defaultAlertEnabled,
|
BalanceAlertEnabled: defaultAlertEnabled,
|
||||||
BalanceAlertThreshold: defaultAlertThreshold,
|
BalanceAlertThreshold: defaultAlertThreshold,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -90,12 +164,12 @@ func (u *ApiUser) Freeze() {
|
|||||||
func (u *ApiUser) Unfreeze() {
|
func (u *ApiUser) Unfreeze() {
|
||||||
u.Status = ApiUserStatusNormal
|
u.Status = ApiUserStatusNormal
|
||||||
}
|
}
|
||||||
func (u *ApiUser) UpdateWhiteList(list []string) {
|
func (u *ApiUser) UpdateWhiteList(list []WhiteListItem) {
|
||||||
u.WhiteList = list
|
u.WhiteList = WhiteList(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddToWhiteList 新增白名单项(防御性校验)
|
// AddToWhiteList 新增白名单项(防御性校验)
|
||||||
func (u *ApiUser) AddToWhiteList(entry string) error {
|
func (u *ApiUser) AddToWhiteList(entry string, remark string) error {
|
||||||
if len(u.WhiteList) >= 10 {
|
if len(u.WhiteList) >= 10 {
|
||||||
return errors.New("白名单最多只能有10个")
|
return errors.New("白名单最多只能有10个")
|
||||||
}
|
}
|
||||||
@@ -103,27 +177,31 @@ func (u *ApiUser) AddToWhiteList(entry string) error {
|
|||||||
return errors.New("非法IP")
|
return errors.New("非法IP")
|
||||||
}
|
}
|
||||||
for _, w := range u.WhiteList {
|
for _, w := range u.WhiteList {
|
||||||
if w == entry {
|
if w.IPAddress == entry {
|
||||||
return errors.New("白名单已存在")
|
return errors.New("白名单已存在")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u.WhiteList = append(u.WhiteList, entry)
|
u.WhiteList = append(u.WhiteList, WhiteListItem{
|
||||||
|
IPAddress: entry,
|
||||||
|
AddedAt: time.Now(),
|
||||||
|
Remark: remark,
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeUpdate GORM钩子:更新前确保WhiteList不为nil
|
// BeforeUpdate GORM钩子:更新前确保WhiteList不为nil
|
||||||
func (u *ApiUser) BeforeUpdate(tx *gorm.DB) error {
|
func (u *ApiUser) BeforeUpdate(tx *gorm.DB) error {
|
||||||
if u.WhiteList == nil {
|
if u.WhiteList == nil {
|
||||||
u.WhiteList = []string{}
|
u.WhiteList = WhiteList{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveFromWhiteList 删除白名单项
|
// RemoveFromWhiteList 删除白名单项
|
||||||
func (u *ApiUser) RemoveFromWhiteList(entry string) error {
|
func (u *ApiUser) RemoveFromWhiteList(entry string) error {
|
||||||
newList := make([]string, 0, len(u.WhiteList))
|
newList := make([]WhiteListItem, 0, len(u.WhiteList))
|
||||||
for _, w := range u.WhiteList {
|
for _, w := range u.WhiteList {
|
||||||
if w != entry {
|
if w.IPAddress != entry {
|
||||||
newList = append(newList, w)
|
newList = append(newList, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,9 +294,9 @@ func (u *ApiUser) Validate() error {
|
|||||||
if len(u.WhiteList) > 10 {
|
if len(u.WhiteList) > 10 {
|
||||||
return errors.New("白名单最多只能有10个")
|
return errors.New("白名单最多只能有10个")
|
||||||
}
|
}
|
||||||
for _, ip := range u.WhiteList {
|
for _, item := range u.WhiteList {
|
||||||
if net.ParseIP(ip) == nil {
|
if net.ParseIP(item.IPAddress) == nil {
|
||||||
return errors.New("白名单项必须为合法IP地址: " + ip)
|
return errors.New("白名单项必须为合法IP地址: " + item.IPAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -259,7 +337,26 @@ func (c *ApiUser) BeforeCreate(tx *gorm.DB) error {
|
|||||||
c.ID = uuid.New().String()
|
c.ID = uuid.New().String()
|
||||||
}
|
}
|
||||||
if c.WhiteList == nil {
|
if c.WhiteList == nil {
|
||||||
c.WhiteList = []string{}
|
c.WhiteList = WhiteList{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AfterFind GORM钩子:查询后处理数据,确保AddedAt不为零值
|
||||||
|
func (u *ApiUser) AfterFind(tx *gorm.DB) error {
|
||||||
|
// 如果 WhiteList 为空,初始化为空数组
|
||||||
|
if u.WhiteList == nil {
|
||||||
|
u.WhiteList = WhiteList{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保所有项的AddedAt不为零值(处理可能从旧数据迁移的情况)
|
||||||
|
now := time.Now()
|
||||||
|
for i := range u.WhiteList {
|
||||||
|
if u.WhiteList[i].AddedAt.IsZero() {
|
||||||
|
u.WhiteList[i].AddedAt = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ type ApiCallRepository interface {
|
|||||||
// 新增:统计用户API调用次数
|
// 新增:统计用户API调用次数
|
||||||
CountByUserId(ctx context.Context, userId string) (int64, error)
|
CountByUserId(ctx context.Context, userId string) (int64, error)
|
||||||
|
|
||||||
|
// 新增:根据用户ID和产品ID统计API调用次数
|
||||||
|
CountByUserIdAndProductId(ctx context.Context, userId string, productId string) (int64, error)
|
||||||
|
|
||||||
// 新增:根据TransactionID查询
|
// 新增:根据TransactionID查询
|
||||||
FindByTransactionId(ctx context.Context, transactionId string) (*entities.ApiCall, error)
|
FindByTransactionId(ctx context.Context, transactionId string) (*entities.ApiCall, error)
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"FLXG9C1D": flxg.ProcessFLXG9C1DRequest,
|
"FLXG9C1D": flxg.ProcessFLXG9C1DRequest,
|
||||||
"FLXG2E8F": flxg.ProcessFLXG2E8FRequest,
|
"FLXG2E8F": flxg.ProcessFLXG2E8FRequest,
|
||||||
"FLXG7E8F": flxg.ProcessFLXG7E8FRequest,
|
"FLXG7E8F": flxg.ProcessFLXG7E8FRequest,
|
||||||
|
"FLXG3A9B": flxg.ProcessFLXG3A9BRequest,
|
||||||
|
"FLXGK5D2": flxg.ProcessFLXGK5D2Request,
|
||||||
// JRZQ系列处理器
|
// JRZQ系列处理器
|
||||||
"JRZQ8203": jrzq.ProcessJRZQ8203Request,
|
"JRZQ8203": jrzq.ProcessJRZQ8203Request,
|
||||||
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
|
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
|
||||||
@@ -125,6 +126,13 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"JRZQ0L85": jrzq.ProcessJRZQ0L85Request,
|
"JRZQ0L85": jrzq.ProcessJRZQ0L85Request,
|
||||||
"JRZQ2F8A": jrzq.ProcessJRZQ2F8ARequest,
|
"JRZQ2F8A": jrzq.ProcessJRZQ2F8ARequest,
|
||||||
"JRZQ1E7B": jrzq.ProcessJRZQ1E7BRequest,
|
"JRZQ1E7B": jrzq.ProcessJRZQ1E7BRequest,
|
||||||
|
"JRZQ3C9R": jrzq.ProcessJRZQ3C9RRequest,
|
||||||
|
"JRZQ0B6Y": jrzq.ProcessJRZQ0B6YRequest,
|
||||||
|
"JRZQ9A1W": jrzq.ProcessJRZQ9A1WRequest,
|
||||||
|
"JRZQ8F7C": jrzq.ProcessJRZQ8F7CRequest,
|
||||||
|
"JRZQ1W4X": jrzq.ProcessJRZQ1W4XRequest,
|
||||||
|
"JRZQ3P01": jrzq.ProcessJRZQ3P01Request,
|
||||||
|
"JRZQ3AG6": jrzq.ProcessJRZQ3AG6Request,
|
||||||
|
|
||||||
// QYGL系列处理器
|
// QYGL系列处理器
|
||||||
"QYGL8261": qygl.ProcessQYGL8261Request,
|
"QYGL8261": qygl.ProcessQYGL8261Request,
|
||||||
@@ -144,6 +152,11 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"COMENT01": qygl.ProcessCOMENT01Request, // 企业风险报告
|
"COMENT01": qygl.ProcessCOMENT01Request, // 企业风险报告
|
||||||
"QYGL5F6A": qygl.ProcessQYGL5F6ARequest, // 企业相关查询
|
"QYGL5F6A": qygl.ProcessQYGL5F6ARequest, // 企业相关查询
|
||||||
"QYGL2B5C": qygl.ProcessQYGL2B5CRequest, // 企业联系人实际经营地址
|
"QYGL2B5C": qygl.ProcessQYGL2B5CRequest, // 企业联系人实际经营地址
|
||||||
|
"QYGL6S1B": qygl.ProcessQYGL6S1BRequest, //董监高司法综合信息核验
|
||||||
|
"QYGL9T1Q": qygl.ProcessQYGL9T1QRequest, //全国企业借贷意向验证查询_V1
|
||||||
|
"QYGL5A9T": qygl.ProcessQYGL5A9TRequest, //全国企业各类工商风险统计数量查询
|
||||||
|
"QYGL2S0W": qygl.ProcessQYGL2S0WRequest, //失信被执行企业个人查询
|
||||||
|
"QYGL5CMP": qygl.ProcessQYGL5CMPRequest, //企业五要素验证
|
||||||
|
|
||||||
// YYSY系列处理器
|
// YYSY系列处理器
|
||||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||||
@@ -162,6 +175,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"YYSY8C2D": yysy.ProcessYYSY8C2DRequest,
|
"YYSY8C2D": yysy.ProcessYYSY8C2DRequest,
|
||||||
"YYSY7D3E": yysy.ProcessYYSY7D3ERequest,
|
"YYSY7D3E": yysy.ProcessYYSY7D3ERequest,
|
||||||
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
|
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
|
||||||
|
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest,
|
||||||
|
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest,
|
||||||
|
|
||||||
// IVYZ系列处理器
|
// IVYZ系列处理器
|
||||||
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
|
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
|
||||||
@@ -185,10 +200,17 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"IVYZ81NC": ivyz.ProcessIVYZ81NCRequest,
|
"IVYZ81NC": ivyz.ProcessIVYZ81NCRequest,
|
||||||
"IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest,
|
"IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest,
|
||||||
"IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest,
|
"IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest,
|
||||||
|
"IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest,
|
||||||
|
"IVYZ2C1P": ivyz.ProcessIVYZ2C1PRequest,
|
||||||
|
"IVYZP2Q6": ivyz.ProcessIVYZP2Q6Request,
|
||||||
|
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
|
||||||
|
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
|
||||||
|
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
|
||||||
|
|
||||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||||
"COMBHZY2": comb.ProcessCOMBHZY2Request, // 自定义处理:生成合规报告
|
"COMBHZY2": comb.ProcessCOMBHZY2Request, // 自定义处理:生成合规报告
|
||||||
|
"COMBWD01": comb.ProcessCOMBWD01Request, // 自定义处理:将返回结构从数组改为对象
|
||||||
|
|
||||||
// QCXG系列处理器
|
// QCXG系列处理器
|
||||||
"QCXG7A2B": qcxg.ProcessQCXG7A2BRequest,
|
"QCXG7A2B": qcxg.ProcessQCXG7A2BRequest,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
"tyapi-server/internal/config"
|
"tyapi-server/internal/config"
|
||||||
"tyapi-server/internal/domains/api/entities"
|
"tyapi-server/internal/domains/api/entities"
|
||||||
repo "tyapi-server/internal/domains/api/repositories"
|
repo "tyapi-server/internal/domains/api/repositories"
|
||||||
@@ -10,7 +11,7 @@ import (
|
|||||||
type ApiUserAggregateService interface {
|
type ApiUserAggregateService interface {
|
||||||
CreateApiUser(ctx context.Context, apiUserId string) error
|
CreateApiUser(ctx context.Context, apiUserId string) error
|
||||||
UpdateWhiteList(ctx context.Context, apiUserId string, whiteList []string) error
|
UpdateWhiteList(ctx context.Context, apiUserId string, whiteList []string) error
|
||||||
AddToWhiteList(ctx context.Context, apiUserId string, entry string) error
|
AddToWhiteList(ctx context.Context, apiUserId string, entry string, remark string) error
|
||||||
RemoveFromWhiteList(ctx context.Context, apiUserId string, entry string) error
|
RemoveFromWhiteList(ctx context.Context, apiUserId string, entry string) error
|
||||||
FreezeApiUser(ctx context.Context, apiUserId string) error
|
FreezeApiUser(ctx context.Context, apiUserId string) error
|
||||||
UnfreezeApiUser(ctx context.Context, apiUserId string) error
|
UnfreezeApiUser(ctx context.Context, apiUserId string) error
|
||||||
@@ -44,16 +45,25 @@ func (s *ApiUserAggregateServiceImpl) UpdateWhiteList(ctx context.Context, apiUs
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
apiUser.UpdateWhiteList(whiteList)
|
// 将字符串数组转换为WhiteListItem数组
|
||||||
|
items := make([]entities.WhiteListItem, 0, len(whiteList))
|
||||||
|
now := time.Now()
|
||||||
|
for _, ip := range whiteList {
|
||||||
|
items = append(items, entities.WhiteListItem{
|
||||||
|
IPAddress: ip,
|
||||||
|
AddedAt: now, // 批量更新时使用当前时间
|
||||||
|
})
|
||||||
|
}
|
||||||
|
apiUser.UpdateWhiteList(items) // UpdateWhiteList 会转换为 WhiteList 类型
|
||||||
return s.repo.Update(ctx, apiUser)
|
return s.repo.Update(ctx, apiUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ApiUserAggregateServiceImpl) AddToWhiteList(ctx context.Context, apiUserId string, entry string) error {
|
func (s *ApiUserAggregateServiceImpl) AddToWhiteList(ctx context.Context, apiUserId string, entry string, remark string) error {
|
||||||
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = apiUser.AddToWhiteList(entry)
|
err = apiUser.AddToWhiteList(entry, remark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -90,7 +100,6 @@ func (s *ApiUserAggregateServiceImpl) UnfreezeApiUser(ctx context.Context, apiUs
|
|||||||
return s.repo.Update(ctx, apiUser)
|
return s.repo.Update(ctx, apiUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (s *ApiUserAggregateServiceImpl) LoadApiUserByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error) {
|
func (s *ApiUserAggregateServiceImpl) LoadApiUserByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error) {
|
||||||
return s.repo.FindByAccessId(ctx, accessId)
|
return s.repo.FindByAccessId(ctx, accessId)
|
||||||
}
|
}
|
||||||
@@ -103,7 +112,7 @@ func (s *ApiUserAggregateServiceImpl) LoadApiUserByUserId(ctx context.Context, a
|
|||||||
|
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = entities.WhiteList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiUser, nil
|
return apiUser, nil
|
||||||
@@ -117,7 +126,7 @@ func (s *ApiUserAggregateServiceImpl) SaveApiUser(ctx context.Context, apiUser *
|
|||||||
if exists != nil {
|
if exists != nil {
|
||||||
// 确保WhiteList不为nil
|
// 确保WhiteList不为nil
|
||||||
if apiUser.WhiteList == nil {
|
if apiUser.WhiteList == nil {
|
||||||
apiUser.WhiteList = []string{}
|
apiUser.WhiteList = []entities.WhiteListItem{}
|
||||||
}
|
}
|
||||||
return s.repo.Update(ctx, apiUser)
|
return s.repo.Update(ctx, apiUser)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||||
"JRZQDBCE": &dto.JRZQDBCEReq{},
|
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
||||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||||
@@ -113,7 +113,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"YYSY4B21": &dto.YYSY4B21Req{},
|
"YYSY4B21": &dto.YYSY4B21Req{},
|
||||||
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
||||||
"YYSY09CD": &dto.YYSY09CDReq{},
|
"YYSY09CD": &dto.YYSY09CDReq{},
|
||||||
"IVYZ0b03": &dto.IVYZ0b03Req{},
|
"IVYZ0B03": &dto.IVYZ0B03Req{},
|
||||||
"YYSYBE08": &dto.YYSYBE08Req{},
|
"YYSYBE08": &dto.YYSYBE08Req{},
|
||||||
"YYSYD50F": &dto.YYSYD50FReq{},
|
"YYSYD50F": &dto.YYSYD50FReq{},
|
||||||
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
||||||
@@ -155,6 +155,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
||||||
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
||||||
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
||||||
|
"IVYZ9K2L": &dto.IVYZ9K2LReq{},
|
||||||
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
||||||
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
||||||
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
||||||
@@ -171,12 +172,33 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"IVYZ6G7H": &dto.IVYZ6G7HReq{},
|
"IVYZ6G7H": &dto.IVYZ6G7HReq{},
|
||||||
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
||||||
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
||||||
"COMBHZY2": &dto.COMBHZY2Req{},
|
"COMBHZY2": &dto.COMBHZY2Req{}, // 自此无imp11.28
|
||||||
"QCXG8A3D": &dto.QCXG8A3DReq{},
|
"QCXG8A3D": &dto.QCXG8A3DReq{},
|
||||||
"QCXG6B4E": &dto.QCXG6B4EReq{},
|
"QCXG6B4E": &dto.QCXG6B4EReq{},
|
||||||
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
||||||
"JRZQ2F8A": &dto.JRZQ2F8AReq{},
|
"JRZQ2F8A": &dto.JRZQ2F8AReq{},
|
||||||
"JRZQ1E7B": &dto.JRZQ1E7BReq{},
|
"JRZQ1E7B": &dto.JRZQ1E7BReq{},
|
||||||
|
"JRZQ3C9R": &dto.JRZQ3C9RReq{},
|
||||||
|
"IVYZ2C1P": &dto.IVYZ2C1PReq{},
|
||||||
|
"YYSY9F1B": &dto.YYSY9F1BReq{},
|
||||||
|
"YYSY6F2B": &dto.YYSY6F2BReq{},
|
||||||
|
"QYGL6S1B": &dto.QYGL6S1BReq{},
|
||||||
|
"JRZQ0B6Y": &dto.JRZQ0B6YReq{},
|
||||||
|
"JRZQ9A1W": &dto.JRZQ9A1WReq{},
|
||||||
|
"JRZQ8F7C": &dto.JRZQ8F7CReq{}, //综合多头
|
||||||
|
"FLXGK5D2": &dto.FLXGK5D2Req{},
|
||||||
|
"FLXG3A9B": &dto.FLXG3A9BReq{},
|
||||||
|
"IVYZP2Q6": &dto.IVYZP2Q6Req{},
|
||||||
|
"JRZQ1W4X": &dto.JRZQ1W4XReq{}, //全景档案
|
||||||
|
"QYGL2S0W": &dto.QYGL2S0WReq{}, //失信被执行企业个人查询
|
||||||
|
"QYGL9T1Q": &dto.QYGL9T1QReq{}, //全国企业借贷意向验证查询_V1
|
||||||
|
"QYGL5A9T": &dto.QYGL5A9TReq{}, //全国企业各类工商风险统计数量查询
|
||||||
|
"JRZQ3P01": &dto.JRZQ3P01Req{}, //天远风控决策
|
||||||
|
"JRZQ3AG6": &dto.JRZQ3AG6Req{}, //轻松查公积
|
||||||
|
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
|
||||||
|
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
|
||||||
|
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
|
||||||
|
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先返回已配置的DTO
|
// 优先返回已配置的DTO
|
||||||
@@ -294,10 +316,13 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
|
|||||||
frontendRules = append(frontendRules, "返回链接格式")
|
frontendRules = append(frontendRules, "返回链接格式")
|
||||||
case rule == "validAuthorizationURL":
|
case rule == "validAuthorizationURL":
|
||||||
frontendRules = append(frontendRules, "授权链接格式")
|
frontendRules = append(frontendRules, "授权链接格式")
|
||||||
|
case rule == "validBase64Image":
|
||||||
|
frontendRules = append(frontendRules, "Base64图片格式(JPG、BMP、PNG)")
|
||||||
case strings.HasPrefix(rule, "oneof="):
|
case strings.HasPrefix(rule, "oneof="):
|
||||||
values := strings.TrimPrefix(rule, "oneof=")
|
values := strings.TrimPrefix(rule, "oneof=")
|
||||||
frontendRules = append(frontendRules, "可选值: "+values)
|
frontendRules = append(frontendRules, "可选值: "+values)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(frontendRules, "、")
|
return strings.Join(frontendRules, "、")
|
||||||
@@ -323,6 +348,8 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
|
|||||||
return "url"
|
return "url"
|
||||||
} else if strings.Contains(validation, "可选值") {
|
} else if strings.Contains(validation, "可选值") {
|
||||||
return "select"
|
return "select"
|
||||||
|
} else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "base64") {
|
||||||
|
return "textarea"
|
||||||
}
|
}
|
||||||
return "text"
|
return "text"
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
@@ -368,6 +395,10 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
|||||||
"plate_type": "号牌类型",
|
"plate_type": "号牌类型",
|
||||||
"vin_code": "车辆识别代号VIN码",
|
"vin_code": "车辆识别代号VIN码",
|
||||||
"return_type": "返回类型",
|
"return_type": "返回类型",
|
||||||
|
"photo_data": "人脸图片",
|
||||||
|
"owner_type": "企业主类型",
|
||||||
|
"type": "查询类型",
|
||||||
|
"query_reason_id": "查询原因ID",
|
||||||
}
|
}
|
||||||
|
|
||||||
if label, exists := labelMap[jsonTag]; exists {
|
if label, exists := labelMap[jsonTag]; exists {
|
||||||
@@ -409,6 +440,10 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
|||||||
"plate_type": "01",
|
"plate_type": "01",
|
||||||
"vin_code": "LSGBF53M8DS123456",
|
"vin_code": "LSGBF53M8DS123456",
|
||||||
"return_type": "1",
|
"return_type": "1",
|
||||||
|
"photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||||
|
"ownerType": "1",
|
||||||
|
"type": "per",
|
||||||
|
"query_reason_id": "1",
|
||||||
}
|
}
|
||||||
|
|
||||||
if example, exists := exampleMap[jsonTag]; exists {
|
if example, exists := exampleMap[jsonTag]; exists {
|
||||||
@@ -459,6 +494,10 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
|||||||
"plate_type": "请选择号牌类型(01或02)",
|
"plate_type": "请选择号牌类型(01或02)",
|
||||||
"vin_code": "请输入17位车辆识别代号VIN码",
|
"vin_code": "请输入17位车辆识别代号VIN码",
|
||||||
"return_type": "请选择返回类型",
|
"return_type": "请选择返回类型",
|
||||||
|
"photo_data": "请输入base64编码的人脸图片(支持JPG、BMP、PNG格式)",
|
||||||
|
"ownerType": "请选择企业主类型",
|
||||||
|
"type": "请选择查询类型",
|
||||||
|
"query_reason_id": "请选择查询原因ID",
|
||||||
}
|
}
|
||||||
|
|
||||||
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
||||||
@@ -484,7 +523,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
|||||||
func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation string) string {
|
func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation string) string {
|
||||||
descMap := map[string]string{
|
descMap := map[string]string{
|
||||||
"mobile_no": "请输入11位手机号码",
|
"mobile_no": "请输入11位手机号码",
|
||||||
"id_card": "请输入18位身份证号码",
|
"id_card": "请输入18位身份证号码最后一位如是字母请大写",
|
||||||
"name": "请输入真实姓名",
|
"name": "请输入真实姓名",
|
||||||
"man_name": "请输入男方真实姓名",
|
"man_name": "请输入男方真实姓名",
|
||||||
"woman_name": "请输入女方真实姓名",
|
"woman_name": "请输入女方真实姓名",
|
||||||
@@ -511,6 +550,10 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
|||||||
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
||||||
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
||||||
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
"return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称",
|
||||||
|
"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-企业背调",
|
||||||
}
|
}
|
||||||
|
|
||||||
if desc, exists := descMap[jsonTag]; exists {
|
if desc, exists := descMap[jsonTag]; exists {
|
||||||
|
|||||||
@@ -4,61 +4,145 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/shared/logger"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessCOMBHZY2Request 处理 COMBHZY2 组合包请求
|
// ProcessCOMBHZY2Request 处理 COMBHZY2 组合包请求
|
||||||
func ProcessCOMBHZY2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
func ProcessCOMBHZY2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
|
|
||||||
var req dto.COMBHZY2Req
|
var req dto.COMBHZY2Req
|
||||||
if err := json.Unmarshal(params, &req); err != nil {
|
if err := json.Unmarshal(params, &req); err != nil {
|
||||||
|
log.Error("COMBHZY2请求参数反序列化失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("params", string(params)),
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deps.Validator.ValidateStruct(req); err != nil {
|
if err := deps.Validator.ValidateStruct(req); err != nil {
|
||||||
|
log.Error("COMBHZY2请求参数验证失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMBHZY2")
|
combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMBHZY2")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("COMBHZY2组合包服务调用失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCtx, err := buildSourceContextFromCombined(combinedResult)
|
if combinedResult == nil {
|
||||||
|
log.Error("COMBHZY2组合包响应为空",
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
return nil, errors.New("组合包响应为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("COMBHZY2组合包服务调用成功",
|
||||||
|
zap.Int("子产品数量", len(combinedResult.Responses)),
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
|
||||||
|
sourceCtx, err := buildSourceContextFromCombined(ctx, combinedResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("COMBHZY2构建源数据上下文失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
report := buildTargetReport(sourceCtx)
|
report := buildTargetReport(ctx, sourceCtx)
|
||||||
return json.Marshal(report)
|
|
||||||
|
reportBytes, err := json.Marshal(report)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("COMBHZY2报告序列化失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reportBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSourceContextFromCombined(result *processors.CombinedResult) (*sourceContext, error) {
|
func buildSourceContextFromCombined(ctx context.Context, result *processors.CombinedResult) (*sourceContext, error) {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
|
|
||||||
if result == nil {
|
if result == nil {
|
||||||
|
log.Error("组合包响应为空", zap.String("api_code", "COMBHZY2"))
|
||||||
return nil, errors.New("组合包响应为空")
|
return nil, errors.New("组合包响应为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
src := sourceFile{Responses: make([]sourceResponse, 0, len(result.Responses))}
|
src := sourceFile{Responses: make([]sourceResponse, 0, len(result.Responses))}
|
||||||
|
successCount := 0
|
||||||
|
failedCount := 0
|
||||||
|
|
||||||
for _, resp := range result.Responses {
|
for _, resp := range result.Responses {
|
||||||
if !resp.Success {
|
if !resp.Success {
|
||||||
|
log.Warn("子产品调用失败,跳过",
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
zap.String("error", resp.Error),
|
||||||
|
zap.String("parent_api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
failedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.Data == nil {
|
||||||
|
log.Warn("子产品数据为空,跳过",
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
zap.String("parent_api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
failedCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
raw, err := json.Marshal(resp.Data)
|
raw, err := json.Marshal(resp.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("序列化子产品(%s)数据失败: %w", resp.ApiCode, err)
|
log.Error("序列化子产品数据失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
zap.String("parent_api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
failedCount++
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
src.Responses = append(src.Responses, sourceResponse{
|
src.Responses = append(src.Responses, sourceResponse{
|
||||||
ApiCode: resp.ApiCode,
|
ApiCode: resp.ApiCode,
|
||||||
Data: raw,
|
Data: raw,
|
||||||
Success: resp.Success,
|
Success: resp.Success,
|
||||||
})
|
})
|
||||||
|
successCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("组合包子产品处理完成",
|
||||||
|
zap.Int("成功数量", successCount),
|
||||||
|
zap.Int("失败数量", failedCount),
|
||||||
|
zap.Int("总数量", len(result.Responses)),
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
|
||||||
if len(src.Responses) == 0 {
|
if len(src.Responses) == 0 {
|
||||||
|
log.Error("组合包子产品全部调用失败",
|
||||||
|
zap.Int("总数量", len(result.Responses)),
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return nil, errors.New("组合包子产品全部调用失败")
|
return nil, errors.New("组合包子产品全部调用失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildSourceContext(src)
|
return buildSourceContext(ctx, src)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
package comb
|
package comb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/shared/logger"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// =======================
|
// =======================
|
||||||
@@ -468,46 +472,105 @@ type sourceContext struct {
|
|||||||
// =======================
|
// =======================
|
||||||
|
|
||||||
// buildSourceContext 根据 source.json 解析出各子产品的结构化数据
|
// buildSourceContext 根据 source.json 解析出各子产品的结构化数据
|
||||||
func buildSourceContext(src sourceFile) (*sourceContext, error) {
|
func buildSourceContext(ctx context.Context, src sourceFile) (*sourceContext, error) {
|
||||||
ctx := &sourceContext{}
|
log := logger.GetGlobalLogger()
|
||||||
|
result := &sourceContext{}
|
||||||
|
|
||||||
for _, resp := range src.Responses {
|
for _, resp := range src.Responses {
|
||||||
if !resp.Success {
|
if !resp.Success {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查数据是否为空
|
||||||
|
if len(resp.Data) == 0 {
|
||||||
|
log.Warn("子产品数据为空,跳过解析",
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
zap.String("parent_api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch strings.ToUpper(resp.ApiCode) {
|
switch strings.ToUpper(resp.ApiCode) {
|
||||||
case "DWBG8B4D":
|
case "DWBG8B4D":
|
||||||
var data baseProductData
|
var data baseProductData
|
||||||
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
||||||
return nil, fmt.Errorf("解析DWBG8B4D数据失败: %w", err)
|
log.Error("解析DWBG8B4D数据失败,使用兼容处理",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
zap.String("data_preview", string(resp.Data[:min(100, len(resp.Data))])),
|
||||||
|
)
|
||||||
|
// 尝试部分解析,即使失败也继续
|
||||||
|
if partialErr := json.Unmarshal(resp.Data, &data); partialErr != nil {
|
||||||
|
log.Warn("DWBG8B4D数据格式异常,将使用空结构",
|
||||||
|
zap.Error(partialErr),
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
)
|
||||||
|
// 使用空结构,不返回错误
|
||||||
|
data = baseProductData{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctx.BaseData = &data
|
result.BaseData = &data
|
||||||
case "FLXG7E8F":
|
case "FLXG7E8F":
|
||||||
var data judicialProductData
|
var data judicialProductData
|
||||||
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
||||||
return nil, fmt.Errorf("解析FLXG7E8F数据失败: %w", err)
|
log.Warn("解析FLXG7E8F数据失败,使用兼容处理",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
zap.String("data_preview", string(resp.Data[:min(100, len(resp.Data))])),
|
||||||
|
)
|
||||||
|
// 使用空结构,不返回错误
|
||||||
|
data = judicialProductData{}
|
||||||
}
|
}
|
||||||
ctx.JudicialData = &data
|
result.JudicialData = &data
|
||||||
case "JRZQ9D4E":
|
case "JRZQ9D4E":
|
||||||
var data contentsProductData
|
var data contentsProductData
|
||||||
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
||||||
return nil, fmt.Errorf("解析JRZQ9D4E数据失败: %w", err)
|
log.Warn("解析JRZQ9D4E数据失败,使用兼容处理",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
zap.String("data_preview", string(resp.Data[:min(100, len(resp.Data))])),
|
||||||
|
)
|
||||||
|
// 使用空结构,不返回错误
|
||||||
|
data = contentsProductData{}
|
||||||
}
|
}
|
||||||
ctx.ContentsData = &data
|
result.ContentsData = &data
|
||||||
case "JRZQ6F2A":
|
case "JRZQ6F2A":
|
||||||
var data riskScreenProductData
|
var data riskScreenProductData
|
||||||
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
||||||
return nil, fmt.Errorf("解析JRZQ6F2A数据失败: %w", err)
|
log.Warn("解析JRZQ6F2A数据失败,使用兼容处理",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
zap.String("data_preview", string(resp.Data[:min(100, len(resp.Data))])),
|
||||||
|
)
|
||||||
|
// 使用空结构,不返回错误
|
||||||
|
data = riskScreenProductData{}
|
||||||
}
|
}
|
||||||
ctx.RiskScreen = &data
|
result.RiskScreen = &data
|
||||||
|
default:
|
||||||
|
log.Debug("未知的子产品API代码,跳过",
|
||||||
|
zap.String("api_code", resp.ApiCode),
|
||||||
|
zap.String("parent_api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.BaseData == nil {
|
if result.BaseData == nil {
|
||||||
return nil, errors.New("未获取到DWBG8B4D核心数据")
|
log.Warn("未获取到DWBG8B4D核心数据,将使用空结构",
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
// 使用空结构,不返回错误,让后续处理能够继续
|
||||||
|
result.BaseData = &baseProductData{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx, nil
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// min 辅助函数,返回两个整数中的较小值
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================
|
// =======================
|
||||||
@@ -515,22 +578,35 @@ func buildSourceContext(src sourceFile) (*sourceContext, error) {
|
|||||||
// =======================
|
// =======================
|
||||||
|
|
||||||
// buildTargetReport 将上下文数据映射为 target.json 完整结构
|
// buildTargetReport 将上下文数据映射为 target.json 完整结构
|
||||||
func buildTargetReport(ctx *sourceContext) targetReport {
|
func buildTargetReport(ctx context.Context, sourceCtx *sourceContext) targetReport {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
|
|
||||||
|
// 使用recover捕获panic,确保不会导致整个请求失败
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Error("构建目标报告时发生panic,使用默认值",
|
||||||
|
zap.Any("panic", r),
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
report := targetReport{
|
report := targetReport{
|
||||||
ReportSummary: buildReportSummary(ctx),
|
ReportSummary: buildReportSummary(ctx, sourceCtx),
|
||||||
BasicInfo: buildBasicInfo(ctx),
|
BasicInfo: buildBasicInfo(ctx, sourceCtx),
|
||||||
RiskIdentification: buildRiskIdentification(ctx),
|
RiskIdentification: buildRiskIdentification(ctx, sourceCtx),
|
||||||
CreditAssessment: buildCreditAssessment(ctx),
|
CreditAssessment: buildCreditAssessment(ctx, sourceCtx),
|
||||||
LeasingRiskAssessment: buildLeasingRiskAssessment(ctx),
|
LeasingRiskAssessment: buildLeasingRiskAssessment(ctx, sourceCtx),
|
||||||
ComprehensiveAnalysis: buildComprehensiveAnalysis(ctx),
|
ComprehensiveAnalysis: buildComprehensiveAnalysis(ctx, sourceCtx),
|
||||||
ReportFooter: buildReportFooter(ctx),
|
ReportFooter: buildReportFooter(ctx, sourceCtx),
|
||||||
}
|
}
|
||||||
|
|
||||||
return report
|
return report
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildReportSummary 组装 reportSummary,包括规则、反欺诈信息
|
// buildReportSummary 组装 reportSummary,包括规则、反欺诈信息
|
||||||
func buildReportSummary(ctx *sourceContext) reportSummary {
|
func buildReportSummary(ctx context.Context, sourceCtx *sourceContext) reportSummary {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
const strategyCode = "STR0042314/贷前-经营性租赁全量策略"
|
const strategyCode = "STR0042314/贷前-经营性租赁全量策略"
|
||||||
|
|
||||||
summary := reportSummary{
|
summary := reportSummary{
|
||||||
@@ -551,40 +627,66 @@ func buildReportSummary(ctx *sourceContext) reportSummary {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.BaseData == nil {
|
if sourceCtx == nil || sourceCtx.BaseData == nil {
|
||||||
|
log.Warn("BaseData为空,使用默认summary值",
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return summary
|
return summary
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyResult := strings.TrimSpace(ctx.BaseData.VerifyRule)
|
// 兼容处理:安全访问字段
|
||||||
|
verifyResult := ""
|
||||||
|
if sourceCtx.BaseData.VerifyRule != "" {
|
||||||
|
verifyResult = strings.TrimSpace(sourceCtx.BaseData.VerifyRule)
|
||||||
|
}
|
||||||
if verifyResult == "" {
|
if verifyResult == "" {
|
||||||
verifyResult = "未命中"
|
verifyResult = "未命中"
|
||||||
}
|
}
|
||||||
summary.RuleValidation.Result = verifyResult
|
summary.RuleValidation.Result = verifyResult
|
||||||
|
|
||||||
scoreLevel := riskLevelFromScore(ctx.BaseData.FraudScore)
|
scoreLevel := riskLevelFromScore(sourceCtx.BaseData.FraudScore)
|
||||||
summary.AntiFraudScore.Level = scoreLevel
|
summary.AntiFraudScore.Level = scoreLevel
|
||||||
fraudRuleLevel := strings.TrimSpace(ctx.BaseData.FraudRule)
|
|
||||||
|
fraudRuleLevel := ""
|
||||||
|
if sourceCtx.BaseData.FraudRule != "" {
|
||||||
|
fraudRuleLevel = strings.TrimSpace(sourceCtx.BaseData.FraudRule)
|
||||||
|
}
|
||||||
if fraudRuleLevel == "" || fraudRuleLevel == "-" {
|
if fraudRuleLevel == "" || fraudRuleLevel == "-" {
|
||||||
fraudRuleLevel = "未命中"
|
fraudRuleLevel = "未命中"
|
||||||
}
|
}
|
||||||
summary.AntiFraudRule.Level = fraudRuleLevel
|
summary.AntiFraudRule.Level = fraudRuleLevel
|
||||||
|
|
||||||
riskCount := ctx.BaseData.RiskWarning.TotalRiskCounts
|
// 兼容处理:安全访问RiskWarning
|
||||||
|
riskCount := 0
|
||||||
|
if sourceCtx.BaseData.RiskWarning.TotalRiskCounts > 0 {
|
||||||
|
riskCount = sourceCtx.BaseData.RiskWarning.TotalRiskCounts
|
||||||
|
}
|
||||||
summary.AbnormalRulesHit.Count = riskCount
|
summary.AbnormalRulesHit.Count = riskCount
|
||||||
summary.AbnormalRulesHit.Alert = buildRiskWarningAlert(ctx.BaseData.RiskWarning)
|
summary.AbnormalRulesHit.Alert = buildRiskWarningAlert(sourceCtx.BaseData.RiskWarning)
|
||||||
|
|
||||||
return summary
|
return summary
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildBasicInfo 组装 basicInfo,包含基础信息与核验列表
|
// buildBasicInfo 组装 basicInfo,包含基础信息与核验列表
|
||||||
func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
|
func buildBasicInfo(ctx context.Context, sourceCtx *sourceContext) reportBasicInfo {
|
||||||
base := ctx.BaseData.BaseInfo
|
log := logger.GetGlobalLogger()
|
||||||
|
|
||||||
|
// 兼容处理:安全访问BaseData和BaseInfo
|
||||||
|
var base baseInfo
|
||||||
|
if sourceCtx != nil && sourceCtx.BaseData != nil {
|
||||||
|
base = sourceCtx.BaseData.BaseInfo
|
||||||
|
} else {
|
||||||
|
log.Warn("BaseData或BaseInfo为空,使用默认值",
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
|
base = baseInfo{}
|
||||||
|
}
|
||||||
|
|
||||||
reportID := generateReportID()
|
reportID := generateReportID()
|
||||||
|
|
||||||
verifications := make([]verificationItem, 0, 5)
|
verifications := make([]verificationItem, 0, 5)
|
||||||
|
|
||||||
elementResult, elementDetails := buildElementVerificationResult(ctx)
|
elementResult, elementDetails := buildElementVerificationResult(sourceCtx)
|
||||||
verifications = append(verifications, verificationItem{
|
verifications = append(verifications, verificationItem{
|
||||||
Item: "要素核查",
|
Item: "要素核查",
|
||||||
Description: "使用姓名、手机号、身份证信息进行三要素核验",
|
Description: "使用姓名、手机号、身份证信息进行三要素核验",
|
||||||
@@ -592,7 +694,7 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
|
|||||||
Details: elementDetails,
|
Details: elementDetails,
|
||||||
})
|
})
|
||||||
|
|
||||||
carrierResult, carrierDetails := buildCarrierVerificationResult(ctx)
|
carrierResult, carrierDetails := buildCarrierVerificationResult(sourceCtx)
|
||||||
verifications = append(verifications, verificationItem{
|
verifications = append(verifications, verificationItem{
|
||||||
Item: "运营商检验",
|
Item: "运营商检验",
|
||||||
Description: "检查手机号在运营商处的状态及在线时长",
|
Description: "检查手机号在运营商处的状态及在线时长",
|
||||||
@@ -600,25 +702,27 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
|
|||||||
Details: carrierDetails,
|
Details: carrierDetails,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 兼容处理:安全访问JudicialData
|
||||||
var stat *lawsuitStat
|
var stat *lawsuitStat
|
||||||
if ctx.JudicialData != nil {
|
if sourceCtx != nil && sourceCtx.JudicialData != nil {
|
||||||
stat = &ctx.JudicialData.JudicialData.LawsuitStat
|
stat = &sourceCtx.JudicialData.JudicialData.LawsuitStat
|
||||||
}
|
}
|
||||||
|
|
||||||
totalCaseCount := 0
|
totalCaseCount := 0
|
||||||
totalCriminal := 0
|
totalCriminal := 0
|
||||||
totalExecution := 0
|
totalExecution := 0
|
||||||
if stat != nil {
|
if stat != nil {
|
||||||
totalCriminal = len(stat.Criminal.Cases)
|
// 兼容处理:安全访问Cases数组
|
||||||
totalCaseCount = totalCriminal + len(stat.Civil.Cases) + len(stat.Administrative.Cases) + len(stat.Preservation.Cases) + len(stat.Bankrupt.Cases)
|
totalCriminal = safeLen(stat.Criminal.Cases)
|
||||||
totalExecution = len(stat.Implement.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
|
totalDishonest := 0
|
||||||
totalRestriction := 0
|
totalRestriction := 0
|
||||||
if ctx.JudicialData != nil {
|
if sourceCtx != nil && sourceCtx.JudicialData != nil {
|
||||||
totalDishonest = len(ctx.JudicialData.JudicialData.BreachCaseList)
|
totalDishonest = safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList)
|
||||||
totalRestriction = len(ctx.JudicialData.JudicialData.ConsumptionRestrictionList)
|
totalRestriction = safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList)
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalCaseCount > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
if totalCaseCount > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
||||||
@@ -629,11 +733,11 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
|
|||||||
detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count))
|
detailParts = append(detailParts, fmt.Sprintf("%s%d条", label, count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addCaseDetail("刑事案件", len(stat.Criminal.Cases))
|
addCaseDetail("刑事案件", safeLen(stat.Criminal.Cases))
|
||||||
addCaseDetail("民事案件", len(stat.Civil.Cases))
|
addCaseDetail("民事案件", safeLen(stat.Civil.Cases))
|
||||||
addCaseDetail("行政案件", len(stat.Administrative.Cases))
|
addCaseDetail("行政案件", safeLen(stat.Administrative.Cases))
|
||||||
addCaseDetail("非诉保全审查案件", len(stat.Preservation.Cases))
|
addCaseDetail("非诉保全审查案件", safeLen(stat.Preservation.Cases))
|
||||||
addCaseDetail("强制清算与破产案件", len(stat.Bankrupt.Cases))
|
addCaseDetail("强制清算与破产案件", safeLen(stat.Bankrupt.Cases))
|
||||||
}
|
}
|
||||||
if totalExecution > 0 {
|
if totalExecution > 0 {
|
||||||
detailParts = append(detailParts, fmt.Sprintf("执行案件%d条", totalExecution))
|
detailParts = append(detailParts, fmt.Sprintf("执行案件%d条", totalExecution))
|
||||||
@@ -659,7 +763,7 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loanRiskResult, loanRiskDetails := buildLoanRiskResult(ctx)
|
loanRiskResult, loanRiskDetails := buildLoanRiskResult(sourceCtx)
|
||||||
verifications = append(verifications, verificationItem{
|
verifications = append(verifications, verificationItem{
|
||||||
Item: "借贷评估",
|
Item: "借贷评估",
|
||||||
Description: "综合近12个月借贷申请情况评估风险",
|
Description: "综合近12个月借贷申请情况评估风险",
|
||||||
@@ -667,7 +771,7 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
|
|||||||
Details: loanRiskDetails,
|
Details: loanRiskDetails,
|
||||||
})
|
})
|
||||||
|
|
||||||
otherDetails := gatherOtherRiskDetails(ctx)
|
otherDetails := gatherOtherRiskDetails(sourceCtx)
|
||||||
if otherDetails != "" {
|
if otherDetails != "" {
|
||||||
verifications = append(verifications, verificationItem{
|
verifications = append(verifications, verificationItem{
|
||||||
Item: "其他",
|
Item: "其他",
|
||||||
@@ -685,8 +789,18 @@ func buildBasicInfo(ctx *sourceContext) reportBasicInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// safeLen 安全获取切片长度,避免nil指针
|
||||||
|
func safeLen[T any](s []T) int {
|
||||||
|
if s == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
// buildRiskIdentification 组装 riskIdentification,各列表对标 target.json
|
// buildRiskIdentification 组装 riskIdentification,各列表对标 target.json
|
||||||
func buildRiskIdentification(ctx *sourceContext) riskIdentification {
|
func buildRiskIdentification(ctx context.Context, sourceCtx *sourceContext) riskIdentification {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
|
|
||||||
identification := riskIdentification{
|
identification := riskIdentification{
|
||||||
Title: "风险识别产品",
|
Title: "风险识别产品",
|
||||||
CaseAnnouncements: caseAnnouncementSection{
|
CaseAnnouncements: caseAnnouncementSection{
|
||||||
@@ -707,35 +821,58 @@ func buildRiskIdentification(ctx *sourceContext) riskIdentification {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.JudicialData == nil {
|
// 兼容处理:安全访问JudicialData
|
||||||
|
if sourceCtx == nil || sourceCtx.JudicialData == nil {
|
||||||
|
log.Debug("JudicialData为空,返回空的风险识别数据",
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return identification
|
return identification
|
||||||
}
|
}
|
||||||
|
|
||||||
stat := ctx.JudicialData.JudicialData.LawsuitStat
|
stat := sourceCtx.JudicialData.JudicialData.LawsuitStat
|
||||||
baseName := ""
|
baseName := ""
|
||||||
baseID := ""
|
baseID := ""
|
||||||
if ctx.BaseData != nil {
|
if sourceCtx.BaseData != nil {
|
||||||
baseName = ctx.BaseData.BaseInfo.Name
|
baseName = sourceCtx.BaseData.BaseInfo.Name
|
||||||
baseID = ctx.BaseData.BaseInfo.IdCard
|
baseID = sourceCtx.BaseData.BaseInfo.IdCard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兼容处理:安全访问Cases数组
|
||||||
caseRecords := make([]caseAnnouncementRecord, 0)
|
caseRecords := make([]caseAnnouncementRecord, 0)
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...)
|
if stat.Civil.Cases != nil {
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Criminal.Cases, "刑事案件")...)
|
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Civil.Cases, "民事案件")...)
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Administrative.Cases, "行政案件")...)
|
}
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Preservation.Cases, "非诉保全审查")...)
|
if stat.Criminal.Cases != nil {
|
||||||
caseRecords = append(caseRecords, convertCaseAnnouncements(stat.Bankrupt.Cases, "强制清算与破产")...)
|
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, "强制清算与破产")...)
|
||||||
|
}
|
||||||
identification.CaseAnnouncements.Records = caseRecords
|
identification.CaseAnnouncements.Records = caseRecords
|
||||||
|
|
||||||
identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases)
|
if stat.Implement.Cases != nil {
|
||||||
identification.DishonestAnnouncements.Records = convertDishonestAnnouncements(ctx.JudicialData.JudicialData.BreachCaseList, baseName, baseID)
|
identification.EnforcementAnnouncements.Records = convertEnforcementAnnouncements(stat.Implement.Cases)
|
||||||
identification.HighConsumptionRestrictionAnn.Records = convertConsumptionRestrictions(ctx.JudicialData.JudicialData.ConsumptionRestrictionList, baseName, baseID)
|
}
|
||||||
|
if sourceCtx.JudicialData.JudicialData.BreachCaseList != nil {
|
||||||
|
identification.DishonestAnnouncements.Records = convertDishonestAnnouncements(sourceCtx.JudicialData.JudicialData.BreachCaseList, baseName, baseID)
|
||||||
|
}
|
||||||
|
if sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList != nil {
|
||||||
|
identification.HighConsumptionRestrictionAnn.Records = convertConsumptionRestrictions(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList, baseName, baseID)
|
||||||
|
}
|
||||||
|
|
||||||
return identification
|
return identification
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildCreditAssessment 组装 creditAssessment,包含客户类型与异常时间段
|
// buildCreditAssessment 组装 creditAssessment,包含客户类型与异常时间段
|
||||||
func buildCreditAssessment(ctx *sourceContext) creditAssessment {
|
func buildCreditAssessment(ctx context.Context, sourceCtx *sourceContext) creditAssessment {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
|
|
||||||
assessment := creditAssessment{
|
assessment := creditAssessment{
|
||||||
Title: "信贷评估产品",
|
Title: "信贷评估产品",
|
||||||
LoanIntentionByCustomerType: assessmentSection[loanIntentionRecord]{
|
LoanIntentionByCustomerType: assessmentSection[loanIntentionRecord]{
|
||||||
@@ -748,8 +885,12 @@ func buildCreditAssessment(ctx *sourceContext) creditAssessment {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics := extractApplyLoanMetrics(ctx)
|
// 兼容处理:安全提取指标
|
||||||
|
metrics := extractApplyLoanMetrics(sourceCtx)
|
||||||
if len(metrics) == 0 {
|
if len(metrics) == 0 {
|
||||||
|
log.Debug("未获取到借贷指标数据,返回空的信贷评估",
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return assessment
|
return assessment
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,7 +995,9 @@ func buildCreditAssessment(ctx *sourceContext) creditAssessment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildLeasingRiskAssessment 组装 leasingRiskAssessment 中的 3C 多头信息
|
// buildLeasingRiskAssessment 组装 leasingRiskAssessment 中的 3C 多头信息
|
||||||
func buildLeasingRiskAssessment(ctx *sourceContext) leasingRiskAssessment {
|
func buildLeasingRiskAssessment(ctx context.Context, sourceCtx *sourceContext) leasingRiskAssessment {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
|
|
||||||
assessment := leasingRiskAssessment{
|
assessment := leasingRiskAssessment{
|
||||||
Title: "租赁风险评估产品",
|
Title: "租赁风险评估产品",
|
||||||
MultiLender3C: assessmentSection[multiLenderRecord]{
|
MultiLender3C: assessmentSection[multiLenderRecord]{
|
||||||
@@ -863,11 +1006,15 @@ func buildLeasingRiskAssessment(ctx *sourceContext) leasingRiskAssessment {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.ContentsData == nil || len(ctx.ContentsData.Contents) == 0 {
|
// 兼容处理:安全访问ContentsData
|
||||||
|
if sourceCtx == nil || sourceCtx.ContentsData == nil || len(sourceCtx.ContentsData.Contents) == 0 {
|
||||||
|
log.Debug("ContentsData为空或Contents为空,返回空的租赁风险评估",
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return assessment
|
return assessment
|
||||||
}
|
}
|
||||||
|
|
||||||
contents := ctx.ContentsData.Contents
|
contents := sourceCtx.ContentsData.Contents
|
||||||
|
|
||||||
// 消费金融机构指标,优先使用近12个月的机构数,其次退化到近一年内的其它统计
|
// 消费金融机构指标,优先使用近12个月的机构数,其次退化到近一年内的其它统计
|
||||||
consumerApplied := pickFirstInt(contents, "BH_A074", "BH_A065", "BH_A055")
|
consumerApplied := pickFirstInt(contents, "BH_A074", "BH_A065", "BH_A055")
|
||||||
@@ -935,13 +1082,18 @@ func buildLeasingRiskAssessment(ctx *sourceContext) leasingRiskAssessment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildComprehensiveAnalysis 汇总最终的文字结论(仅输出存在风险的要点)
|
// buildComprehensiveAnalysis 汇总最终的文字结论(仅输出存在风险的要点)
|
||||||
func buildComprehensiveAnalysis(ctx *sourceContext) []string {
|
func buildComprehensiveAnalysis(ctx context.Context, sourceCtx *sourceContext) []string {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
|
|
||||||
analysis := make([]string, 0, 8)
|
analysis := make([]string, 0, 8)
|
||||||
if ctx.BaseData == nil {
|
if sourceCtx == nil || sourceCtx.BaseData == nil {
|
||||||
|
log.Debug("BaseData为空,返回空的综合分析",
|
||||||
|
zap.String("api_code", "COMBHZY2"),
|
||||||
|
)
|
||||||
return analysis
|
return analysis
|
||||||
}
|
}
|
||||||
|
|
||||||
summary := buildReportSummary(ctx)
|
summary := buildReportSummary(ctx, sourceCtx)
|
||||||
highRiskDetected := false
|
highRiskDetected := false
|
||||||
mediumRiskDetected := false
|
mediumRiskDetected := false
|
||||||
|
|
||||||
@@ -957,32 +1109,33 @@ func buildComprehensiveAnalysis(ctx *sourceContext) []string {
|
|||||||
mediumRiskDetected = mediumRiskDetected || medium
|
mediumRiskDetected = mediumRiskDetected || medium
|
||||||
}
|
}
|
||||||
|
|
||||||
judicialBullet, judicialHigh, judicialMedium := buildJudicialBullet(ctx)
|
judicialBullet, judicialHigh, judicialMedium := buildJudicialBullet(sourceCtx)
|
||||||
if judicialBullet != "" {
|
if judicialBullet != "" {
|
||||||
analysis = append(analysis, judicialBullet)
|
analysis = append(analysis, judicialBullet)
|
||||||
highRiskDetected = highRiskDetected || judicialHigh
|
highRiskDetected = highRiskDetected || judicialHigh
|
||||||
mediumRiskDetected = mediumRiskDetected || judicialMedium
|
mediumRiskDetected = mediumRiskDetected || judicialMedium
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg, high, medium := buildLoanAssessmentBullet(ctx); msg != "" {
|
if msg, high, medium := buildLoanAssessmentBullet(ctx, sourceCtx); msg != "" {
|
||||||
analysis = append(analysis, msg)
|
analysis = append(analysis, msg)
|
||||||
highRiskDetected = highRiskDetected || high
|
highRiskDetected = highRiskDetected || high
|
||||||
mediumRiskDetected = mediumRiskDetected || medium
|
mediumRiskDetected = mediumRiskDetected || medium
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg, high, medium := buildOtherRiskBullet(ctx, judicialBullet != ""); msg != "" {
|
if msg, high, medium := buildOtherRiskBullet(sourceCtx, judicialBullet != ""); msg != "" {
|
||||||
analysis = append(analysis, msg)
|
analysis = append(analysis, msg)
|
||||||
highRiskDetected = highRiskDetected || high
|
highRiskDetected = highRiskDetected || high
|
||||||
mediumRiskDetected = mediumRiskDetected || medium
|
mediumRiskDetected = mediumRiskDetected || medium
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg, high, medium := buildMultiLenderBullet(ctx); msg != "" {
|
if msg, high, medium := buildMultiLenderBullet(ctx, sourceCtx); msg != "" {
|
||||||
analysis = append(analysis, msg)
|
analysis = append(analysis, msg)
|
||||||
highRiskDetected = highRiskDetected || high
|
highRiskDetected = highRiskDetected || high
|
||||||
mediumRiskDetected = mediumRiskDetected || medium
|
mediumRiskDetected = mediumRiskDetected || medium
|
||||||
}
|
}
|
||||||
|
|
||||||
risk := ctx.BaseData.RiskWarning
|
// 兼容处理:安全访问RiskWarning
|
||||||
|
risk := sourceCtx.BaseData.RiskWarning
|
||||||
if hasHighRiskHit(risk) {
|
if hasHighRiskHit(risk) {
|
||||||
highRiskDetected = true
|
highRiskDetected = true
|
||||||
} else if hasMediumRiskHit(risk) {
|
} else if hasMediumRiskHit(risk) {
|
||||||
@@ -999,7 +1152,7 @@ func buildComprehensiveAnalysis(ctx *sourceContext) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildReportFooter 填充数据来源与免责声明
|
// buildReportFooter 填充数据来源与免责声明
|
||||||
func buildReportFooter(ctx *sourceContext) reportFooter {
|
func buildReportFooter(ctx context.Context, sourceCtx *sourceContext) reportFooter {
|
||||||
return reportFooter{
|
return reportFooter{
|
||||||
DataSource: "天远数据报告",
|
DataSource: "天远数据报告",
|
||||||
GenerationTime: time.Now().Format("2006-01-02"),
|
GenerationTime: time.Now().Format("2006-01-02"),
|
||||||
@@ -1109,12 +1262,13 @@ func mapRiskFlag(flag int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// gatherOtherRiskDetails 汇总其它风险命中项的说明文本
|
// gatherOtherRiskDetails 汇总其它风险命中项的说明文本
|
||||||
func gatherOtherRiskDetails(ctx *sourceContext) string {
|
func gatherOtherRiskDetails(sourceCtx *sourceContext) string {
|
||||||
if ctx.BaseData == nil {
|
if sourceCtx == nil || sourceCtx.BaseData == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
hits := make([]string, 0, 4)
|
hits := make([]string, 0, 4)
|
||||||
risk := ctx.BaseData.RiskWarning
|
// 兼容处理:安全访问RiskWarning
|
||||||
|
risk := sourceCtx.BaseData.RiskWarning
|
||||||
if risk.IsAntiFraudInfo > 0 {
|
if risk.IsAntiFraudInfo > 0 {
|
||||||
hits = append(hits, "涉赌涉诈风险")
|
hits = append(hits, "涉赌涉诈风险")
|
||||||
}
|
}
|
||||||
@@ -1145,12 +1299,13 @@ func gatherOtherRiskDetails(ctx *sourceContext) string {
|
|||||||
if risk.VeryFrequentRentalApplications > 0 {
|
if risk.VeryFrequentRentalApplications > 0 {
|
||||||
hits = append(hits, "租赁机构申请次数极多")
|
hits = append(hits, "租赁机构申请次数极多")
|
||||||
}
|
}
|
||||||
if ctx.JudicialData != nil {
|
// 兼容处理:安全访问JudicialData
|
||||||
stat := ctx.JudicialData.JudicialData.LawsuitStat
|
if sourceCtx != nil && sourceCtx.JudicialData != nil {
|
||||||
totalCase := len(stat.Civil.Cases) + len(stat.Criminal.Cases) + len(stat.Administrative.Cases) + len(stat.Preservation.Cases) + len(stat.Bankrupt.Cases)
|
stat := sourceCtx.JudicialData.JudicialData.LawsuitStat
|
||||||
totalExecution := len(stat.Implement.Cases)
|
totalCase := safeLen(stat.Civil.Cases) + safeLen(stat.Criminal.Cases) + safeLen(stat.Administrative.Cases) + safeLen(stat.Preservation.Cases) + safeLen(stat.Bankrupt.Cases)
|
||||||
totalDishonest := len(ctx.JudicialData.JudicialData.BreachCaseList)
|
totalExecution := safeLen(stat.Implement.Cases)
|
||||||
totalRestriction := len(ctx.JudicialData.JudicialData.ConsumptionRestrictionList)
|
totalDishonest := safeLen(sourceCtx.JudicialData.JudicialData.BreachCaseList)
|
||||||
|
totalRestriction := safeLen(sourceCtx.JudicialData.JudicialData.ConsumptionRestrictionList)
|
||||||
if totalCase > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
if totalCase > 0 || totalExecution > 0 || totalDishonest > 0 || totalRestriction > 0 {
|
||||||
hits = append(hits, "存在司法风险记录")
|
hits = append(hits, "存在司法风险记录")
|
||||||
}
|
}
|
||||||
@@ -1266,8 +1421,8 @@ func buildJudicialBullet(ctx *sourceContext) (string, bool, bool) {
|
|||||||
return sentence, true, false
|
return sentence, true, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLoanAssessmentBullet(ctx *sourceContext) (string, bool, bool) {
|
func buildLoanAssessmentBullet(ctx context.Context, sourceCtx *sourceContext) (string, bool, bool) {
|
||||||
assessment := buildCreditAssessment(ctx)
|
assessment := buildCreditAssessment(ctx, sourceCtx)
|
||||||
records := assessment.LoanIntentionByCustomerType.Records
|
records := assessment.LoanIntentionByCustomerType.Records
|
||||||
if len(records) == 0 {
|
if len(records) == 0 {
|
||||||
return "", false, false
|
return "", false, false
|
||||||
@@ -1311,8 +1466,8 @@ func buildLoanAssessmentBullet(ctx *sourceContext) (string, bool, bool) {
|
|||||||
return sentence, high, medium
|
return sentence, high, medium
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMultiLenderBullet(ctx *sourceContext) (string, bool, bool) {
|
func buildMultiLenderBullet(ctx context.Context, sourceCtx *sourceContext) (string, bool, bool) {
|
||||||
assessment := buildCreditAssessment(ctx)
|
assessment := buildCreditAssessment(ctx, sourceCtx)
|
||||||
records := assessment.LoanIntentionAbnormalTimes.Records
|
records := assessment.LoanIntentionAbnormalTimes.Records
|
||||||
if len(records) == 0 {
|
if len(records) == 0 {
|
||||||
return "", false, false
|
return "", false, false
|
||||||
@@ -1596,12 +1751,13 @@ func addThousandsSeparator(value string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildLoanRiskResult 根据 riskWarning 命中情况生成借贷评估结果
|
// buildLoanRiskResult 根据 riskWarning 命中情况生成借贷评估结果
|
||||||
func buildLoanRiskResult(ctx *sourceContext) (string, string) {
|
func buildLoanRiskResult(sourceCtx *sourceContext) (string, string) {
|
||||||
if ctx.BaseData == nil {
|
if sourceCtx == nil || sourceCtx.BaseData == nil {
|
||||||
return "正常", ""
|
return "正常", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
risk := ctx.BaseData.RiskWarning
|
// 兼容处理:安全访问RiskWarning
|
||||||
|
risk := sourceCtx.BaseData.RiskWarning
|
||||||
hits := make([]string, 0, 3)
|
hits := make([]string, 0, 3)
|
||||||
|
|
||||||
if risk.HitHighRiskBankLastTwoYears > 0 {
|
if risk.HitHighRiskBankLastTwoYears > 0 {
|
||||||
@@ -1639,11 +1795,12 @@ func buildCaseDetails(parts []string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildElementVerificationResult 根据 riskWarning 的要素相关项生成结果
|
// buildElementVerificationResult 根据 riskWarning 的要素相关项生成结果
|
||||||
func buildElementVerificationResult(ctx *sourceContext) (string, string) {
|
func buildElementVerificationResult(sourceCtx *sourceContext) (string, string) {
|
||||||
if ctx.BaseData == nil {
|
if sourceCtx == nil || sourceCtx.BaseData == nil {
|
||||||
return "正常", ""
|
return "正常", ""
|
||||||
}
|
}
|
||||||
risk := ctx.BaseData.RiskWarning
|
// 兼容处理:安全访问RiskWarning
|
||||||
|
risk := sourceCtx.BaseData.RiskWarning
|
||||||
hits := make([]string, 0, 2)
|
hits := make([]string, 0, 2)
|
||||||
if risk.IdCardTwoElementMismatch > 0 {
|
if risk.IdCardTwoElementMismatch > 0 {
|
||||||
hits = append(hits, "身份证二要素不一致")
|
hits = append(hits, "身份证二要素不一致")
|
||||||
@@ -1658,11 +1815,12 @@ func buildElementVerificationResult(ctx *sourceContext) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildCarrierVerificationResult 根据 riskWarning 的运营商相关项生成结果
|
// buildCarrierVerificationResult 根据 riskWarning 的运营商相关项生成结果
|
||||||
func buildCarrierVerificationResult(ctx *sourceContext) (string, string) {
|
func buildCarrierVerificationResult(sourceCtx *sourceContext) (string, string) {
|
||||||
if ctx.BaseData == nil {
|
if sourceCtx == nil || sourceCtx.BaseData == nil {
|
||||||
return "正常", ""
|
return "正常", ""
|
||||||
}
|
}
|
||||||
risk := ctx.BaseData.RiskWarning
|
// 兼容处理:安全访问RiskWarning
|
||||||
|
risk := sourceCtx.BaseData.RiskWarning
|
||||||
hits := make([]string, 0, 4)
|
hits := make([]string, 0, 4)
|
||||||
if risk.ShortPhoneDuration > 0 {
|
if risk.ShortPhoneDuration > 0 {
|
||||||
hits = append(hits, "手机在网时长极短")
|
hits = append(hits, "手机在网时长极短")
|
||||||
@@ -1742,13 +1900,23 @@ func hasMediumRiskHit(r riskWarning) bool {
|
|||||||
r.MoreFrequentNonBankApplications > 0
|
r.MoreFrequentNonBankApplications > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractApplyLoanMetrics(ctx *sourceContext) map[string]int {
|
func extractApplyLoanMetrics(sourceCtx *sourceContext) map[string]int {
|
||||||
if ctx.RiskScreen == nil {
|
// 兼容处理:安全访问RiskScreen
|
||||||
|
if sourceCtx == nil || sourceCtx.RiskScreen == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, variable := range ctx.RiskScreen.RiskScreenV2.Variables {
|
// 兼容处理:安全访问Variables数组
|
||||||
|
if sourceCtx.RiskScreen.RiskScreenV2.Variables == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, variable := range sourceCtx.RiskScreen.RiskScreenV2.Variables {
|
||||||
if strings.EqualFold(variable.VariableName, "bairong_applyloan_extend") {
|
if strings.EqualFold(variable.VariableName, "bairong_applyloan_extend") {
|
||||||
|
// 兼容处理:安全访问VariableValue
|
||||||
|
if variable.VariableValue == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
results := make(map[string]int, len(variable.VariableValue))
|
results := make(map[string]int, len(variable.VariableValue))
|
||||||
for key, val := range variable.VariableValue {
|
for key, val := range variable.VariableValue {
|
||||||
results[key] = parseMetricValue(val)
|
results[key] = parseMetricValue(val)
|
||||||
@@ -1821,3 +1989,4 @@ func riskLevelFromStrictCount(count int) string {
|
|||||||
return "高风险"
|
return "高风险"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package comb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/shared/logger"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessCOMBWD01Request 处理 COMBWD01 组合包请求
|
||||||
|
// 将返回结构从数组改为以 api_code 为 key 的对象结构
|
||||||
|
func ProcessCOMBWD01Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
log := logger.GetGlobalLogger()
|
||||||
|
|
||||||
|
// 调用组合包服务处理请求
|
||||||
|
combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMBWD01")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("COMBWD01组合包服务调用失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", "COMBWD01"),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if combinedResult == nil {
|
||||||
|
log.Error("COMBWD01组合包响应为空",
|
||||||
|
zap.String("api_code", "COMBWD01"),
|
||||||
|
)
|
||||||
|
return nil, errors.New("组合包响应为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("COMBWD01组合包服务调用成功",
|
||||||
|
zap.Int("子产品数量", len(combinedResult.Responses)),
|
||||||
|
zap.String("api_code", "COMBWD01"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 将数组结构转换为对象结构
|
||||||
|
responsesMap := make(map[string]*ResponseItem)
|
||||||
|
for _, resp := range combinedResult.Responses {
|
||||||
|
item := &ResponseItem{
|
||||||
|
ApiCode: resp.ApiCode,
|
||||||
|
Success: resp.Success,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据成功/失败状态设置 data 和 error 字段
|
||||||
|
if resp.Success {
|
||||||
|
// 成功时:data 有值(可能为 nil),error 为 null
|
||||||
|
item.Data = resp.Data
|
||||||
|
item.Error = nil
|
||||||
|
} else {
|
||||||
|
// 失败时:data 为 null,error 有值
|
||||||
|
item.Data = nil
|
||||||
|
if resp.Error != "" {
|
||||||
|
item.Error = resp.Error
|
||||||
|
} else {
|
||||||
|
item.Error = "未知错误"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responsesMap[resp.ApiCode] = item
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建新的响应结构
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"responses": responsesMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化并返回
|
||||||
|
resultBytes, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("COMBWD01响应序列化失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("api_code", "COMBWD01"),
|
||||||
|
)
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseItem 响应项结构
|
||||||
|
type ResponseItem struct {
|
||||||
|
ApiCode string `json:"api_code"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
Error interface{} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
// ProcessDWBG8B4DRequest DWBG8B4D API处理方法 - 谛听多维报告
|
// ProcessDWBG8B4DRequest DWBG8B4D API处理方法 - 谛听多维报告
|
||||||
func ProcessDWBG8B4DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
func ProcessDWBG8B4DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
var paramsDto dto.DWBG8B4DReq
|
var paramsDto dto.DWBG8B4DReq
|
||||||
|
|
||||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/yushan"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessFLXG0687Request FLXG0687 API处理方法
|
// ProcessFLXG0687Request FLXG0687 API处理方法
|
||||||
@@ -21,15 +21,14 @@ func ProcessFLXG0687Request(ctx context.Context, params []byte, deps *processors
|
|||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"keyWord": paramsDto.IDCard,
|
"keyWord": paramsDto.IDCard,
|
||||||
"type": 3,
|
"type": 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes, err := deps.YushanService.CallAPI(ctx, "RIS031", reqData)
|
respBytes, err := deps.YushanService.CallAPI(ctx, "RIS031", reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, westdex.ErrDatasource) {
|
if errors.Is(err, yushan.ErrDatasource) {
|
||||||
return nil, errors.Join(processors.ErrDatasource, err)
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ func ProcessFLXG0V3Bequest(ctx context.Context, params []byte, deps *processors.
|
|||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"name": encryptedName,
|
"name": encryptedName,
|
||||||
"id_card": encryptedIDCard,
|
"id_card": encryptedIDCard,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||||
|
}
|
||||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package flxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessFLXG3A9BRequest FLXG3A9B API处理方法 - 法院被执行人限高版
|
||||||
|
func ProcessFLXG3A9BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.FLXG3A9BReq
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI045", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessFLXG5876Request FLXG5876 API处理方法
|
// ProcessFLXG5876Request FLXG5876 易诉人识别API处理方法
|
||||||
func ProcessFLXG5876Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
func ProcessFLXG5876Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
var paramsDto dto.FLXG5876Req
|
var paramsDto dto.FLXG5876Req
|
||||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||||
|
}
|
||||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ func ProcessFLXG7E8FRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||||
|
}
|
||||||
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"name": paramsDto.Name,
|
"name": paramsDto.Name,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessFLXG970FRequest FLXG970F API处理方法
|
// ProcessFLXG970FRequest FLXG970F 风险人员核验API处理方法
|
||||||
func ProcessFLXG970FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
func ProcessFLXG970FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
var paramsDto dto.FLXG970FReq
|
var paramsDto dto.FLXG970FReq
|
||||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
@@ -33,8 +33,8 @@ func ProcessFLXG970FRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"name": encryptedName,
|
"name": encryptedName,
|
||||||
"cardNo": encryptedIDCard,
|
"cardNo": encryptedIDCard,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/yushan"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessFLXGBC21Request FLXGbc21 API处理方法
|
// ProcessFLXGBC21Request FLXGbc21 API处理方法
|
||||||
@@ -21,14 +21,13 @@ func ProcessFLXGBC21Request(ctx context.Context, params []byte, deps *processors
|
|||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"mobile": paramsDto.MobileNo,
|
"mobile": paramsDto.MobileNo,
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes, err := deps.YushanService.CallAPI(ctx, "MOB032", reqData)
|
respBytes, err := deps.YushanService.CallAPI(ctx, "MOB032", reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, westdex.ErrDatasource) {
|
if errors.Is(err, yushan.ErrDatasource) {
|
||||||
return nil, errors.Join(processors.ErrDatasource, err)
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ func ProcessFLXGCA3DRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||||
|
}
|
||||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
@@ -33,7 +35,7 @@ func ProcessFLXGCA3DRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"name": encryptedName,
|
"name": encryptedName,
|
||||||
"id_card": encryptedIDCard,
|
"id_card": encryptedIDCard,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ func ProcessFLXGDEA9Request(ctx context.Context, params []byte, deps *processors
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
|
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||||
|
}
|
||||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package flxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessFLXGK5D2Request FLXGK5D2 API处理方法 - 法院被执行人高级版
|
||||||
|
func ProcessFLXGK5D2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.FLXGK5D2Req
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI046", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// ProcessIVYZ0B03Request IVYZ0B03 API处理方法
|
// ProcessIVYZ0B03Request IVYZ0B03 API处理方法
|
||||||
func ProcessIVYZ0B03Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
func ProcessIVYZ0B03Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
var paramsDto dto.IVYZ0b03Req
|
var paramsDto dto.IVYZ0B03Req
|
||||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ2B2TRequest IVYZ2B2T API处理方法 能力资质核验(学历)
|
||||||
|
func ProcessIVYZ2B2TRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
|
||||||
|
var paramsDto dto.IVYZ2B2TReq
|
||||||
|
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.WestDexService.Encrypt(paramsDto.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
encryptedQueryReasonId, err := deps.WestDexService.Encrypt(strconv.FormatInt(paramsDto.QueryReasonId, 10))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"idCard": encryptedIDCard,
|
||||||
|
"name": encryptedName,
|
||||||
|
"queryReasonId": encryptedQueryReasonId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.WestDexService.CallAPI(ctx, "G11JX01", reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, westdex.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
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/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ2C1PRequest IVYZ2C1P API处理方法 - 风控黑名单
|
||||||
|
func ProcessIVYZ2C1PRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZ2C1PReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"name": encryptedName,
|
||||||
|
"idCard": encryptedIDCard,
|
||||||
|
"authorized": paramsDto.Authorized,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI037", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
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/westdex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ5A9ORequest IVYZ5A9O API处理方法 全国⾃然⼈⻛险评估评分模型
|
||||||
|
func ProcessIVYZ5A9ORequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
|
||||||
|
var paramsDto dto.IVYZ5A9OReq
|
||||||
|
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.WestDexService.Encrypt(paramsDto.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
encryptedAuthAuthorizeFileCode, err := deps.WestDexService.Encrypt(paramsDto.AuthAuthorizeFileCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"idcard": encryptedIDCard,
|
||||||
|
"name": encryptedName,
|
||||||
|
"auth_authorizeFileCode": encryptedAuthAuthorizeFileCode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.WestDexService.CallAPI(ctx, "G01SC01", reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, westdex.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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/xingwei"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ6M8PRequest IVYZ6M8P 职业资格证书API处理方法
|
||||||
|
func ProcessIVYZ6M8PRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZ6M8PReq
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用行为数据API,使用指定的project_id
|
||||||
|
projectID := "CDJ-1147725836315455488"
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -38,13 +38,27 @@ func ProcessIVYZ81NCRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes, err := deps.WestDexService.CallAPI(ctx, "G09XM02", reqData)
|
const maxRetries = 5
|
||||||
if err != nil {
|
var respBytes []byte
|
||||||
if errors.Is(err, westdex.ErrDatasource) {
|
|
||||||
return nil, errors.Join(processors.ErrDatasource, err)
|
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||||
} else {
|
var err error
|
||||||
|
respBytes, err = deps.WestDexService.CallAPI(ctx, "G09XM02", reqData)
|
||||||
|
if err == nil {
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是数据源异常,直接返回错误
|
||||||
|
if !errors.Is(err, westdex.ErrDatasource) {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是最后一次尝试,返回错误
|
||||||
|
if attempt == maxRetries {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即重试,不等待
|
||||||
}
|
}
|
||||||
|
|
||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ9K2LRequest IVYZ9K2L API处理方法 - 身份认证三要素(人脸图像版)
|
||||||
|
func ProcessIVYZ9K2LRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZ9K2LReq
|
||||||
|
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.WestDexService.Encrypt(paramsDto.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密身份证号
|
||||||
|
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成时间戳(毫秒)
|
||||||
|
timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||||
|
|
||||||
|
// 获取自定义编号(从 WestDexService 配置中获取 secret_id)
|
||||||
|
config := deps.WestDexService.GetConfig()
|
||||||
|
customNumber := config.SecretID
|
||||||
|
|
||||||
|
// 构建请求数据
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"timeStamp": timestamp,
|
||||||
|
"customNumber": customNumber,
|
||||||
|
"xM": encryptedName,
|
||||||
|
"gMSFZHM": encryptedIDCard,
|
||||||
|
"photoData": paramsDto.PhotoData,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.WestDexService.CallAPI(ctx, "idCardThreeElements", reqData)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, westdex.ErrDatasource):
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
case errors.Is(err, westdex.ErrSystem):
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
default:
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用gjson提取authResult字段
|
||||||
|
// 尝试多个可能的路径
|
||||||
|
var authResult string
|
||||||
|
paths := []string{
|
||||||
|
"WEST00037.WEST00038.authResult",
|
||||||
|
"WEST00036.WEST00037.WEST00038.authResult",
|
||||||
|
"authResult",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range paths {
|
||||||
|
result := gjson.GetBytes(respBytes, path)
|
||||||
|
if result.Exists() {
|
||||||
|
authResult = result.String()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找不到authResult,返回ErrDatasource
|
||||||
|
if authResult == "" {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, errors.New("响应中未找到authResult字段"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建返回格式 {result: XXXX}
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"result": authResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBytes, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
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/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZP2Q6Request IVYZP2Q6 API处理方法 - 身份认证二要素
|
||||||
|
func ProcessIVYZP2Q6Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZP2Q6Req
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"name": encryptedName,
|
||||||
|
"idCard": encryptedIDCard,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI011", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
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/yushan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQ0B6YRequest JRZQ0B6Y 银行卡黑名单查询V1API处理方法
|
||||||
|
func ProcessJRZQ0B6YRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQ0B6YReq
|
||||||
|
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{}{
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
"cardld": paramsDto.BankCard,
|
||||||
|
"cardNo": paramsDto.IDCard,
|
||||||
|
"mobile": paramsDto.MobileNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.YushanService.CallAPI(ctx, "FIN019", reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, yushan.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessjrzqW4XRequest JRZQ1W4XAPI处理方法 - 全景档案
|
||||||
|
func ProcessJRZQ1W4XRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQ1W4XReq
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI022", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQ3AG6Request JRZQ3AG6 轻松查公积API处理方法
|
||||||
|
func ProcessJRZQ3AG6Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQ3AG6Req
|
||||||
|
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,
|
||||||
|
"return_url": paramsDto.ReturnURL,
|
||||||
|
"authorization_url": paramsDto.AuthorizationURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI108", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -37,9 +37,9 @@ func ProcessJRZQ3C7BRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
}
|
}
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"name": encryptedName,
|
"name": encryptedName,
|
||||||
"idCard": encryptedIDCard,
|
"idCard": encryptedIDCard,
|
||||||
"phone": encryptedMobileNo,
|
"phone": encryptedMobileNo,
|
||||||
"authorized": paramsDto.Authorized,
|
"authorized": paramsDto.Authorized,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQ3c9RRequest JRZQ3c9R API处理方法 - 支付行为指数
|
||||||
|
func ProcessJRZQ3C9RRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQ3C9RReq
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI036", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQ3P01Request JRZQ3P01 天远风控决策API处理方法
|
||||||
|
func ProcessJRZQ3P01Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQ3P01Req
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"name": encryptedName,
|
||||||
|
"idCard": encryptedIDCard,
|
||||||
|
"authorized": paramsDto.Authorized,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI109", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||||
@@ -37,9 +36,9 @@ func ProcessJRZQ4B6CRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
}
|
}
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"name": encryptedName,
|
"name": encryptedName,
|
||||||
"idCard": encryptedIDCard,
|
"idCard": encryptedIDCard,
|
||||||
"phone": encryptedMobileNo,
|
"phone": encryptedMobileNo,
|
||||||
"authorized": paramsDto.Authorized,
|
"authorized": paramsDto.Authorized,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQ8F7CRequest JRZQ8F7C API处理方法
|
||||||
|
func ProcessJRZQ8F7CRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQ8F7CReq
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI047", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
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/yushan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessJRZQ9A1WRequest JRZQ9A1W 银行卡鉴权V1API处理方法
|
||||||
|
func ProcessJRZQ9A1WRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.JRZQ9A1WReq
|
||||||
|
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{}{
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
"cardId": paramsDto.BankCard,
|
||||||
|
"cardNo": paramsDto.IDCard,
|
||||||
|
"phone": paramsDto.MobileNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.YushanService.CallAPI(ctx, "PCB145", reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, yushan.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -24,8 +24,8 @@ func ProcessJRZQ9E2ARequest(ctx context.Context, params []byte, deps *processors
|
|||||||
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"phoneNumber": paramsDto.MobileNo,
|
"phoneNumber": paramsDto.MobileNo,
|
||||||
"idCardNum": paramsDto.IDCard,
|
"idCardNum": paramsDto.IDCard,
|
||||||
"name": paramsDto.Name,
|
"name": paramsDto.Name,
|
||||||
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
|
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// ProcessJRZQDCBERequest JRZQDCBE API处理方法
|
// ProcessJRZQDCBERequest JRZQDCBE API处理方法
|
||||||
func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
var paramsDto dto.JRZQDBCEReq
|
var paramsDto dto.JRZQDCBEReq
|
||||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
@@ -43,10 +43,10 @@ func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors
|
|||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"name": encryptedName,
|
"name": encryptedName,
|
||||||
"idcard": encryptedIDCard,
|
"idcard": encryptedIDCard,
|
||||||
"mobile": encryptedMobileNo,
|
"mobile": encryptedMobileNo,
|
||||||
"acc_no": encryptedBankCard,
|
"acc_no": encryptedBankCard,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func ProcessQCXG6B4ERequest(ctx context.Context, params []byte, deps *processors
|
|||||||
}
|
}
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"vin": paramsDto.VINCode,
|
"vin": paramsDto.VINCode,
|
||||||
"authorized": paramsDto.Authorized,
|
"authorized": paramsDto.Authorized,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,4 +43,3 @@ func ProcessQCXG6B4ERequest(ctx context.Context, params []byte, deps *processors
|
|||||||
|
|
||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/yushan"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessQCXG7A2BRequest QCXG7A2B API处理方法
|
// ProcessQCXG7A2BRequest QCXG7A2B API处理方法
|
||||||
@@ -27,7 +27,7 @@ func ProcessQCXG7A2BRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
|
|
||||||
respBytes, err := deps.YushanService.CallAPI(ctx, "CAR061", reqData)
|
respBytes, err := deps.YushanService.CallAPI(ctx, "CAR061", reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, westdex.ErrDatasource) {
|
if errors.Is(err, yushan.ErrDatasource) {
|
||||||
return nil, errors.Join(processors.ErrDatasource, err)
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessQYGL23T7Request QYGL23T7 API处理方法 - 企业三要素验证
|
// ProcessQYGL23T7Request QYGL23T7 API处理方法 - 企业四要素验证
|
||||||
func ProcessQYGL23T7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
func ProcessQYGL23T7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
var paramsDto dto.QYGL23T7Req
|
var paramsDto dto.QYGL23T7Req
|
||||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ func ProcessQYGL2ACDRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"entname": encryptedEntName,
|
"name": encryptedEntName,
|
||||||
"realname": encryptedLegalPerson,
|
"oper_name": encryptedLegalPerson,
|
||||||
"idcard": encryptedEntCode,
|
"keyword": encryptedEntCode,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ func ProcessQYGL2B5CRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
|
|
||||||
// 两选一校验:EntName 和 EntCode 至少传一个
|
// 两选一校验:EntName 和 EntCode 至少传一个
|
||||||
var keyword string
|
var keyword string
|
||||||
if paramsDto.EntName != "" && paramsDto.EntCode != "" {
|
|
||||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("企业名称和企业统一信用代码只能传其中一个"))
|
|
||||||
}
|
|
||||||
if paramsDto.EntName == "" && paramsDto.EntCode == "" {
|
if paramsDto.EntName == "" && paramsDto.EntCode == "" {
|
||||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("必须提供企业名称或企业统一信用代码中的其中一个"))
|
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("必须提供企业名称或企业统一信用代码中的其中一个"))
|
||||||
}
|
}
|
||||||
@@ -39,7 +36,7 @@ func ProcessQYGL2B5CRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
}
|
}
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"keyword": keyword,
|
"keyword": keyword,
|
||||||
"authorized": paramsDto.Authorized,
|
"authorized": paramsDto.Authorized,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,4 +57,3 @@ func ProcessQYGL2B5CRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
|
|
||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Processqygl2s0wRequest QYGL2S0W API处理方法 - 失信被执行企业个人查询
|
||||||
|
func ProcessQYGL2S0WRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QYGL2S0WReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证逻辑
|
||||||
|
var nameValue string
|
||||||
|
if paramsDto.Type == "per" {
|
||||||
|
// 个人查询:idCardNum 必填
|
||||||
|
nameValue = paramsDto.Name
|
||||||
|
if paramsDto.IDCard == "" {
|
||||||
|
fmt.Print("个人身份证件号不能为空")
|
||||||
|
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("当失信被执行人类型为个人时,身份证件号不能为空"))
|
||||||
|
}
|
||||||
|
} else if paramsDto.Type == "ent" {
|
||||||
|
// 企业查询:name 和 entMark 两者必填其一
|
||||||
|
nameValue = paramsDto.EntName
|
||||||
|
if paramsDto.EntName == "" && paramsDto.EntCode == "" {
|
||||||
|
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("当查询为企业时,企业名称和企业标识统一代码注册号两者必填其一"))
|
||||||
|
} // 确定使用哪个值作为 name
|
||||||
|
if paramsDto.EntName != "" {
|
||||||
|
nameValue = paramsDto.EntName
|
||||||
|
} else {
|
||||||
|
nameValue = paramsDto.EntCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("dto2s0w", paramsDto)
|
||||||
|
// 构建请求数据(不传的参数也需要添加,值为空字符串)
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"idCardNum": paramsDto.IDCard,
|
||||||
|
"name": nameValue,
|
||||||
|
"entMark": paramsDto.EntCode,
|
||||||
|
"type": paramsDto.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用行为数据API,使用指定的project_id
|
||||||
|
projectID := "CDJ-1079244717102657536"
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -24,18 +24,18 @@ func ProcessQYGL4B2ERequest(ctx context.Context, params []byte, deps *processors
|
|||||||
// 设置默认值
|
// 设置默认值
|
||||||
pageSize := paramsDto.PageSize
|
pageSize := paramsDto.PageSize
|
||||||
if pageSize == 0 {
|
if pageSize == 0 {
|
||||||
pageSize = 20
|
pageSize = int64(20)
|
||||||
}
|
}
|
||||||
pageNum := paramsDto.PageNum
|
pageNum := paramsDto.PageNum
|
||||||
if pageNum == 0 {
|
if pageNum == 0 {
|
||||||
pageNum = 1
|
pageNum = int64(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建API调用参数
|
// 构建API调用参数
|
||||||
apiParams := map[string]string{
|
apiParams := map[string]string{
|
||||||
"keyword": paramsDto.EntCode,
|
"keyword": paramsDto.EntCode,
|
||||||
"pageSize": strconv.Itoa(pageSize),
|
"pageSize": strconv.FormatInt(pageSize, 10),
|
||||||
"pageNum": strconv.Itoa(pageNum),
|
"pageNum": strconv.FormatInt(pageNum, 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用天眼查API - 税收违法
|
// 调用天眼查API - 税收违法
|
||||||
|
|||||||
@@ -24,18 +24,18 @@ func ProcessQYGL5A3CRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
// 设置默认值
|
// 设置默认值
|
||||||
pageSize := paramsDto.PageSize
|
pageSize := paramsDto.PageSize
|
||||||
if pageSize == 0 {
|
if pageSize == 0 {
|
||||||
pageSize = 20
|
pageSize = int64(20)
|
||||||
}
|
}
|
||||||
pageNum := paramsDto.PageNum
|
pageNum := paramsDto.PageNum
|
||||||
if pageNum == 0 {
|
if pageNum == 0 {
|
||||||
pageNum = 1
|
pageNum = int64(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建API调用参数
|
// 构建API调用参数
|
||||||
apiParams := map[string]string{
|
apiParams := map[string]string{
|
||||||
"keyword": paramsDto.EntCode,
|
"keyword": paramsDto.EntCode,
|
||||||
"pageSize": strconv.Itoa(pageSize),
|
"pageSize": strconv.FormatInt(pageSize, 10),
|
||||||
"pageNum": strconv.Itoa(pageNum),
|
"pageNum": strconv.FormatInt(pageNum, 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用天眼查API - 对外投资历史
|
// 调用天眼查API - 对外投资历史
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Processqygl5a9tRequest QYGL5A9T API处理方法 - 全国企业各类工商风险统计数量查询
|
||||||
|
func ProcessQYGL5A9TRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
|
||||||
|
var paramsDto dto.QYGL5A9TReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 两选一校验:EntName 和 EntCode 至少传一个
|
||||||
|
var keyword string
|
||||||
|
if paramsDto.EntName == "" && paramsDto.EntCode == "" {
|
||||||
|
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("必须提供企业名称或企业统一信用代码中的其中一个"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确定使用哪个值作为 keyword
|
||||||
|
if paramsDto.EntName != "" {
|
||||||
|
keyword = paramsDto.EntName
|
||||||
|
} else {
|
||||||
|
keyword = paramsDto.EntCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求数据,
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"nameCode": keyword,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用行为数据API,使用指定的project_id
|
||||||
|
projectID := "CDJ-1054665422426533888"
|
||||||
|
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,147 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQYGL5CMPRequest QYGL5CMP API处理方法 - 企业五要素验证
|
||||||
|
func ProcessQYGL5CMPRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QYGL5CMPReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建API调用参数
|
||||||
|
apiParams := map[string]string{
|
||||||
|
"code": paramsDto.EntCode,
|
||||||
|
"name": paramsDto.EntName,
|
||||||
|
"legalPersonName": paramsDto.LegalPerson,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用天眼查API - 使用通用的CallAPI方法
|
||||||
|
response, err := deps.TianYanChaService.CallAPI(ctx, "VerifyThreeElements", apiParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, convertTianYanChaError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查天眼查API调用是否成功
|
||||||
|
if !response.Success {
|
||||||
|
// 天眼查API调用失败,返回企业信息校验不通过
|
||||||
|
return createStatusResponsess(1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析天眼查响应数据
|
||||||
|
if response.Data == nil {
|
||||||
|
// 天眼查响应数据为空,返回企业信息校验不通过
|
||||||
|
return createStatusResponsess(1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将response.Data转换为JSON字符串,然后使用gjson解析
|
||||||
|
dataBytes, err := json.Marshal(response.Data)
|
||||||
|
if err != nil {
|
||||||
|
// 数据序列化失败,返回企业信息校验不通过
|
||||||
|
return createStatusResponsess(1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用gjson解析嵌套的data.result.data字段
|
||||||
|
result := gjson.GetBytes(dataBytes, "result")
|
||||||
|
if !result.Exists() {
|
||||||
|
// 字段不存在,返回企业信息校验不通过
|
||||||
|
return createStatusResponsess(1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查data.result.data是否等于1
|
||||||
|
if result.Int() != 1 {
|
||||||
|
// 不等于1,返回企业信息校验不通过
|
||||||
|
return createStatusResponsess(1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 天眼查三要素验证通过,继续调用星维身份证三要素验证
|
||||||
|
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.LegalPerson,
|
||||||
|
"idCardNum": paramsDto.IDCard,
|
||||||
|
"phoneNumber": paramsDto.MobileNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用行为数据API,使用指定的project_id
|
||||||
|
projectID := "CDJ-1100244702166183936"
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析星维API返回的数据
|
||||||
|
var xingweiData map[string]interface{}
|
||||||
|
if err := json.Unmarshal(respBytes, &xingweiData); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("解析星维API响应失败: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建天眼查API返回的数据结构
|
||||||
|
tianYanChaData := map[string]interface{}{
|
||||||
|
"success": response.Success,
|
||||||
|
"message": response.Message,
|
||||||
|
"data": response.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析status响应(将JSON字节解析为对象)
|
||||||
|
statusBytes := createStatusResponsess(0) // 验证通过,status为0
|
||||||
|
var statusData map[string]interface{}
|
||||||
|
if err := json.Unmarshal(statusBytes, &statusData); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("解析status响应失败: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并两个API的返回数据
|
||||||
|
mergedData := map[string]interface{}{
|
||||||
|
"Personal Information": xingweiData, // 星维API返回的数据
|
||||||
|
"Enterprise Information": tianYanChaData, // 天眼查API返回的数据
|
||||||
|
"status": statusData, // 解析后的status对象
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将合并后的数据序列化为JSON
|
||||||
|
mergedBytes, err := json.Marshal(mergedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("合并数据序列化失败: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedBytes, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// createStatusResponsess 创建状态响应
|
||||||
|
func createStatusResponsess(status int) []byte {
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
}
|
||||||
|
respBytes, _ := json.Marshal(response)
|
||||||
|
return respBytes
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
@@ -20,24 +21,16 @@ func ProcessQYGL5F6ARequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
fmt.Println("paramsDto", paramsDto)
|
||||||
|
|
||||||
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"idCardNum": paramsDto.IDCard,
|
"idCardNum": paramsDto.IDCard,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果传了身份证,则添加到请求数据中
|
|
||||||
if paramsDto.MobileNo != "" {
|
|
||||||
reqData["phoneNumber"] = paramsDto.MobileNo
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果传了企业统一信用代码,则添加到请求数据中
|
|
||||||
if paramsDto.EntCode != "" {
|
|
||||||
reqData["ucc"] = paramsDto.EntCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用行为数据API,使用指定的project_id
|
// 调用行为数据API,使用指定的project_id
|
||||||
projectID := "CDJ-1101695397213958144"
|
projectID := "CDJ-1101695397213958144"
|
||||||
|
fmt.Println("reqData", reqData)
|
||||||
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, xingwei.ErrNotFound) {
|
if errors.Is(err, xingwei.ErrNotFound) {
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQYGL6S1BRequest QYGL6S1B API处理方法 - 董监高司法综合信息核验
|
||||||
|
|
||||||
|
func ProcessQYGL6S1BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QYGL6S1BReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedIdCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建API调用参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"idCard": encryptedIdCard,
|
||||||
|
"authorized": paramsDto.Authorized,
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI043", 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应数据转换为JSON字节
|
||||||
|
respBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
@@ -16,6 +17,7 @@ func ProcessQYGL7C1ARequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
|
fmt.Println("paramsDto", paramsDto)
|
||||||
|
|
||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
@@ -24,18 +26,18 @@ func ProcessQYGL7C1ARequest(ctx context.Context, params []byte, deps *processors
|
|||||||
// 设置默认值
|
// 设置默认值
|
||||||
pageSize := paramsDto.PageSize
|
pageSize := paramsDto.PageSize
|
||||||
if pageSize == 0 {
|
if pageSize == 0 {
|
||||||
pageSize = 20
|
pageSize = int64(20)
|
||||||
}
|
}
|
||||||
pageNum := paramsDto.PageNum
|
pageNum := paramsDto.PageNum
|
||||||
if pageNum == 0 {
|
if pageNum == 0 {
|
||||||
pageNum = 1
|
pageNum = int64(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建API调用参数
|
// 构建API调用参数
|
||||||
apiParams := map[string]string{
|
apiParams := map[string]string{
|
||||||
"keyword": paramsDto.EntCode,
|
"keyword": paramsDto.EntCode,
|
||||||
"pageSize": strconv.Itoa(pageSize),
|
"pageSize": strconv.FormatInt(pageSize, 10),
|
||||||
"pageNum": strconv.Itoa(pageNum),
|
"pageNum": strconv.FormatInt(pageNum, 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用天眼查API - 经营异常
|
// 调用天眼查API - 经营异常
|
||||||
|
|||||||
@@ -24,18 +24,18 @@ func ProcessQYGL7D9ARequest(ctx context.Context, params []byte, deps *processors
|
|||||||
// 设置默认值
|
// 设置默认值
|
||||||
pageSize := paramsDto.PageSize
|
pageSize := paramsDto.PageSize
|
||||||
if pageSize == 0 {
|
if pageSize == 0 {
|
||||||
pageSize = 20
|
pageSize = int64(20)
|
||||||
}
|
}
|
||||||
pageNum := paramsDto.PageNum
|
pageNum := paramsDto.PageNum
|
||||||
if pageNum == 0 {
|
if pageNum == 0 {
|
||||||
pageNum = 1
|
pageNum = int64(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建API调用参数
|
// 构建API调用参数
|
||||||
apiParams := map[string]string{
|
apiParams := map[string]string{
|
||||||
"keyword": paramsDto.EntCode,
|
"keyword": paramsDto.EntCode,
|
||||||
"pageSize": strconv.Itoa(pageSize),
|
"pageSize": strconv.FormatInt(pageSize, 10),
|
||||||
"pageNum": strconv.Itoa(pageNum),
|
"pageNum": strconv.FormatInt(pageNum, 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用天眼查API - 欠税公告
|
// 调用天眼查API - 欠税公告
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ func ProcessQYGL8271Request(ctx context.Context, params []byte, deps *processors
|
|||||||
}
|
}
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"org_name": encryptedEntName,
|
"org_name": encryptedEntName,
|
||||||
"uscc": encryptedEntCode,
|
"uscc": encryptedEntCode,
|
||||||
"auth_authorizeFileCode": encryptedAuthAuthorizeFileCode,
|
"auth_authorizeFileCode": encryptedAuthAuthorizeFileCode,
|
||||||
"inquired_auth": fmt.Sprintf("authed:%s", paramsDto.AuthDate),
|
"inquired_auth": fmt.Sprintf("authed:%s", paramsDto.AuthDate),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,18 +24,18 @@ func ProcessQYGL8B4DRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
// 设置默认值
|
// 设置默认值
|
||||||
pageSize := paramsDto.PageSize
|
pageSize := paramsDto.PageSize
|
||||||
if pageSize == 0 {
|
if pageSize == 0 {
|
||||||
pageSize = 20
|
pageSize = int64(20)
|
||||||
}
|
}
|
||||||
pageNum := paramsDto.PageNum
|
pageNum := paramsDto.PageNum
|
||||||
if pageNum == 0 {
|
if pageNum == 0 {
|
||||||
pageNum = 1
|
pageNum = int64(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建API调用参数
|
// 构建API调用参数
|
||||||
apiParams := map[string]string{
|
apiParams := map[string]string{
|
||||||
"keyword": paramsDto.EntCode,
|
"keyword": paramsDto.EntCode,
|
||||||
"pageSize": strconv.Itoa(pageSize),
|
"pageSize": strconv.FormatInt(pageSize, 10),
|
||||||
"pageNum": strconv.Itoa(pageNum),
|
"pageNum": strconv.FormatInt(pageNum, 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用天眼查API - 融资历史
|
// 调用天眼查API - 融资历史
|
||||||
|
|||||||
@@ -24,18 +24,18 @@ func ProcessQYGL9E2FRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
// 设置默认值
|
// 设置默认值
|
||||||
pageSize := paramsDto.PageSize
|
pageSize := paramsDto.PageSize
|
||||||
if pageSize == 0 {
|
if pageSize == 0 {
|
||||||
pageSize = 20
|
pageSize = int64(20)
|
||||||
}
|
}
|
||||||
pageNum := paramsDto.PageNum
|
pageNum := paramsDto.PageNum
|
||||||
if pageNum == 0 {
|
if pageNum == 0 {
|
||||||
pageNum = 1
|
pageNum = int64(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建API调用参数
|
// 构建API调用参数
|
||||||
apiParams := map[string]string{
|
apiParams := map[string]string{
|
||||||
"keyword": paramsDto.EntCode,
|
"keyword": paramsDto.EntCode,
|
||||||
"pageSize": strconv.Itoa(pageSize),
|
"pageSize": strconv.FormatInt(pageSize, 10),
|
||||||
"pageNum": strconv.Itoa(pageNum),
|
"pageNum": strconv.FormatInt(pageNum, 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用天眼查API - 行政处罚
|
// 调用天眼查API - 行政处罚
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user