Compare commits
73 Commits
c740ae5639
...
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 | |||
| 15d0759cfb | |||
| 604174cce7 | |||
| 1bfeac0504 | |||
| 00a3f0f1e9 | |||
| f00cee7410 | |||
| 3745a3768f | |||
| a3d0b341a9 | |||
| b74c02b9f0 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -26,6 +26,7 @@ Thumbs.db
|
||||
tmp/
|
||||
temp/
|
||||
console
|
||||
worker
|
||||
|
||||
# 依赖目录
|
||||
vendor/
|
||||
@@ -34,6 +35,11 @@ vendor/
|
||||
coverage.out
|
||||
coverage.html
|
||||
|
||||
# 字体文件(大文件,不进行版本控制)
|
||||
internal/shared/pdf/fonts/*.ttf
|
||||
internal/shared/pdf/fonts/*.ttc
|
||||
internal/shared/pdf/fonts/*.otf
|
||||
|
||||
# 其他
|
||||
*.exe
|
||||
*.dll
|
||||
|
||||
@@ -50,9 +50,11 @@ WORKDIR /app
|
||||
COPY --from=builder /app/tyapi-server .
|
||||
|
||||
# 复制配置文件
|
||||
COPY --chown=tyapi:tyapi config.yaml .
|
||||
COPY --chown=tyapi:tyapi configs/ ./configs/
|
||||
COPY config.yaml .
|
||||
COPY configs/ ./configs/
|
||||
|
||||
# 复制资源文件(直接从构建上下文复制,与配置文件一致)
|
||||
COPY resources ./resources
|
||||
|
||||
# 暴露端口
|
||||
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 (
|
||||
TaskTypeArticlePublish = "article:publish"
|
||||
TaskTypeArticlePublish = "article:publish"
|
||||
TaskTypeAnnouncementPublish = "announcement_publish"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -78,6 +79,9 @@ func main() {
|
||||
mux.HandleFunc(TaskTypeArticlePublish, func(ctx context.Context, t *asynq.Task) error {
|
||||
return handleArticlePublish(ctx, t, db, logger)
|
||||
})
|
||||
mux.HandleFunc(TaskTypeAnnouncementPublish, func(ctx context.Context, t *asynq.Task) error {
|
||||
return handleAnnouncementPublish(ctx, t, db, logger)
|
||||
})
|
||||
|
||||
// 启动 Worker
|
||||
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))
|
||||
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
|
||||
- "255.255.255.255" # 广播IP
|
||||
|
||||
enable_user_agent: true # 是否检查User-Agent
|
||||
enable_user_agent: false # 是否检查User-Agent
|
||||
blocked_user_agents: # 被阻止的User-Agent
|
||||
- "bot" # 机器人
|
||||
- "crawler" # 爬虫
|
||||
@@ -198,7 +198,7 @@ daily_ratelimit:
|
||||
- "https://console.tianyuanapi.com" # 天元API控制台
|
||||
- "https://consoletest.tianyuanapi.com" # 天元API测试控制台
|
||||
|
||||
enable_proxy_check: true # 是否检查代理
|
||||
enable_proxy_check: false # 是否检查代理
|
||||
enable_geo_block: false # 是否启用地理位置阻止
|
||||
blocked_countries: # 被阻止的国家/地区
|
||||
- "XX" # 示例国家代码
|
||||
@@ -243,7 +243,7 @@ esign:
|
||||
app_id: "7439073138"
|
||||
app_secret: "d76e27fdd169b391e09262a0959dac5c"
|
||||
server_url: "https://smlopenapi.esign.cn"
|
||||
template_id: "1fd7ed9c6d134d1db7b5af9582633d76"
|
||||
template_id: "9f7a3f63cc5a48b085b127ba027d234d"
|
||||
contract:
|
||||
name: "天远数据API合作协议"
|
||||
expire_days: 7
|
||||
@@ -362,6 +362,28 @@ alipay:
|
||||
notify_url: "https://console.tianyuanapi.com/api/v1/finance/alipay/callback"
|
||||
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_secret: "c7d8cb0d701f7890601d221e9b6edfef"
|
||||
server_url: "https://smlopenapi.esign.cn"
|
||||
template_id: "1fd7ed9c6d134d1db7b5af9582633d76"
|
||||
template_id: "9f7a3f63cc5a48b085b127ba027d234d"
|
||||
contract:
|
||||
name: "天远数据API合作协议"
|
||||
expire_days: 7
|
||||
@@ -67,6 +67,8 @@ westdex:
|
||||
key: "121a1e41fc1690dd6b90afbcacd80cf4"
|
||||
secret_id: "449159"
|
||||
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"
|
||||
|
||||
# ===========================================
|
||||
# 💰 微信支付配置
|
||||
# ===========================================
|
||||
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:
|
||||
@@ -112,34 +135,42 @@ development:
|
||||
cors_allowed_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
|
||||
cors_allowed_headers: "Origin,Content-Type,Accept,Authorization,X-Requested-With,Access-Id"
|
||||
|
||||
# ===========================================
|
||||
# 🚦 开发环境全局限流(放宽或近似关闭)
|
||||
# ===========================================
|
||||
ratelimit:
|
||||
requests: 1000000 # 每窗口允许的请求数,足够大,相当于关闭
|
||||
window: 1s # 时间窗口
|
||||
burst: 1000000 # 令牌桶突发容量
|
||||
|
||||
# ===========================================
|
||||
# 🚀 开发环境频率限制配置(放宽限制)
|
||||
# ===========================================
|
||||
daily_ratelimit:
|
||||
max_requests_per_day: 1000000 # 开发环境每日最大请求次数
|
||||
max_requests_per_ip: 10000000 # 开发环境每个IP每日最大请求次数
|
||||
max_concurrent: 50 # 开发环境最大并发请求数
|
||||
|
||||
max_requests_per_day: 1000000 # 开发环境每日最大请求次数
|
||||
max_requests_per_ip: 10000000 # 开发环境每个IP每日最大请求次数
|
||||
max_concurrent: 50 # 开发环境最大并发请求数
|
||||
|
||||
# 排除频率限制的路径
|
||||
exclude_paths:
|
||||
- "/health" # 健康检查接口
|
||||
- "/metrics" # 监控指标接口
|
||||
|
||||
- "/health" # 健康检查接口
|
||||
- "/metrics" # 监控指标接口
|
||||
|
||||
# 排除频率限制的域名
|
||||
exclude_domains:
|
||||
- "api.*" # API二级域名不受频率限制
|
||||
- "*.api.*" # 支持多级API域名
|
||||
|
||||
- "api.*" # API二级域名不受频率限制
|
||||
- "*.api.*" # 支持多级API域名
|
||||
|
||||
# 开发环境安全配置(放宽限制)
|
||||
enable_ip_whitelist: true # 启用IP白名单
|
||||
ip_whitelist: # 开发环境IP白名单
|
||||
- "127.0.0.1" # 本地回环
|
||||
- "localhost" # 本地主机
|
||||
- "192.168.*" # 内网IP段
|
||||
- "10.*" # 内网IP段
|
||||
- "172.16.*" # 内网IP段
|
||||
|
||||
enable_ip_blacklist: false # 开发环境禁用IP黑名单
|
||||
enable_user_agent: false # 开发环境禁用User-Agent检查
|
||||
enable_referer: false # 开发环境禁用Referer检查
|
||||
enable_proxy_check: false # 开发环境禁用代理检查
|
||||
enable_ip_whitelist: true # 启用IP白名单
|
||||
ip_whitelist: # 开发环境IP白名单
|
||||
- "127.0.0.1" # 本地回环
|
||||
- "localhost" # 本地主机
|
||||
- "192.168.*" # 内网IP段
|
||||
- "10.*" # 内网IP段
|
||||
- "172.16.*" # 内网IP段
|
||||
|
||||
enable_ip_blacklist: false # 开发环境禁用IP黑名单
|
||||
enable_user_agent: false # 开发环境禁用User-Agent检查
|
||||
enable_referer: false # 开发环境禁用Referer检查
|
||||
enable_proxy_check: false # 开发环境禁用代理检查
|
||||
|
||||
@@ -75,7 +75,7 @@ esign:
|
||||
app_id: "5112008003"
|
||||
app_secret: "d487672273e7aa70c800804a1d9499b9"
|
||||
server_url: "https://openapi.esign.cn"
|
||||
template_id: "c82af4df2790430299c81321f309eef3"
|
||||
template_id: "9f7a3f63cc5a48b085b127ba027d234d"
|
||||
contract:
|
||||
name: "天远数据API合作协议"
|
||||
expire_days: 7
|
||||
|
||||
@@ -58,7 +58,8 @@ services:
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
restart:
|
||||
unless-stopped
|
||||
|
||||
# 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/google/uuid v1.6.0
|
||||
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/qiniu/go-sdk/v7 v7.25.4
|
||||
github.com/redis/go-redis/v9 v9.11.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
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/spf13/viper v1.20.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
@@ -24,6 +26,7 @@ require (
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/wechatpay-apiv3/wechatpay-go v0.2.21
|
||||
github.com/xuri/excelize/v2 v2.9.1
|
||||
go.opentelemetry.io/otel 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/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/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/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=
|
||||
@@ -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/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/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/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
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/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
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/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
|
||||
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/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/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/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Qw=
|
||||
|
||||
@@ -102,7 +102,6 @@ func (a *Application) Run() error {
|
||||
|
||||
// RunMigrations 运行数据库迁移
|
||||
func (a *Application) RunMigrations() error {
|
||||
return nil
|
||||
a.logger.Info("Running database migrations...")
|
||||
|
||||
// 创建数据库连接
|
||||
@@ -246,6 +245,8 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
||||
&articleEntities.Category{},
|
||||
&articleEntities.Tag{},
|
||||
&articleEntities.ScheduledTask{},
|
||||
// 公告
|
||||
&articleEntities.Announcement{},
|
||||
|
||||
// 统计域
|
||||
&statisticsEntities.StatisticsMetric{},
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
"tyapi-server/internal/application/api/dto"
|
||||
@@ -37,8 +39,8 @@ type ApiApplicationService interface {
|
||||
GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error)
|
||||
|
||||
// 用户白名单管理
|
||||
GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error)
|
||||
AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
||||
GetUserWhiteList(ctx context.Context, userID string, remarkKeyword string) (*dto.WhiteListListResponse, error)
|
||||
AddWhiteListIP(ctx context.Context, userID string, ipAddress string, remark string) error
|
||||
DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
||||
|
||||
// 获取用户API调用记录
|
||||
@@ -46,7 +48,7 @@ type ApiApplicationService interface {
|
||||
|
||||
// 管理端API调用记录
|
||||
GetAdminApiCalls(ctx context.Context, filters map[string]interface{}, options shared_interfaces.ListOptions) (*dto.ApiCallListResponse, error)
|
||||
|
||||
|
||||
// 导出功能
|
||||
ExportAdminApiCalls(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error)
|
||||
|
||||
@@ -442,7 +444,7 @@ func (s *ApiApplicationServiceImpl) asyncRecordFailure(ctx context.Context, apiC
|
||||
zap.String("transaction_id", apiCall.TransactionId),
|
||||
zap.String("error_type", errorType),
|
||||
zap.String("error_msg", errorMsg))
|
||||
|
||||
|
||||
// 可选:如果需要统计失败请求,可以在这里添加计数器
|
||||
// s.failureCounter.Inc()
|
||||
}
|
||||
@@ -466,7 +468,7 @@ func (s *ApiApplicationServiceImpl) GetUserApiKeys(ctx context.Context, userID s
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -474,28 +476,49 @@ func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID
|
||||
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
apiUser.WhiteList = entities.WhiteList{}
|
||||
}
|
||||
|
||||
// 将白名单字符串数组转换为响应格式
|
||||
// 将白名单转换为响应格式
|
||||
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{
|
||||
ID: apiUser.ID, // 使用API用户ID作为标识
|
||||
UserID: apiUser.UserId,
|
||||
IPAddress: ip,
|
||||
CreatedAt: apiUser.CreatedAt, // 使用API用户创建时间
|
||||
IPAddress: item.IPAddress,
|
||||
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{
|
||||
Items: items,
|
||||
Total: len(items),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// contains 检查字符串是否包含子字符串(不区分大小写)
|
||||
func contains(s, substr string) bool {
|
||||
if substr == "" {
|
||||
return true
|
||||
}
|
||||
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -503,11 +526,11 @@ func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID s
|
||||
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
apiUser.WhiteList = entities.WhiteList{}
|
||||
}
|
||||
|
||||
// 使用实体的领域方法添加IP到白名单
|
||||
err = apiUser.AddToWhiteList(ipAddress)
|
||||
// 使用实体的领域方法添加IP到白名单(会自动记录添加时间和备注)
|
||||
err = apiUser.AddToWhiteList(ipAddress, remark)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -530,7 +553,7 @@ func (s *ApiApplicationServiceImpl) DeleteWhiteListIP(ctx context.Context, userI
|
||||
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
apiUser.WhiteList = entities.WhiteList{}
|
||||
}
|
||||
|
||||
// 使用实体的领域方法删除IP
|
||||
@@ -774,7 +797,7 @@ func (s *ApiApplicationServiceImpl) ExportAdminApiCalls(ctx context.Context, fil
|
||||
const batchSize = 1000 // 每批处理1000条记录
|
||||
var allCalls []*entities.ApiCall
|
||||
var productNameMap map[string]string
|
||||
|
||||
|
||||
// 分批获取数据
|
||||
page := 1
|
||||
for {
|
||||
@@ -819,7 +842,7 @@ func (s *ApiApplicationServiceImpl) ExportAdminApiCalls(ctx context.Context, fil
|
||||
// 准备导出数据
|
||||
headers := []string{"企业名称", "产品名称", "交易ID", "客户端IP", "状态", "开始时间", "结束时间"}
|
||||
columnWidths := []float64{30, 20, 40, 15, 10, 20, 20}
|
||||
|
||||
|
||||
data := make([][]interface{}, len(allCalls))
|
||||
for i, call := range allCalls {
|
||||
// 从映射中获取企业名称
|
||||
@@ -1206,8 +1229,8 @@ func (s *ApiApplicationServiceImpl) GetUserBalanceAlertSettings(ctx context.Cont
|
||||
// 获取API用户信息
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("获取API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return nil, fmt.Errorf("获取API用户信息失败: %w", err)
|
||||
}
|
||||
@@ -1218,9 +1241,9 @@ func (s *ApiApplicationServiceImpl) GetUserBalanceAlertSettings(ctx context.Cont
|
||||
|
||||
// 返回预警设置
|
||||
settings := map[string]interface{}{
|
||||
"enabled": apiUser.BalanceAlertEnabled,
|
||||
"threshold": apiUser.BalanceAlertThreshold,
|
||||
"alert_phone": apiUser.AlertPhone,
|
||||
"enabled": apiUser.BalanceAlertEnabled,
|
||||
"threshold": apiUser.BalanceAlertThreshold,
|
||||
"alert_phone": apiUser.AlertPhone,
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
@@ -1231,8 +1254,8 @@ func (s *ApiApplicationServiceImpl) UpdateUserBalanceAlertSettings(ctx context.C
|
||||
// 获取API用户信息
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("获取API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("获取API用户信息失败: %w", err)
|
||||
}
|
||||
@@ -1243,16 +1266,16 @@ func (s *ApiApplicationServiceImpl) UpdateUserBalanceAlertSettings(ctx context.C
|
||||
|
||||
// 更新预警设置
|
||||
if err := apiUser.UpdateBalanceAlertSettings(enabled, threshold, alertPhone); err != nil {
|
||||
s.logger.Error("更新预警设置失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("更新预警设置失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("更新预警设置失败: %w", err)
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
if err := s.apiUserService.SaveApiUser(ctx, apiUser); err != nil {
|
||||
s.logger.Error("保存API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("保存API用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("保存API用户信息失败: %w", err)
|
||||
}
|
||||
@@ -1271,8 +1294,8 @@ func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, use
|
||||
// 获取用户信息以获取企业名称
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("获取用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("获取用户信息失败: %w", err)
|
||||
}
|
||||
@@ -1285,8 +1308,8 @@ func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, use
|
||||
|
||||
// 调用短信服务发送测试短信
|
||||
if err := s.balanceAlertService.CheckAndSendAlert(ctx, userID, decimal.NewFromFloat(balance)); err != nil {
|
||||
s.logger.Error("发送测试预警短信失败",
|
||||
zap.String("user_id", userID),
|
||||
s.logger.Error("发送测试预警短信失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("phone", phone),
|
||||
zap.Float64("balance", balance),
|
||||
zap.String("alert_type", alertType),
|
||||
|
||||
@@ -26,11 +26,13 @@ type WhiteListResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
IPAddress string `json:"ip_address"`
|
||||
Remark string `json:"remark"` // 备注
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type WhiteListRequest struct {
|
||||
IPAddress string `json:"ip_address" binding:"required,ip"`
|
||||
Remark string `json:"remark"` // 备注(可选)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -5,7 +5,6 @@ type CreateWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
}
|
||||
|
||||
|
||||
// TransferRechargeCommand 对公转账充值命令
|
||||
type TransferRechargeCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
@@ -16,16 +15,24 @@ type TransferRechargeCommand struct {
|
||||
|
||||
// GiftRechargeCommand 赠送充值命令
|
||||
type GiftRechargeCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount string `json:"amount" binding:"required"`
|
||||
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount string `json:"amount" binding:"required"`
|
||||
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
|
||||
}
|
||||
|
||||
|
||||
// CreateAlipayRechargeCommand 创建支付宝充值订单命令
|
||||
type CreateAlipayRechargeCommand struct {
|
||||
UserID string `json:"-"` // 用户ID(从token获取)
|
||||
Amount string `json:"amount" binding:"required"` // 充值金额
|
||||
Subject string `json:"-"` // 订单标题
|
||||
UserID string `json:"-"` // 用户ID(从token获取)
|
||||
Amount string `json:"amount" binding:"required"` // 充值金额
|
||||
Subject string `json:"-"` // 订单标题
|
||||
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 钱包响应
|
||||
type WalletResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
IsActive bool `json:"is_active"`
|
||||
Balance decimal.Decimal `json:"balance"`
|
||||
BalanceStatus string `json:"balance_status"` // normal, low, arrears
|
||||
IsArrears bool `json:"is_arrears"` // 是否欠费
|
||||
IsLowBalance bool `json:"is_low_balance"` // 是否余额较低
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
IsActive bool `json:"is_active"`
|
||||
Balance decimal.Decimal `json:"balance"`
|
||||
BalanceStatus string `json:"balance_status"` // normal, low, arrears
|
||||
IsArrears bool `json:"is_arrears"` // 是否欠费
|
||||
IsLowBalance bool `json:"is_low_balance"` // 是否余额较低
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TransactionResponse 交易响应
|
||||
@@ -49,34 +49,36 @@ type WalletStatsResponse struct {
|
||||
|
||||
// RechargeRecordResponse 充值记录响应
|
||||
type RechargeRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
RechargeType string `json:"recharge_type"`
|
||||
Status string `json:"status"`
|
||||
AlipayOrderID string `json:"alipay_order_id,omitempty"`
|
||||
TransferOrderID string `json:"transfer_order_id,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
OperatorID string `json:"operator_id,omitempty"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
User *UserSimpleResponse `json:"user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
RechargeType string `json:"recharge_type"`
|
||||
Status string `json:"status"`
|
||||
AlipayOrderID string `json:"alipay_order_id,omitempty"`
|
||||
WechatOrderID string `json:"wechat_order_id,omitempty"`
|
||||
TransferOrderID string `json:"transfer_order_id,omitempty"`
|
||||
Platform string `json:"platform,omitempty"` // 支付平台:pc/wx_native等
|
||||
Notes string `json:"notes,omitempty"`
|
||||
OperatorID string `json:"operator_id,omitempty"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
User *UserSimpleResponse `json:"user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// WalletTransactionResponse 钱包交易记录响应
|
||||
type WalletTransactionResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
ApiCallID string `json:"api_call_id"`
|
||||
TransactionID string `json:"transaction_id"`
|
||||
ProductID string `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
ApiCallID string `json:"api_call_id"`
|
||||
TransactionID string `json:"transaction_id"`
|
||||
ProductID string `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
User *UserSimpleResponse `json:"user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// WalletTransactionListResponse 钱包交易记录列表响应
|
||||
@@ -97,17 +99,17 @@ type RechargeRecordListResponse struct {
|
||||
|
||||
// AlipayRechargeOrderResponse 支付宝充值订单响应
|
||||
type AlipayRechargeOrderResponse struct {
|
||||
PayURL string `json:"pay_url"` // 支付链接
|
||||
OutTradeNo string `json:"out_trade_no"` // 商户订单号
|
||||
Amount decimal.Decimal `json:"amount"` // 充值金额
|
||||
Platform string `json:"platform"` // 支付平台
|
||||
Subject string `json:"subject"` // 订单标题
|
||||
PayURL string `json:"pay_url"` // 支付链接
|
||||
OutTradeNo string `json:"out_trade_no"` // 商户订单号
|
||||
Amount decimal.Decimal `json:"amount"` // 充值金额
|
||||
Platform string `json:"platform"` // 支付平台
|
||||
Subject string `json:"subject"` // 订单标题
|
||||
}
|
||||
|
||||
// RechargeConfigResponse 充值配置响应
|
||||
type RechargeConfigResponse struct {
|
||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||
MinAmount string `json:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `json:"max_amount"` // 最高充值金额
|
||||
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,13 +17,14 @@ type FinanceApplicationService interface {
|
||||
|
||||
// 充值管理
|
||||
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)
|
||||
GiftRecharge(ctx context.Context, cmd *commands.GiftRechargeCommand) (*responses.RechargeRecordResponse, error)
|
||||
|
||||
// 交易记录
|
||||
GetUserWalletTransactions(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error)
|
||||
GetAdminWalletTransactions(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error)
|
||||
|
||||
|
||||
// 导出功能
|
||||
ExportAdminWalletTransactions(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error)
|
||||
ExportAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error)
|
||||
@@ -33,12 +34,15 @@ type FinanceApplicationService interface {
|
||||
HandleAlipayReturn(ctx context.Context, outTradeNo string) (string, 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)
|
||||
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
||||
|
||||
// 获取充值配置
|
||||
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@ package finance
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"time"
|
||||
"tyapi-server/internal/application/finance/dto/commands"
|
||||
"tyapi-server/internal/application/finance/dto/queries"
|
||||
"tyapi-server/internal/application/finance/dto/responses"
|
||||
@@ -16,19 +21,18 @@ import (
|
||||
"tyapi-server/internal/shared/export"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
"tyapi-server/internal/shared/payment"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// FinanceApplicationServiceImpl 财务应用服务实现
|
||||
type FinanceApplicationServiceImpl struct {
|
||||
aliPayClient *payment.AliPayService
|
||||
wechatPayService *payment.WechatPayService
|
||||
walletService finance_services.WalletAggregateService
|
||||
rechargeRecordService finance_services.RechargeRecordService
|
||||
walletTransactionRepository finance_repositories.WalletTransactionRepository
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
||||
wechatOrderRepo finance_repositories.WechatOrderRepository
|
||||
rechargeRecordRepo finance_repositories.RechargeRecordRepository
|
||||
userRepo user_repositories.UserRepository
|
||||
txManager *database.TransactionManager
|
||||
exportManager *export.ExportManager
|
||||
@@ -39,10 +43,13 @@ type FinanceApplicationServiceImpl struct {
|
||||
// NewFinanceApplicationService 创建财务应用服务
|
||||
func NewFinanceApplicationService(
|
||||
aliPayClient *payment.AliPayService,
|
||||
wechatPayService *payment.WechatPayService,
|
||||
walletService finance_services.WalletAggregateService,
|
||||
rechargeRecordService finance_services.RechargeRecordService,
|
||||
walletTransactionRepository finance_repositories.WalletTransactionRepository,
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
||||
wechatOrderRepo finance_repositories.WechatOrderRepository,
|
||||
rechargeRecordRepo finance_repositories.RechargeRecordRepository,
|
||||
userRepo user_repositories.UserRepository,
|
||||
txManager *database.TransactionManager,
|
||||
logger *zap.Logger,
|
||||
@@ -51,10 +58,13 @@ func NewFinanceApplicationService(
|
||||
) FinanceApplicationService {
|
||||
return &FinanceApplicationServiceImpl{
|
||||
aliPayClient: aliPayClient,
|
||||
wechatPayService: wechatPayService,
|
||||
walletService: walletService,
|
||||
rechargeRecordService: rechargeRecordService,
|
||||
walletTransactionRepository: walletTransactionRepository,
|
||||
alipayOrderRepo: alipayOrderRepo,
|
||||
wechatOrderRepo: wechatOrderRepo,
|
||||
rechargeRecordRepo: rechargeRecordRepo,
|
||||
userRepo: userRepo,
|
||||
txManager: txManager,
|
||||
exportManager: exportManager,
|
||||
@@ -100,8 +110,9 @@ func (s *FinanceApplicationServiceImpl) GetWallet(ctx context.Context, query *qu
|
||||
BalanceStatus: wallet.GetBalanceStatus(),
|
||||
IsArrears: wallet.IsArrears(),
|
||||
IsLowBalance: wallet.IsLowBalance(),
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -188,6 +199,168 @@ func (s *FinanceApplicationServiceImpl) CreateAlipayRechargeOrder(ctx context.Co
|
||||
}, 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 对公转账充值
|
||||
func (s *FinanceApplicationServiceImpl) TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error) {
|
||||
// 将字符串金额转换为 decimal.Decimal
|
||||
@@ -507,8 +680,8 @@ func (s *FinanceApplicationServiceImpl) ExportAdminRechargeRecords(ctx context.C
|
||||
}
|
||||
|
||||
// 准备导出数据
|
||||
headers := []string{"企业名称", "充值金额", "充值类型", "状态", "支付宝订单号", "转账订单号", "备注", "充值时间"}
|
||||
columnWidths := []float64{25, 15, 15, 10, 20, 20, 20, 20}
|
||||
headers := []string{"企业名称", "充值金额", "充值类型", "状态", "支付宝订单号", "微信订单号", "转账订单号", "备注", "充值时间"}
|
||||
columnWidths := []float64{25, 15, 15, 10, 20, 20, 20, 20, 20}
|
||||
|
||||
data := make([][]interface{}, len(allRecords))
|
||||
for i, record := range allRecords {
|
||||
@@ -523,6 +696,10 @@ func (s *FinanceApplicationServiceImpl) ExportAdminRechargeRecords(ctx context.C
|
||||
if record.AlipayOrderID != nil && *record.AlipayOrderID != "" {
|
||||
alipayOrderID = *record.AlipayOrderID
|
||||
}
|
||||
wechatOrderID := ""
|
||||
if record.WechatOrderID != nil && *record.WechatOrderID != "" {
|
||||
wechatOrderID = *record.WechatOrderID
|
||||
}
|
||||
transferOrderID := ""
|
||||
if record.TransferOrderID != nil && *record.TransferOrderID != "" {
|
||||
transferOrderID = *record.TransferOrderID
|
||||
@@ -543,6 +720,7 @@ func (s *FinanceApplicationServiceImpl) ExportAdminRechargeRecords(ctx context.C
|
||||
translateRechargeType(record.RechargeType),
|
||||
translateRechargeStatus(record.Status),
|
||||
alipayOrderID,
|
||||
wechatOrderID,
|
||||
transferOrderID,
|
||||
notes,
|
||||
createdAt,
|
||||
@@ -566,6 +744,8 @@ func translateRechargeType(rechargeType finance_entities.RechargeType) string {
|
||||
switch rechargeType {
|
||||
case finance_entities.RechargeTypeAlipay:
|
||||
return "支付宝充值"
|
||||
case finance_entities.RechargeTypeWechat:
|
||||
return "微信充值"
|
||||
case finance_entities.RechargeTypeTransfer:
|
||||
return "对公转账"
|
||||
case finance_entities.RechargeTypeGift:
|
||||
@@ -890,15 +1070,27 @@ func (s *FinanceApplicationServiceImpl) GetAlipayOrderStatus(ctx context.Context
|
||||
|
||||
// GetUserRechargeRecords 获取用户充值记录
|
||||
func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
|
||||
// 查询用户充值记录
|
||||
records, err := s.rechargeRecordService.GetByUserID(ctx, userID)
|
||||
// 确保 filters 不为 nil
|
||||
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 {
|
||||
s.logger.Error("查询用户充值记录失败", zap.Error(err), zap.String("userID", userID))
|
||||
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
|
||||
var items []responses.RechargeRecordResponse
|
||||
@@ -914,9 +1106,20 @@ func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Conte
|
||||
UpdatedAt: record.UpdatedAt,
|
||||
}
|
||||
|
||||
// 根据充值类型设置相应的订单号
|
||||
// 根据充值类型设置相应的订单号和平台信息
|
||||
if record.AlipayOrderID != nil {
|
||||
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 {
|
||||
item.TransferOrderID = *record.TransferOrderID
|
||||
@@ -963,9 +1166,20 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
|
||||
UpdatedAt: record.UpdatedAt,
|
||||
}
|
||||
|
||||
// 根据充值类型设置相应的订单号
|
||||
// 根据充值类型设置相应的订单号和平台信息
|
||||
if record.AlipayOrderID != nil {
|
||||
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 {
|
||||
item.TransferOrderID = *record.TransferOrderID
|
||||
@@ -1012,3 +1226,445 @@ func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (
|
||||
AlipayRechargeBonus: bonus,
|
||||
}, 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
|
||||
}
|
||||
|
||||
// 3. 获取真实充值金额(支付宝充值+对公转账)和总赠送金额
|
||||
// 3. 获取真实充值金额(支付宝充值+微信充值+对公转账)和总赠送金额
|
||||
realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -408,7 +408,7 @@ func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context,
|
||||
// 5. 构建响应DTO
|
||||
return &dto.AvailableAmountResponse{
|
||||
AvailableAmount: availableAmount,
|
||||
TotalRecharged: realRecharged, // 使用真实充值金额(支付宝充值+对公转账)
|
||||
TotalRecharged: realRecharged, // 使用真实充值金额(支付宝充值+微信充值+对公转账)
|
||||
TotalGifted: totalGifted,
|
||||
TotalInvoiced: totalInvoiced,
|
||||
PendingApplications: pendingAmount,
|
||||
@@ -417,7 +417,7 @@ func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context,
|
||||
|
||||
// calculateAvailableAmount 计算可开票金额(私有方法)
|
||||
func (s *InvoiceApplicationServiceImpl) calculateAvailableAmount(ctx context.Context, userID string) (decimal.Decimal, error) {
|
||||
// 1. 获取真实充值金额(支付宝充值+对公转账)和总赠送金额
|
||||
// 1. 获取真实充值金额(支付宝充值+微信充值+对公转账)和总赠送金额
|
||||
realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID)
|
||||
if err != nil {
|
||||
return decimal.Zero, err
|
||||
@@ -433,7 +433,7 @@ func (s *InvoiceApplicationServiceImpl) calculateAvailableAmount(ctx context.Con
|
||||
fmt.Println("totalInvoiced", totalInvoiced)
|
||||
fmt.Println("pendingAmount", pendingAmount)
|
||||
// 3. 计算可开票金额:真实充值金额 - 已开票 - 待处理申请
|
||||
// 可开票金额 = 真实充值金额(支付宝充值+对公转账) - 已开票金额 - 待处理申请金额
|
||||
// 可开票金额 = 真实充值金额(支付宝充值+微信充值+对公转账) - 已开票金额 - 待处理申请金额
|
||||
availableAmount := realRecharged.Sub(totalInvoiced).Sub(pendingAmount)
|
||||
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)
|
||||
}
|
||||
|
||||
// 2. 计算真实充值金额(支付宝充值 + 对公转账)和总赠送金额
|
||||
var realRecharged decimal.Decimal // 真实充值金额:支付宝充值 + 对公转账
|
||||
// 2. 计算真实充值金额(支付宝充值 + 微信充值 + 对公转账)和总赠送金额
|
||||
var realRecharged decimal.Decimal // 真实充值金额:支付宝充值 + 微信充值 + 对公转账
|
||||
var totalGifted decimal.Decimal // 总赠送金额
|
||||
for _, record := range rechargeRecords {
|
||||
if record.IsSuccess() {
|
||||
if record.RechargeType == entities.RechargeTypeGift {
|
||||
// 赠送金额不计入可开票金额
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
@@ -28,6 +30,9 @@ type DocumentationApplicationServiceInterface interface {
|
||||
|
||||
// GetDocumentationsByProductIDs 批量获取文档
|
||||
GetDocumentationsByProductIDs(ctx context.Context, productIDs []string) ([]responses.DocumentationResponse, error)
|
||||
|
||||
// GenerateFullDocumentation 生成完整的接口文档(Markdown格式)
|
||||
GenerateFullDocumentation(ctx context.Context, productID string) (string, error)
|
||||
}
|
||||
|
||||
// DocumentationApplicationService 文档应用服务
|
||||
@@ -53,6 +58,7 @@ func (s *DocumentationApplicationService) CreateDocumentation(ctx context.Contex
|
||||
ResponseFields: cmd.ResponseFields,
|
||||
ResponseExample: cmd.ResponseExample,
|
||||
ErrorCodes: cmd.ErrorCodes,
|
||||
PDFFilePath: cmd.PDFFilePath,
|
||||
}
|
||||
|
||||
// 调用领域服务创建文档
|
||||
@@ -88,6 +94,20 @@ func (s *DocumentationApplicationService) UpdateDocumentation(ctx context.Contex
|
||||
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)
|
||||
return &resp, nil
|
||||
@@ -136,3 +156,93 @@ func (s *DocumentationApplicationService) GetDocumentationsByProductIDs(ctx cont
|
||||
|
||||
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"`
|
||||
ResponseExample string `json:"response_example"`
|
||||
ErrorCodes string `json:"error_codes"`
|
||||
PDFFilePath string `json:"pdf_file_path,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateDocumentationCommand 更新文档命令
|
||||
@@ -21,4 +22,5 @@ type UpdateDocumentationCommand struct {
|
||||
ResponseFields string `json:"response_fields"`
|
||||
ResponseExample string `json:"response_example"`
|
||||
ErrorCodes string `json:"error_codes"`
|
||||
PDFFilePath string `json:"pdf_file_path,omitempty"`
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ type CreateProductCommand struct {
|
||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
@@ -27,6 +29,8 @@ type UpdateProductCommand struct {
|
||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
|
||||
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
@@ -14,7 +14,9 @@ type UpdateSubscriptionPriceCommand struct {
|
||||
|
||||
// BatchUpdateSubscriptionPricesCommand 批量更新订阅价格命令
|
||||
type BatchUpdateSubscriptionPricesCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户ID"`
|
||||
Discount float64 `json:"discount" binding:"required,min=0.1,max=10" comment:"折扣比例(0.1-10折)"`
|
||||
Scope string `json:"scope" binding:"required,oneof=undiscounted all" comment:"改价范围(undiscounted:仅未打折,all:所有)"`
|
||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户ID"`
|
||||
AdjustmentType string `json:"adjustment_type" binding:"required,oneof=discount cost_multiple" comment:"调整方式(discount:按售价折扣,cost_multiple:按成本价倍数)"`
|
||||
Discount float64 `json:"discount,omitempty" binding:"omitempty,min=0.1,max=10" comment:"折扣比例(0.1-10折)"`
|
||||
CostMultiple float64 `json:"cost_multiple,omitempty" binding:"omitempty,min=0.1" comment:"成本价倍数"`
|
||||
Scope string `json:"scope" binding:"required,oneof=undiscounted all" comment:"改价范围(undiscounted:仅未打折,all:所有)"`
|
||||
}
|
||||
@@ -18,6 +18,7 @@ type DocumentationResponse struct {
|
||||
ResponseExample string `json:"response_example"`
|
||||
ErrorCodes string `json:"error_codes"`
|
||||
Version string `json:"version"`
|
||||
PDFFilePath string `json:"pdf_file_path,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -35,6 +36,7 @@ func NewDocumentationResponse(doc *entities.ProductDocumentation) DocumentationR
|
||||
ResponseExample: doc.ResponseExample,
|
||||
ErrorCodes: doc.ErrorCodes,
|
||||
Version: doc.Version,
|
||||
PDFFilePath: doc.PDFFilePath,
|
||||
CreatedAt: doc.CreatedAt,
|
||||
UpdatedAt: doc.UpdatedAt,
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ type PackageItemResponse struct {
|
||||
ProductName string `json:"product_name" comment:"子产品名称"`
|
||||
SortOrder int `json:"sort_order" comment:"排序"`
|
||||
Price float64 `json:"price" comment:"子产品价格"`
|
||||
CostPrice float64 `json:"cost_price" comment:"子产品成本价"`
|
||||
}
|
||||
|
||||
// ProductInfoResponse 产品详情响应
|
||||
@@ -70,6 +71,12 @@ type ProductSimpleResponse struct {
|
||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||
}
|
||||
|
||||
// ProductSimpleAdminResponse 管理员产品简单信息响应(包含成本价)
|
||||
type ProductSimpleAdminResponse struct {
|
||||
ProductSimpleResponse
|
||||
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||
}
|
||||
|
||||
// ProductStatsResponse 产品统计响应
|
||||
type ProductStatsResponse struct {
|
||||
TotalProducts int64 `json:"total_products" comment:"产品总数"`
|
||||
@@ -88,6 +95,8 @@ type ProductAdminInfoResponse struct {
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||
Remark string `json:"remark" comment:"备注"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
@@ -22,6 +22,8 @@ type SubscriptionInfoResponse struct {
|
||||
// 关联信息
|
||||
User *UserSimpleResponse `json:"user,omitempty" comment:"用户信息"`
|
||||
Product *ProductSimpleResponse `json:"product,omitempty" comment:"产品信息"`
|
||||
// 管理员端使用,包含成本价的产品信息
|
||||
ProductAdmin *ProductSimpleAdminResponse `json:"product_admin,omitempty" comment:"产品信息(管理员端,包含成本价)"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
|
||||
@@ -11,7 +11,8 @@ import (
|
||||
// ProductApplicationService 产品应用服务接口
|
||||
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
|
||||
DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error
|
||||
|
||||
@@ -38,14 +39,12 @@ type ProductApplicationService interface {
|
||||
ReorderPackageItems(ctx context.Context, packageID string, cmd *commands.ReorderPackageItemsCommand) error
|
||||
UpdatePackageItems(ctx context.Context, packageID string, cmd *commands.UpdatePackageItemsCommand) error
|
||||
|
||||
// 可选子产品查询
|
||||
GetAvailableProducts(ctx context.Context, query *queries.GetAvailableProductsQuery) (*responses.ProductListResponse, error)
|
||||
// 可选子产品查询(管理员端,返回包含成本价的数据)
|
||||
GetAvailableProducts(ctx context.Context, query *queries.GetAvailableProductsQuery) (*responses.ProductAdminListResponse, error)
|
||||
|
||||
// API配置管理
|
||||
GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error)
|
||||
CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error
|
||||
UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error
|
||||
DeleteProductApiConfig(ctx context.Context, configID string) error
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func NewProductApplicationService(
|
||||
|
||||
// CreateProduct 创建产品
|
||||
// 业务流程<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. 构建产品实体
|
||||
product := &entities.Product{
|
||||
Name: cmd.Name,
|
||||
@@ -60,6 +60,8 @@ func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *
|
||||
Content: cmd.Content,
|
||||
CategoryID: cmd.CategoryID,
|
||||
Price: decimal.NewFromFloat(cmd.Price),
|
||||
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
||||
Remark: cmd.Remark,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
IsPackage: cmd.IsPackage,
|
||||
@@ -69,8 +71,13 @@ func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *
|
||||
}
|
||||
|
||||
// 2. 创建产品
|
||||
_, err := s.productManagementService.CreateProduct(ctx, product)
|
||||
return err
|
||||
createdProduct, err := s.productManagementService.CreateProduct(ctx, product)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 转换为响应对象
|
||||
return s.convertToProductAdminInfoResponse(createdProduct), nil
|
||||
}
|
||||
|
||||
// UpdateProduct 更新产品
|
||||
@@ -89,6 +96,8 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
|
||||
existingProduct.Content = cmd.Content
|
||||
existingProduct.CategoryID = cmd.CategoryID
|
||||
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
|
||||
existingProduct.CostPrice = decimal.NewFromFloat(cmd.CostPrice)
|
||||
existingProduct.Remark = cmd.Remark
|
||||
existingProduct.IsEnabled = cmd.IsEnabled
|
||||
existingProduct.IsVisible = cmd.IsVisible
|
||||
existingProduct.IsPackage = cmd.IsPackage
|
||||
@@ -353,9 +362,9 @@ func (s *ProductApplicationServiceImpl) UpdatePackageItems(ctx context.Context,
|
||||
return s.productManagementService.UpdatePackageItemsBatch(ctx, packageID, cmd.Items)
|
||||
}
|
||||
|
||||
// GetAvailableProducts 获取可选子产品列表
|
||||
// 业务流程:1. 获取启用产品 2. 过滤可订阅产品 3. 构建响应数据
|
||||
func (s *ProductApplicationServiceImpl) GetAvailableProducts(ctx context.Context, query *appQueries.GetAvailableProductsQuery) (*responses.ProductListResponse, error) {
|
||||
// GetAvailableProducts 获取可选子产品列表(管理员端,返回包含成本价的数据)
|
||||
// 业务流程:1. 获取启用产品 2. 过滤可订阅产品 3. 构建管理员响应数据
|
||||
func (s *ProductApplicationServiceImpl) GetAvailableProducts(ctx context.Context, query *appQueries.GetAvailableProductsQuery) (*responses.ProductAdminListResponse, error) {
|
||||
// 构建筛选条件
|
||||
filters := make(map[string]interface{})
|
||||
filters["is_package"] = false // 只获取非组合包产品
|
||||
@@ -381,13 +390,13 @@ func (s *ProductApplicationServiceImpl) GetAvailableProducts(ctx context.Context
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]responses.ProductInfoResponse, len(products))
|
||||
// 转换为管理员响应对象(包含成本价,用于组合包配置)
|
||||
items := make([]responses.ProductAdminInfoResponse, len(products))
|
||||
for i := range products {
|
||||
items[i] = *s.convertToProductInfoResponse(products[i])
|
||||
items[i] = *s.convertToProductAdminInfoResponse(products[i])
|
||||
}
|
||||
|
||||
return &responses.ProductListResponse{
|
||||
return &responses.ProductAdminListResponse{
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
@@ -510,6 +519,7 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
|
||||
ProductName: item.Product.Name,
|
||||
SortOrder: item.SortOrder,
|
||||
Price: item.Product.Price.InexactFloat64(),
|
||||
CostPrice: item.Product.CostPrice.InexactFloat64(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,6 +538,8 @@ func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(produc
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
Remark: product.Remark,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||
IsPackage: product.IsPackage,
|
||||
@@ -554,6 +566,7 @@ func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(produc
|
||||
ProductName: item.Product.Name,
|
||||
SortOrder: item.SortOrder,
|
||||
Price: item.Product.Price.InexactFloat64(),
|
||||
CostPrice: item.Product.CostPrice.InexactFloat64(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -957,7 +970,7 @@ func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} {
|
||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||
"JRZQDBCE": &dto.JRZQDBCEReq{},
|
||||
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||
@@ -974,7 +987,7 @@ func (s *ProductApplicationServiceImpl) getDTOMap() map[string]interface{} {
|
||||
"YYSY4B21": &dto.YYSY4B21Req{},
|
||||
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
||||
"YYSY09CD": &dto.YYSY09CDReq{},
|
||||
"IVYZ0b03": &dto.IVYZ0b03Req{},
|
||||
"IVYZ0B03": &dto.IVYZ0B03Req{},
|
||||
"YYSYBE08": &dto.YYSYBE08Req{},
|
||||
"YYSYD50F": &dto.YYSYD50FReq{},
|
||||
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
||||
|
||||
@@ -20,6 +20,7 @@ type SubscriptionApplicationService interface {
|
||||
// 我的订阅(用户专用)
|
||||
ListMySubscriptions(ctx context.Context, userID string, query *queries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, 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)
|
||||
|
||||
@@ -2,6 +2,7 @@ package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
appQueries "tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
domain_api_repo "tyapi-server/internal/domains/api/repositories"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
@@ -20,6 +22,7 @@ import (
|
||||
type SubscriptionApplicationServiceImpl struct {
|
||||
productSubscriptionService *product_service.ProductSubscriptionService
|
||||
userRepo user_repositories.UserRepository
|
||||
apiCallRepository domain_api_repo.ApiCallRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
@@ -27,11 +30,13 @@ type SubscriptionApplicationServiceImpl struct {
|
||||
func NewSubscriptionApplicationService(
|
||||
productSubscriptionService *product_service.ProductSubscriptionService,
|
||||
userRepo user_repositories.UserRepository,
|
||||
apiCallRepository domain_api_repo.ApiCallRepository,
|
||||
logger *zap.Logger,
|
||||
) SubscriptionApplicationService {
|
||||
return &SubscriptionApplicationServiceImpl{
|
||||
productSubscriptionService: productSubscriptionService,
|
||||
userRepo: userRepo,
|
||||
apiCallRepository: apiCallRepository,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -45,6 +50,22 @@ func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context
|
||||
// BatchUpdateSubscriptionPrices 一键改价
|
||||
// 业务流程:1. 获取用户所有订阅 2. 根据范围筛选 3. 批量更新价格
|
||||
func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx context.Context, cmd *commands.BatchUpdateSubscriptionPricesCommand) error {
|
||||
// 记录请求参数
|
||||
s.logger.Info("开始批量更新订阅价格",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("adjustment_type", cmd.AdjustmentType),
|
||||
zap.Float64("discount", cmd.Discount),
|
||||
zap.Float64("cost_multiple", cmd.CostMultiple),
|
||||
zap.String("scope", cmd.Scope))
|
||||
|
||||
// 验证调整方式对应的参数
|
||||
if cmd.AdjustmentType == "discount" && cmd.Discount <= 0 {
|
||||
return fmt.Errorf("按售价折扣调整时,折扣比例必须大于0")
|
||||
}
|
||||
if cmd.AdjustmentType == "cost_multiple" && cmd.CostMultiple <= 0 {
|
||||
return fmt.Errorf("按成本价倍数调整时,倍数必须大于0")
|
||||
}
|
||||
|
||||
subscriptions, _, err := s.productSubscriptionService.ListSubscriptions(ctx, &repoQueries.ListSubscriptionsQuery{
|
||||
UserID: cmd.UserID,
|
||||
Page: 1,
|
||||
@@ -54,6 +75,9 @@ func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx c
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("获取到订阅列表",
|
||||
zap.Int("total_subscriptions", len(subscriptions)))
|
||||
|
||||
// 根据范围筛选订阅
|
||||
var targetSubscriptions []*entities.Subscription
|
||||
for _, sub := range subscriptions {
|
||||
@@ -69,24 +93,64 @@ func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx c
|
||||
}
|
||||
|
||||
// 批量更新价格
|
||||
updatedCount := 0
|
||||
skippedCount := 0
|
||||
for _, sub := range targetSubscriptions {
|
||||
if sub.Product != nil {
|
||||
// 计算折扣后的价格
|
||||
discountRatio := cmd.Discount / 10
|
||||
newPrice := sub.Product.Price.Mul(decimal.NewFromFloat(discountRatio))
|
||||
// 四舍五入到2位小数
|
||||
newPrice = newPrice.Round(2)
|
||||
if sub.Product == nil {
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
err := s.productSubscriptionService.UpdateSubscriptionPrice(ctx, sub.ID, newPrice.InexactFloat64())
|
||||
if err != nil {
|
||||
s.logger.Error("批量更新订阅价格失败",
|
||||
var newPrice decimal.Decimal
|
||||
|
||||
if cmd.AdjustmentType == "discount" {
|
||||
// 按售价折扣调整
|
||||
discountRatio := cmd.Discount / 10
|
||||
newPrice = sub.Product.Price.Mul(decimal.NewFromFloat(discountRatio))
|
||||
} else if cmd.AdjustmentType == "cost_multiple" {
|
||||
// 按成本价倍数调整
|
||||
// 检查成本价是否有效(必须大于0)
|
||||
// 使用严格检查:成本价必须大于0
|
||||
if !sub.Product.CostPrice.GreaterThan(decimal.Zero) {
|
||||
// 跳过没有成本价或成本价为0的产品
|
||||
skippedCount++
|
||||
s.logger.Info("跳过未设置成本价或成本价为0的订阅",
|
||||
zap.String("subscription_id", sub.ID),
|
||||
zap.Error(err))
|
||||
// 继续处理其他订阅,不中断整个流程
|
||||
zap.String("product_id", sub.ProductID),
|
||||
zap.String("product_name", sub.Product.Name),
|
||||
zap.String("cost_price", sub.Product.CostPrice.String()))
|
||||
continue
|
||||
}
|
||||
// 计算成本价倍数后的价格
|
||||
newPrice = sub.Product.CostPrice.Mul(decimal.NewFromFloat(cmd.CostMultiple))
|
||||
} else {
|
||||
s.logger.Warn("未知的调整方式",
|
||||
zap.String("adjustment_type", cmd.AdjustmentType),
|
||||
zap.String("subscription_id", sub.ID))
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 四舍五入到2位小数
|
||||
newPrice = newPrice.Round(2)
|
||||
|
||||
err := s.productSubscriptionService.UpdateSubscriptionPrice(ctx, sub.ID, newPrice.InexactFloat64())
|
||||
if err != nil {
|
||||
s.logger.Error("批量更新订阅价格失败",
|
||||
zap.String("subscription_id", sub.ID),
|
||||
zap.Error(err))
|
||||
skippedCount++
|
||||
// 继续处理其他订阅,不中断整个流程
|
||||
} else {
|
||||
updatedCount++
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("批量更新订阅价格完成",
|
||||
zap.Int("total", len(targetSubscriptions)),
|
||||
zap.Int("updated", updatedCount),
|
||||
zap.Int("skipped", skippedCount))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -129,7 +193,7 @@ func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Conte
|
||||
}
|
||||
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
resp := s.convertToSubscriptionInfoResponse(subscriptions[i])
|
||||
resp := s.convertToSubscriptionInfoResponseForAdmin(subscriptions[i])
|
||||
if resp != nil {
|
||||
items[i] = *resp // 解引用指针
|
||||
}
|
||||
@@ -202,17 +266,30 @@ func (s *SubscriptionApplicationServiceImpl) GetProductSubscriptions(ctx context
|
||||
}
|
||||
|
||||
// GetSubscriptionUsage 获取订阅使用情况
|
||||
// 业务流程:1. 获取订阅使用情况 2. 构建响应数据
|
||||
// 业务流程:1. 获取订阅信息 2. 根据产品ID和用户ID统计API调用次数 3. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) {
|
||||
// 获取订阅信息
|
||||
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
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{
|
||||
ID: subscription.ID,
|
||||
ProductID: subscription.ProductID,
|
||||
APIUsed: subscription.APIUsed,
|
||||
APIUsed: apiCallCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -244,6 +321,38 @@ func (s *SubscriptionApplicationServiceImpl) GetMySubscriptionStats(ctx context.
|
||||
}, 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 转换为订阅信息响应
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
|
||||
// 查询用户信息
|
||||
@@ -300,6 +409,65 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(prod
|
||||
}
|
||||
}
|
||||
|
||||
// convertToSubscriptionInfoResponseForAdmin 转换为订阅信息响应(管理员端,包含成本价)
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponseForAdmin(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
|
||||
// 查询用户信息
|
||||
var userInfo *responses.UserSimpleResponse
|
||||
if subscription.UserID != "" {
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(context.Background(), subscription.UserID)
|
||||
if err == nil {
|
||||
companyName := "未知公司"
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
userInfo = &responses.UserSimpleResponse{
|
||||
ID: user.ID,
|
||||
CompanyName: companyName,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var productAdminResponse *responses.ProductSimpleAdminResponse
|
||||
if subscription.Product != nil {
|
||||
productAdminResponse = s.convertToProductSimpleAdminResponse(subscription.Product)
|
||||
}
|
||||
|
||||
return &responses.SubscriptionInfoResponse{
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price.InexactFloat64(),
|
||||
User: userInfo,
|
||||
ProductAdmin: productAdminResponse,
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// convertToProductSimpleAdminResponse 转换为管理员产品简单信息响应(包含成本价)
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleAdminResponse(product *entities.Product) *responses.ProductSimpleAdminResponse {
|
||||
var categoryResponse *responses.CategorySimpleResponse
|
||||
if product.Category != nil {
|
||||
categoryResponse = s.convertToCategorySimpleResponse(product.Category)
|
||||
}
|
||||
|
||||
return &responses.ProductSimpleAdminResponse{
|
||||
ProductSimpleResponse: responses.ProductSimpleResponse{
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
Category: categoryResponse,
|
||||
IsPackage: product.IsPackage,
|
||||
},
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
}
|
||||
}
|
||||
|
||||
// convertToCategorySimpleResponse 转换为分类简单信息响应
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
|
||||
if category == nil {
|
||||
|
||||
@@ -1301,8 +1301,10 @@ func (s *StatisticsApplicationServiceImpl) getUserApiCallsStats(ctx context.Cont
|
||||
}
|
||||
|
||||
// 获取今日调用次数
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
tomorrow := today.Add(24 * time.Hour)
|
||||
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点
|
||||
todayCalls, err := s.getApiCallsCountByDateRange(ctx, userID, today, tomorrow)
|
||||
if err != nil {
|
||||
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)
|
||||
tomorrow := today.Add(24 * time.Hour)
|
||||
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点
|
||||
todayAmount, err := s.getWalletTransactionsByDateRange(ctx, userID, today, tomorrow)
|
||||
if err != nil {
|
||||
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)
|
||||
tomorrow := today.Add(24 * time.Hour)
|
||||
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点
|
||||
todayAmount, err := s.getRechargeRecordsByDateRange(ctx, userID, today, tomorrow)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
// 根据时间范围获取趋势数据
|
||||
// 根据时间范围获取认证趋势数据(基于is_certified字段)
|
||||
var trendData []map[string]interface{}
|
||||
if !startTime.IsZero() && !endTime.IsZero() {
|
||||
if period == "day" {
|
||||
trendData, err = s.userRepo.GetSystemDailyUserStats(ctx, startTime, endTime)
|
||||
trendData, err = s.userRepo.GetSystemDailyCertificationStats(ctx, startTime, endTime)
|
||||
} else if period == "month" {
|
||||
trendData, err = s.userRepo.GetSystemMonthlyUserStats(ctx, startTime, endTime)
|
||||
trendData, err = s.userRepo.GetSystemMonthlyCertificationStats(ctx, startTime, endTime)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Error("获取认证趋势数据失败", zap.Error(err))
|
||||
@@ -1698,16 +1704,35 @@ func (s *StatisticsApplicationServiceImpl) getCertificationStats(ctx context.Con
|
||||
// 默认获取最近7天的数据
|
||||
endDate := time.Now()
|
||||
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 {
|
||||
s.logger.Error("获取认证每日趋势失败", zap.Error(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{}{
|
||||
"total_certified": userStats.CertifiedUsers,
|
||||
"certified_today": userStats.TodayRegistrations, // 今日注册的用户
|
||||
"certified_today": certifiedToday, // 今日认证的用户数(基于is_certified字段)
|
||||
"success_rate": successRate,
|
||||
"daily_trend": trendData,
|
||||
}
|
||||
@@ -1723,9 +1748,11 @@ func (s *StatisticsApplicationServiceImpl) getSystemApiCallStats(ctx context.Con
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取今日API调用次数
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
tomorrow := today.Add(24 * time.Hour)
|
||||
// 获取今日API调用次数(东八时区)
|
||||
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点
|
||||
todayCalls, err := s.apiCallRepo.GetSystemCallsByDateRange(ctx, today, tomorrow)
|
||||
if err != nil {
|
||||
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)
|
||||
tomorrow := today.Add(24 * time.Hour)
|
||||
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点
|
||||
|
||||
todayConsumption, err := s.walletTransactionRepo.GetSystemAmountByDateRange(ctx, today, tomorrow)
|
||||
if err != nil {
|
||||
s.logger.Error("获取今日消费金额失败", zap.Error(err))
|
||||
@@ -2275,6 +2305,10 @@ func (s *StatisticsApplicationServiceImpl) AdminGetApiDomainStatistics(ctx conte
|
||||
s.logger.Error("解析开始日期失败", zap.Error(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 != "" {
|
||||
endTime, err = time.Parse("2006-01-02", endDate)
|
||||
@@ -2282,6 +2316,14 @@ func (s *StatisticsApplicationServiceImpl) AdminGetApiDomainStatistics(ctx conte
|
||||
s.logger.Error("解析结束日期失败", zap.Error(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调用统计数据
|
||||
@@ -2318,6 +2360,10 @@ func (s *StatisticsApplicationServiceImpl) AdminGetConsumptionDomainStatistics(c
|
||||
s.logger.Error("解析开始日期失败", zap.Error(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 != "" {
|
||||
endTime, err = time.Parse("2006-01-02", endDate)
|
||||
@@ -2325,6 +2371,14 @@ func (s *StatisticsApplicationServiceImpl) AdminGetConsumptionDomainStatistics(c
|
||||
s.logger.Error("解析结束日期失败", zap.Error(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)
|
||||
tomorrow := today.Add(24 * time.Hour)
|
||||
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点
|
||||
todayConsumption, err := s.walletTransactionRepo.GetSystemAmountByDateRange(ctx, today, tomorrow)
|
||||
if err != nil {
|
||||
s.logger.Error("获取今日消费金额失败", zap.Error(err))
|
||||
@@ -2370,7 +2426,7 @@ func (s *StatisticsApplicationServiceImpl) AdminGetConsumptionDomainStatistics(c
|
||||
defaultEndDate := time.Now()
|
||||
defaultStartDate := defaultEndDate.AddDate(0, 0, -7)
|
||||
consumptionTrend, err = s.walletTransactionRepo.GetSystemDailyStats(ctx, defaultStartDate, defaultEndDate)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
s.logger.Error("获取消费每日趋势失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
@@ -2406,6 +2462,10 @@ func (s *StatisticsApplicationServiceImpl) AdminGetRechargeDomainStatistics(ctx
|
||||
s.logger.Error("解析开始日期失败", zap.Error(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 != "" {
|
||||
endTime, err = time.Parse("2006-01-02", endDate)
|
||||
@@ -2413,6 +2473,14 @@ func (s *StatisticsApplicationServiceImpl) AdminGetRechargeDomainStatistics(ctx
|
||||
s.logger.Error("解析结束日期失败", zap.Error(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)
|
||||
tomorrow := today.Add(24 * time.Hour)
|
||||
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点
|
||||
todayRecharge, err := s.rechargeRecordRepo.GetSystemAmountByDateRange(ctx, today, tomorrow)
|
||||
if err != nil {
|
||||
s.logger.Error("获取今日充值金额失败", zap.Error(err))
|
||||
@@ -2716,15 +2786,15 @@ func (s *StatisticsApplicationServiceImpl) AdminGetTodayCertifiedEnterprises(ctx
|
||||
}
|
||||
|
||||
enterprise := map[string]interface{}{
|
||||
"id": cert.ID,
|
||||
"user_id": cert.UserID,
|
||||
"username": user.Username,
|
||||
"enterprise_name": enterpriseInfo.CompanyName,
|
||||
"legal_person_name": enterpriseInfo.LegalPersonName,
|
||||
"legal_person_phone": enterpriseInfo.LegalPersonPhone,
|
||||
"unified_social_code": enterpriseInfo.UnifiedSocialCode,
|
||||
"enterprise_address": enterpriseInfo.EnterpriseAddress,
|
||||
"certified_at": cert.CompletedAt.Format(time.RFC3339),
|
||||
"id": cert.ID,
|
||||
"user_id": cert.UserID,
|
||||
"username": user.Username,
|
||||
"enterprise_name": enterpriseInfo.CompanyName,
|
||||
"legal_person_name": enterpriseInfo.LegalPersonName,
|
||||
"legal_person_phone": enterpriseInfo.LegalPersonPhone,
|
||||
"unified_social_code": enterpriseInfo.UnifiedSocialCode,
|
||||
"enterprise_address": enterpriseInfo.EnterpriseAddress,
|
||||
"certified_at": cert.CompletedAt.Format(time.RFC3339),
|
||||
}
|
||||
enterprises = append(enterprises, enterprise)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ type Config struct {
|
||||
Zhicha ZhichaConfig `mapstructure:"zhicha"`
|
||||
Muzi MuziConfig `mapstructure:"muzi"`
|
||||
AliPay AliPayConfig `mapstructure:"alipay"`
|
||||
Wxpay WxpayConfig `mapstructure:"wxpay"`
|
||||
WechatMini WechatMiniConfig `mapstructure:"wechat_mini"`
|
||||
WechatH5 WechatH5Config `mapstructure:"wechat_h5"`
|
||||
Yushan YushanConfig `mapstructure:"yushan"`
|
||||
TianYanCha TianYanChaConfig `mapstructure:"tianyancha"`
|
||||
Alicloud AlicloudConfig `mapstructure:"alicloud"`
|
||||
@@ -429,6 +432,29 @@ type AliPayConfig struct {
|
||||
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 羽山配置
|
||||
type YushanConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
|
||||
@@ -3,6 +3,8 @@ package container
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"go.uber.org/fx"
|
||||
@@ -66,6 +68,7 @@ import (
|
||||
"tyapi-server/internal/shared/middleware"
|
||||
sharedOCR "tyapi-server/internal/shared/ocr"
|
||||
"tyapi-server/internal/shared/payment"
|
||||
"tyapi-server/internal/shared/pdf"
|
||||
"tyapi-server/internal/shared/resilience"
|
||||
"tyapi-server/internal/shared/saga"
|
||||
"tyapi-server/internal/shared/tracing"
|
||||
@@ -304,6 +307,16 @@ func NewContainer() *Container {
|
||||
}
|
||||
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 {
|
||||
return export.NewExportManager(logger)
|
||||
@@ -509,6 +522,11 @@ func NewContainer() *Container {
|
||||
finance_repo.NewGormAlipayOrderRepository,
|
||||
fx.As(new(domain_finance_repo.AlipayOrderRepository)),
|
||||
),
|
||||
// 微信订单仓储
|
||||
fx.Annotate(
|
||||
finance_repo.NewGormWechatOrderRepository,
|
||||
fx.As(new(domain_finance_repo.WechatOrderRepository)),
|
||||
),
|
||||
// 发票申请仓储
|
||||
fx.Annotate(
|
||||
finance_repo.NewGormInvoiceApplicationRepository,
|
||||
@@ -571,6 +589,11 @@ func NewContainer() *Container {
|
||||
article_repo.NewGormScheduledTaskRepository,
|
||||
fx.As(new(domain_article_repo.ScheduledTaskRepository)),
|
||||
),
|
||||
// 公告仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
article_repo.NewGormAnnouncementRepository,
|
||||
fx.As(new(domain_article_repo.AnnouncementRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
// API域仓储层
|
||||
@@ -675,6 +698,8 @@ func NewContainer() *Container {
|
||||
certification_service.NewEnterpriseInfoSubmitRecordService,
|
||||
// 文章领域服务
|
||||
article_service.NewArticleService,
|
||||
// 公告领域服务
|
||||
article_service.NewAnnouncementService,
|
||||
// 统计领域服务
|
||||
statistics_service.NewStatisticsAggregateService,
|
||||
statistics_service.NewStatisticsCalculationService,
|
||||
@@ -775,6 +800,7 @@ func NewContainer() *Container {
|
||||
cfg *config.Config,
|
||||
logger *zap.Logger,
|
||||
articleApplicationService article.ArticleApplicationService,
|
||||
announcementApplicationService article.AnnouncementApplicationService,
|
||||
apiApplicationService api_app.ApiApplicationService,
|
||||
walletService finance_services.WalletAggregateService,
|
||||
subscriptionService *product_services.ProductSubscriptionService,
|
||||
@@ -785,6 +811,7 @@ func NewContainer() *Container {
|
||||
redisAddr,
|
||||
logger,
|
||||
articleApplicationService,
|
||||
announcementApplicationService,
|
||||
apiApplicationService,
|
||||
walletService,
|
||||
subscriptionService,
|
||||
@@ -843,10 +870,13 @@ func NewContainer() *Container {
|
||||
fx.Annotate(
|
||||
func(
|
||||
aliPayClient *payment.AliPayService,
|
||||
wechatPayService *payment.WechatPayService,
|
||||
walletService finance_services.WalletAggregateService,
|
||||
rechargeRecordService finance_services.RechargeRecordService,
|
||||
walletTransactionRepo domain_finance_repo.WalletTransactionRepository,
|
||||
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
||||
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
||||
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
||||
userRepo domain_user_repo.UserRepository,
|
||||
txManager *shared_database.TransactionManager,
|
||||
logger *zap.Logger,
|
||||
@@ -855,10 +885,13 @@ func NewContainer() *Container {
|
||||
) finance.FinanceApplicationService {
|
||||
return finance.NewFinanceApplicationService(
|
||||
aliPayClient,
|
||||
wechatPayService,
|
||||
walletService,
|
||||
rechargeRecordService,
|
||||
walletTransactionRepo,
|
||||
alipayOrderRepo,
|
||||
wechatOrderRepo,
|
||||
rechargeRecordRepo,
|
||||
userRepo,
|
||||
txManager,
|
||||
logger,
|
||||
@@ -941,6 +974,23 @@ func NewContainer() *Container {
|
||||
},
|
||||
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(
|
||||
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处理器
|
||||
fx.Provide(
|
||||
// 用户HTTP处理器
|
||||
@@ -1005,6 +1111,15 @@ func NewContainer() *Container {
|
||||
) *handlers.ArticleHandler {
|
||||
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.NewArticleRoutes,
|
||||
// 公告路由
|
||||
routes.NewAnnouncementRoutes,
|
||||
// API路由
|
||||
routes.NewApiRoutes,
|
||||
// 统计路由
|
||||
@@ -1132,6 +1249,7 @@ func RegisterRoutes(
|
||||
productRoutes *routes.ProductRoutes,
|
||||
productAdminRoutes *routes.ProductAdminRoutes,
|
||||
articleRoutes *routes.ArticleRoutes,
|
||||
announcementRoutes *routes.AnnouncementRoutes,
|
||||
apiRoutes *routes.ApiRoutes,
|
||||
statisticsRoutes *routes.StatisticsRoutes,
|
||||
cfg *config.Config,
|
||||
@@ -1149,6 +1267,7 @@ func RegisterRoutes(
|
||||
productRoutes.Register(router)
|
||||
productAdminRoutes.Register(router)
|
||||
articleRoutes.Register(router)
|
||||
announcementRoutes.Register(router)
|
||||
statisticsRoutes.Register(router)
|
||||
|
||||
// 打印注册的路由信息
|
||||
|
||||
@@ -94,7 +94,7 @@ type JRZQ8203Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
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"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||
@@ -133,6 +133,13 @@ type QYGL23T7Req struct {
|
||||
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 {
|
||||
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"`
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
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 {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
@@ -213,6 +232,13 @@ type COMB298YReq struct {
|
||||
TimeRange string `json:"time_range" validate:"omitempty,validTimeRange"` // 非必填字段
|
||||
}
|
||||
|
||||
type COMBHZY2Req 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"`
|
||||
AuthorizationURL string `json:"authorization_url" validate:"required,authorization_url"`
|
||||
}
|
||||
|
||||
type COMB86PMReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
@@ -282,8 +308,9 @@ type IVYZ7F3AReq struct {
|
||||
}
|
||||
|
||||
type IVYZ3P9MReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
ReturnType string `json:"return_type" validate:"omitempty,oneof=1 2"`
|
||||
}
|
||||
|
||||
type IVYZ3A7FReq struct {
|
||||
@@ -291,12 +318,35 @@ type IVYZ3A7FReq struct {
|
||||
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 {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
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 行为数据查询请求参数
|
||||
type DWBG7F3AReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
@@ -307,26 +357,26 @@ type DWBG7F3AReq struct {
|
||||
// 新增的QYGL处理器DTO
|
||||
type QYGL5A3CReq struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
PageSize int `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PageNum int `json:"page_num" validate:"omitempty,min=1"`
|
||||
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||
}
|
||||
|
||||
type QYGL8B4DReq struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
PageSize int `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PageNum int `json:"page_num" validate:"omitempty,min=1"`
|
||||
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||
}
|
||||
|
||||
type QYGL9E2FReq struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
PageSize int `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PageNum int `json:"page_num" validate:"omitempty,min=1"`
|
||||
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||
}
|
||||
|
||||
type QYGL7C1AReq struct {
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
PageSize int `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PageNum int `json:"page_num" validate:"omitempty,min=1"`
|
||||
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||
}
|
||||
|
||||
type QYGL3F8EReq struct {
|
||||
@@ -340,6 +390,15 @@ type YYSY4F2EReq struct {
|
||||
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 {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
@@ -367,6 +426,31 @@ type FLXG9C1DReq struct {
|
||||
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 {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
@@ -380,7 +464,27 @@ type JRZQ3C7BReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
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 {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
@@ -390,9 +494,32 @@ type JRZQ8A2DReq struct {
|
||||
|
||||
// YYSY8F3AReq 行为数据查询请求参数
|
||||
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"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
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 {
|
||||
@@ -445,6 +572,62 @@ type QCXG9P1CReq struct {
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type QCXG8A3DReq struct {
|
||||
PlateNo string `json:"plate_no" validate:"required"`
|
||||
PlateType string `json:"plate_type" validate:"omitempty,oneof=01 02"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type QCXG6B4EReq struct {
|
||||
VINCode string `json:"vin_code" validate:"required"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type QYGL2B5CReq struct {
|
||||
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
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 {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type JRZQ1E7BReq struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
type JRZQ9E2AReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
@@ -499,9 +682,7 @@ type FLXG7E8FReq struct {
|
||||
}
|
||||
|
||||
type QYGL5F6AReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type IVYZ6G7HReq struct {
|
||||
@@ -515,6 +696,11 @@ type IVYZ8I9JReq struct {
|
||||
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 {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package entities
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
@@ -18,14 +20,86 @@ const (
|
||||
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用户(聚合根)
|
||||
type ApiUser struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(64)" json:"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"`
|
||||
SecretKey string `gorm:"type:varchar(128);not null" json:"secret_key"`
|
||||
Status string `gorm:"type:varchar(20);not null;default:'normal'" json:"status"`
|
||||
WhiteList []string `gorm:"type:json;serializer:json;default:'[]'" json:"white_list"` // 支持多个白名单
|
||||
ID string `gorm:"primaryKey;type:varchar(64)" json:"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"`
|
||||
SecretKey string `gorm:"type:varchar(128);not null" json:"secret_key"`
|
||||
Status string `gorm:"type:varchar(20);not null;default:'normal'" json:"status"`
|
||||
WhiteList WhiteList `gorm:"type:json;default:'[]'" json:"white_list"` // 支持多个白名单,包含IP和添加时间,支持向后兼容
|
||||
|
||||
// 余额预警配置
|
||||
BalanceAlertEnabled bool `gorm:"default:true" json:"balance_alert_enabled" comment:"是否启用余额预警"`
|
||||
@@ -41,7 +115,7 @@ type ApiUser struct {
|
||||
// IsWhiteListed 校验IP/域名是否在白名单
|
||||
func (u *ApiUser) IsWhiteListed(target string) bool {
|
||||
for _, w := range u.WhiteList {
|
||||
if w == target {
|
||||
if w.IPAddress == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -77,7 +151,7 @@ func NewApiUser(userId string, defaultAlertEnabled bool, defaultAlertThreshold f
|
||||
AccessId: accessId,
|
||||
SecretKey: secretKey,
|
||||
Status: ApiUserStatusNormal,
|
||||
WhiteList: []string{},
|
||||
WhiteList: WhiteList{},
|
||||
BalanceAlertEnabled: defaultAlertEnabled,
|
||||
BalanceAlertThreshold: defaultAlertThreshold,
|
||||
}, nil
|
||||
@@ -90,12 +164,12 @@ func (u *ApiUser) Freeze() {
|
||||
func (u *ApiUser) Unfreeze() {
|
||||
u.Status = ApiUserStatusNormal
|
||||
}
|
||||
func (u *ApiUser) UpdateWhiteList(list []string) {
|
||||
u.WhiteList = list
|
||||
func (u *ApiUser) UpdateWhiteList(list []WhiteListItem) {
|
||||
u.WhiteList = WhiteList(list)
|
||||
}
|
||||
|
||||
// AddToWhiteList 新增白名单项(防御性校验)
|
||||
func (u *ApiUser) AddToWhiteList(entry string) error {
|
||||
func (u *ApiUser) AddToWhiteList(entry string, remark string) error {
|
||||
if len(u.WhiteList) >= 10 {
|
||||
return errors.New("白名单最多只能有10个")
|
||||
}
|
||||
@@ -103,27 +177,31 @@ func (u *ApiUser) AddToWhiteList(entry string) error {
|
||||
return errors.New("非法IP")
|
||||
}
|
||||
for _, w := range u.WhiteList {
|
||||
if w == entry {
|
||||
if w.IPAddress == entry {
|
||||
return errors.New("白名单已存在")
|
||||
}
|
||||
}
|
||||
u.WhiteList = append(u.WhiteList, entry)
|
||||
u.WhiteList = append(u.WhiteList, WhiteListItem{
|
||||
IPAddress: entry,
|
||||
AddedAt: time.Now(),
|
||||
Remark: remark,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate GORM钩子:更新前确保WhiteList不为nil
|
||||
func (u *ApiUser) BeforeUpdate(tx *gorm.DB) error {
|
||||
if u.WhiteList == nil {
|
||||
u.WhiteList = []string{}
|
||||
u.WhiteList = WhiteList{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveFromWhiteList 删除白名单项
|
||||
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 {
|
||||
if w != entry {
|
||||
if w.IPAddress != entry {
|
||||
newList = append(newList, w)
|
||||
}
|
||||
}
|
||||
@@ -216,9 +294,9 @@ func (u *ApiUser) Validate() error {
|
||||
if len(u.WhiteList) > 10 {
|
||||
return errors.New("白名单最多只能有10个")
|
||||
}
|
||||
for _, ip := range u.WhiteList {
|
||||
if net.ParseIP(ip) == nil {
|
||||
return errors.New("白名单项必须为合法IP地址: " + ip)
|
||||
for _, item := range u.WhiteList {
|
||||
if net.ParseIP(item.IPAddress) == nil {
|
||||
return errors.New("白名单项必须为合法IP地址: " + item.IPAddress)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -259,7 +337,26 @@ func (c *ApiUser) BeforeCreate(tx *gorm.DB) error {
|
||||
c.ID = uuid.New().String()
|
||||
}
|
||||
if c.WhiteList == nil {
|
||||
c.WhiteList = []string{}
|
||||
c.WhiteList = WhiteList{}
|
||||
}
|
||||
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调用次数
|
||||
CountByUserId(ctx context.Context, userId string) (int64, error)
|
||||
|
||||
// 新增:根据用户ID和产品ID统计API调用次数
|
||||
CountByUserIdAndProductId(ctx context.Context, userId string, productId string) (int64, error)
|
||||
|
||||
// 新增:根据TransactionID查询
|
||||
FindByTransactionId(ctx context.Context, transactionId string) (*entities.ApiCall, error)
|
||||
|
||||
|
||||
@@ -105,7 +105,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"FLXG9C1D": flxg.ProcessFLXG9C1DRequest,
|
||||
"FLXG2E8F": flxg.ProcessFLXG2E8FRequest,
|
||||
"FLXG7E8F": flxg.ProcessFLXG7E8FRequest,
|
||||
|
||||
"FLXG3A9B": flxg.ProcessFLXG3A9BRequest,
|
||||
"FLXGK5D2": flxg.ProcessFLXGK5D2Request,
|
||||
// JRZQ系列处理器
|
||||
"JRZQ8203": jrzq.ProcessJRZQ8203Request,
|
||||
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
|
||||
@@ -123,6 +124,15 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"JRZQ8B3C": jrzq.ProcessJRZQ8B3CRequest,
|
||||
"JRZQ9D4E": jrzq.ProcessJRZQ9D4ERequest,
|
||||
"JRZQ0L85": jrzq.ProcessJRZQ0L85Request,
|
||||
"JRZQ2F8A": jrzq.ProcessJRZQ2F8ARequest,
|
||||
"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系列处理器
|
||||
"QYGL8261": qygl.ProcessQYGL8261Request,
|
||||
@@ -141,6 +151,12 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"QYGL4B2E": qygl.ProcessQYGL4B2ERequest, // 税收违法
|
||||
"COMENT01": qygl.ProcessCOMENT01Request, // 企业风险报告
|
||||
"QYGL5F6A": qygl.ProcessQYGL5F6ARequest, // 企业相关查询
|
||||
"QYGL2B5C": qygl.ProcessQYGL2B5CRequest, // 企业联系人实际经营地址
|
||||
"QYGL6S1B": qygl.ProcessQYGL6S1BRequest, //董监高司法综合信息核验
|
||||
"QYGL9T1Q": qygl.ProcessQYGL9T1QRequest, //全国企业借贷意向验证查询_V1
|
||||
"QYGL5A9T": qygl.ProcessQYGL5A9TRequest, //全国企业各类工商风险统计数量查询
|
||||
"QYGL2S0W": qygl.ProcessQYGL2S0WRequest, //失信被执行企业个人查询
|
||||
"QYGL5CMP": qygl.ProcessQYGL5CMPRequest, //企业五要素验证
|
||||
|
||||
// YYSY系列处理器
|
||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||
@@ -159,6 +175,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"YYSY8C2D": yysy.ProcessYYSY8C2DRequest,
|
||||
"YYSY7D3E": yysy.ProcessYYSY7D3ERequest,
|
||||
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
|
||||
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest,
|
||||
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest,
|
||||
|
||||
// IVYZ系列处理器
|
||||
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
|
||||
@@ -182,13 +200,23 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"IVYZ81NC": ivyz.ProcessIVYZ81NCRequest,
|
||||
"IVYZ6G7H": ivyz.ProcessIVYZ6G7HRequest,
|
||||
"IVYZ8I9J": ivyz.ProcessIVYZ8I9JRequest,
|
||||
"IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest,
|
||||
"IVYZ2C1P": ivyz.ProcessIVYZ2C1PRequest,
|
||||
"IVYZP2Q6": ivyz.ProcessIVYZP2Q6Request,
|
||||
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
|
||||
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
|
||||
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
|
||||
|
||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||
"COMBHZY2": comb.ProcessCOMBHZY2Request, // 自定义处理:生成合规报告
|
||||
"COMBWD01": comb.ProcessCOMBWD01Request, // 自定义处理:将返回结构从数组改为对象
|
||||
|
||||
// QCXG系列处理器
|
||||
"QCXG7A2B": qcxg.ProcessQCXG7A2BRequest,
|
||||
"QCXG9P1C": qcxg.ProcessQCXG9P1CRequest,
|
||||
"QCXG8A3D": qcxg.ProcessQCXG8A3DRequest,
|
||||
"QCXG6B4E": qcxg.ProcessQCXG6B4ERequest,
|
||||
|
||||
// DWBG系列处理器 - 多维报告
|
||||
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
|
||||
|
||||
@@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
repo "tyapi-server/internal/domains/api/repositories"
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
type ApiUserAggregateService interface {
|
||||
CreateApiUser(ctx context.Context, apiUserId 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
|
||||
FreezeApiUser(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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = apiUser.AddToWhiteList(entry)
|
||||
err = apiUser.AddToWhiteList(entry, remark)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,7 +100,6 @@ func (s *ApiUserAggregateServiceImpl) UnfreezeApiUser(ctx context.Context, apiUs
|
||||
return s.repo.Update(ctx, apiUser)
|
||||
}
|
||||
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) LoadApiUserByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error) {
|
||||
return s.repo.FindByAccessId(ctx, accessId)
|
||||
}
|
||||
@@ -100,12 +109,12 @@ func (s *ApiUserAggregateServiceImpl) LoadApiUserByUserId(ctx context.Context, a
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
apiUser.WhiteList = entities.WhiteList{}
|
||||
}
|
||||
|
||||
|
||||
return apiUser, nil
|
||||
}
|
||||
|
||||
@@ -117,10 +126,10 @@ func (s *ApiUserAggregateServiceImpl) SaveApiUser(ctx context.Context, apiUser *
|
||||
if exists != nil {
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
apiUser.WhiteList = []entities.WhiteListItem{}
|
||||
}
|
||||
return s.repo.Update(ctx, apiUser)
|
||||
} else {
|
||||
return s.repo.Create(ctx, apiUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"JRZQ0A03": &dto.JRZQ0A03Req{},
|
||||
"JRZQ4AA8": &dto.JRZQ4AA8Req{},
|
||||
"JRZQ8203": &dto.JRZQ8203Req{},
|
||||
"JRZQDBCE": &dto.JRZQDBCEReq{},
|
||||
"JRZQDCBE": &dto.JRZQDCBEReq{},
|
||||
"QYGL2ACD": &dto.QYGL2ACDReq{},
|
||||
"QYGL6F2D": &dto.QYGL6F2DReq{},
|
||||
"QYGL45BD": &dto.QYGL45BDReq{},
|
||||
@@ -113,7 +113,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"YYSY4B21": &dto.YYSY4B21Req{},
|
||||
"YYSY6F2E": &dto.YYSY6F2EReq{},
|
||||
"YYSY09CD": &dto.YYSY09CDReq{},
|
||||
"IVYZ0b03": &dto.IVYZ0b03Req{},
|
||||
"IVYZ0B03": &dto.IVYZ0B03Req{},
|
||||
"YYSYBE08": &dto.YYSYBE08Req{},
|
||||
"YYSYD50F": &dto.YYSYD50FReq{},
|
||||
"YYSYF7DB": &dto.YYSYF7DBReq{},
|
||||
@@ -155,6 +155,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"IVYZ3P9M": &dto.IVYZ3P9MReq{},
|
||||
"IVYZ3A7F": &dto.IVYZ3A7FReq{},
|
||||
"IVYZ9D2E": &dto.IVYZ9D2EReq{},
|
||||
"IVYZ9K2L": &dto.IVYZ9K2LReq{},
|
||||
"DWBG7F3A": &dto.DWBG7F3AReq{},
|
||||
"YYSY8F3A": &dto.YYSY8F3AReq{},
|
||||
"QCXG9P1C": &dto.QCXG9P1CReq{},
|
||||
@@ -171,6 +172,33 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"IVYZ6G7H": &dto.IVYZ6G7HReq{},
|
||||
"IVYZ8I9J": &dto.IVYZ8I9JReq{},
|
||||
"JRZQ0L85": &dto.JRZQ0L85Req{},
|
||||
"COMBHZY2": &dto.COMBHZY2Req{}, // 自此无imp11.28
|
||||
"QCXG8A3D": &dto.QCXG8A3DReq{},
|
||||
"QCXG6B4E": &dto.QCXG6B4EReq{},
|
||||
"QYGL2B5C": &dto.QYGL2B5CReq{},
|
||||
"JRZQ2F8A": &dto.JRZQ2F8AReq{},
|
||||
"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
|
||||
@@ -270,6 +298,8 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
|
||||
frontendRules = append(frontendRules, "姓名格式")
|
||||
case rule == "validUSCI":
|
||||
frontendRules = append(frontendRules, "统一社会信用代码格式")
|
||||
case rule == "validEnterpriseName" || rule == "enterprise_name":
|
||||
frontendRules = append(frontendRules, "企业名称格式")
|
||||
case rule == "validBankCard":
|
||||
frontendRules = append(frontendRules, "银行卡号格式")
|
||||
case rule == "validDate":
|
||||
@@ -286,10 +316,13 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
|
||||
frontendRules = append(frontendRules, "返回链接格式")
|
||||
case rule == "validAuthorizationURL":
|
||||
frontendRules = append(frontendRules, "授权链接格式")
|
||||
case rule == "validBase64Image":
|
||||
frontendRules = append(frontendRules, "Base64图片格式(JPG、BMP、PNG)")
|
||||
case strings.HasPrefix(rule, "oneof="):
|
||||
values := strings.TrimPrefix(rule, "oneof=")
|
||||
frontendRules = append(frontendRules, "可选值: "+values)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return strings.Join(frontendRules, "、")
|
||||
@@ -315,6 +348,8 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
|
||||
return "url"
|
||||
} else if strings.Contains(validation, "可选值") {
|
||||
return "select"
|
||||
} else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "base64") {
|
||||
return "textarea"
|
||||
}
|
||||
return "text"
|
||||
case reflect.Int64:
|
||||
@@ -356,6 +391,14 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
||||
"page_size": "每页数量",
|
||||
"use_scenario": "使用场景",
|
||||
"auth_authorize_file_code": "授权文件编码",
|
||||
"plate_no": "车牌号",
|
||||
"plate_type": "号牌类型",
|
||||
"vin_code": "车辆识别代号VIN码",
|
||||
"return_type": "返回类型",
|
||||
"photo_data": "人脸图片",
|
||||
"owner_type": "企业主类型",
|
||||
"type": "查询类型",
|
||||
"query_reason_id": "查询原因ID",
|
||||
}
|
||||
|
||||
if label, exists := labelMap[jsonTag]; exists {
|
||||
@@ -393,6 +436,14 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
||||
"page_size": "10",
|
||||
"use_scenario": "1",
|
||||
"auth_authorize_file_code": "AUTH123456",
|
||||
"plate_no": "京A12345",
|
||||
"plate_type": "01",
|
||||
"vin_code": "LSGBF53M8DS123456",
|
||||
"return_type": "1",
|
||||
"photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"ownerType": "1",
|
||||
"type": "per",
|
||||
"query_reason_id": "1",
|
||||
}
|
||||
|
||||
if example, exists := exampleMap[jsonTag]; exists {
|
||||
@@ -439,6 +490,14 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
||||
"page_size": "请输入每页数量(1-100)",
|
||||
"use_scenario": "请选择使用场景",
|
||||
"auth_authorize_file_code": "请输入授权文件编码",
|
||||
"plate_no": "请输入车牌号",
|
||||
"plate_type": "请选择号牌类型(01或02)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码",
|
||||
"return_type": "请选择返回类型",
|
||||
"photo_data": "请输入base64编码的人脸图片(支持JPG、BMP、PNG格式)",
|
||||
"ownerType": "请选择企业主类型",
|
||||
"type": "请选择查询类型",
|
||||
"query_reason_id": "请选择查询原因ID",
|
||||
}
|
||||
|
||||
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
||||
@@ -464,7 +523,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
||||
func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation string) string {
|
||||
descMap := map[string]string{
|
||||
"mobile_no": "请输入11位手机号码",
|
||||
"id_card": "请输入18位身份证号码",
|
||||
"id_card": "请输入18位身份证号码最后一位如是字母请大写",
|
||||
"name": "请输入真实姓名",
|
||||
"man_name": "请输入男方真实姓名",
|
||||
"woman_name": "请输入女方真实姓名",
|
||||
@@ -487,6 +546,14 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
||||
"page_size": "请输入每页数量,范围1-100",
|
||||
"use_scenario": "使用场景:1-信贷审核;2-保险评估;3-招聘背景调查;4-其他业务场景;99-其他",
|
||||
"auth_authorize_file_code": "请输入授权文件编码",
|
||||
"plate_no": "请输入车牌号",
|
||||
"plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)",
|
||||
"vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)",
|
||||
"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 {
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package comb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/shared/logger"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProcessCOMBHZY2Request 处理 COMBHZY2 组合包请求
|
||||
func ProcessCOMBHZY2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
log := logger.GetGlobalLogger()
|
||||
|
||||
var req dto.COMBHZY2Req
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMBHZY2")
|
||||
if err != nil {
|
||||
log.Error("COMBHZY2组合包服务调用失败",
|
||||
zap.Error(err),
|
||||
zap.String("api_code", "COMBHZY2"),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
log.Error("COMBHZY2构建源数据上下文失败",
|
||||
zap.Error(err),
|
||||
zap.String("api_code", "COMBHZY2"),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
report := buildTargetReport(ctx, sourceCtx)
|
||||
|
||||
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(ctx context.Context, result *processors.CombinedResult) (*sourceContext, error) {
|
||||
log := logger.GetGlobalLogger()
|
||||
|
||||
if result == nil {
|
||||
log.Error("组合包响应为空", zap.String("api_code", "COMBHZY2"))
|
||||
return nil, errors.New("组合包响应为空")
|
||||
}
|
||||
|
||||
src := sourceFile{Responses: make([]sourceResponse, 0, len(result.Responses))}
|
||||
successCount := 0
|
||||
failedCount := 0
|
||||
|
||||
for _, resp := range result.Responses {
|
||||
if !resp.Success {
|
||||
log.Warn("子产品调用失败,跳过",
|
||||
zap.String("api_code", resp.ApiCode),
|
||||
zap.String("error", resp.Error),
|
||||
zap.String("parent_api_code", "COMBHZY2"),
|
||||
)
|
||||
failedCount++
|
||||
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)
|
||||
if err != nil {
|
||||
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{
|
||||
ApiCode: resp.ApiCode,
|
||||
Data: raw,
|
||||
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 {
|
||||
log.Error("组合包子产品全部调用失败",
|
||||
zap.Int("总数量", len(result.Responses)),
|
||||
zap.String("api_code", "COMBHZY2"),
|
||||
)
|
||||
return nil, errors.New("组合包子产品全部调用失败")
|
||||
}
|
||||
|
||||
return buildSourceContext(ctx, src)
|
||||
}
|
||||
1992
internal/domains/api/services/processors/comb/combhzy2_transform.go
Normal file
1992
internal/domains/api/services/processors/comb/combhzy2_transform.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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处理方法 - 谛听多维报告
|
||||
func ProcessDWBG8B4DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.DWBG8B4DReq
|
||||
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/yushan"
|
||||
)
|
||||
|
||||
// ProcessFLXG0687Request FLXG0687 API处理方法
|
||||
@@ -21,15 +21,14 @@ func ProcessFLXG0687Request(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"keyWord": paramsDto.IDCard,
|
||||
"type": 3,
|
||||
"keyWord": paramsDto.IDCard,
|
||||
"type": 3,
|
||||
}
|
||||
|
||||
respBytes, err := deps.YushanService.CallAPI(ctx, "RIS031", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
if errors.Is(err, yushan.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
|
||||
@@ -33,8 +33,8 @@ func ProcessFLXG0V3Bequest(ctx context.Context, params []byte, deps *processors.
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id_card": encryptedIDCard,
|
||||
"name": encryptedName,
|
||||
"id_card": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@ func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
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"
|
||||
)
|
||||
|
||||
// ProcessFLXG5876Request FLXG5876 API处理方法
|
||||
// ProcessFLXG5876Request FLXG5876 易诉人识别API处理方法
|
||||
func ProcessFLXG5876Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG5876Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
@@ -42,4 +42,4 @@ func ProcessFLXG5876Request(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||
reqData := map[string]interface{}{
|
||||
"name": paramsDto.Name,
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG970FRequest FLXG970F API处理方法
|
||||
// ProcessFLXG970FRequest FLXG970F 风险人员核验API处理方法
|
||||
func ProcessFLXG970FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG970FReq
|
||||
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{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"cardNo": encryptedIDCard,
|
||||
"name": encryptedName,
|
||||
"cardNo": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -48,4 +48,4 @@ func ProcessFLXG970FRequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/yushan"
|
||||
)
|
||||
|
||||
// ProcessFLXGBC21Request FLXGbc21 API处理方法
|
||||
@@ -21,14 +21,13 @@ func ProcessFLXGBC21Request(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"mobile": paramsDto.MobileNo,
|
||||
}
|
||||
|
||||
respBytes, err := deps.YushanService.CallAPI(ctx, "MOB032", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
if errors.Is(err, yushan.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
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 {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
@@ -33,7 +35,7 @@ func ProcessFLXGCA3DRequest(ctx context.Context, params []byte, deps *processors
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"name": encryptedName,
|
||||
"id_card": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ func ProcessFLXGDEA9Request(ctx context.Context, params []byte, deps *processors
|
||||
if err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||
}
|
||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
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处理方法
|
||||
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 {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
@@ -48,4 +48,4 @@ func ProcessIVYZ0B03Request(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -31,9 +31,16 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 处理 returnType 参数,默认为 "1"
|
||||
returnType := paramsDto.ReturnType
|
||||
if returnType == "" {
|
||||
returnType = "1"
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"realName": encryptedName,
|
||||
"certCode": encryptedCertCode,
|
||||
"realName": encryptedName,
|
||||
"certCode": encryptedCertCode,
|
||||
"returnType": returnType,
|
||||
}
|
||||
|
||||
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", reqData)
|
||||
|
||||
@@ -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)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
const maxRetries = 5
|
||||
var respBytes []byte
|
||||
|
||||
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||
var err error
|
||||
respBytes, err = deps.WestDexService.CallAPI(ctx, "G09XM02", reqData)
|
||||
if err == nil {
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
// 如果不是数据源异常,直接返回错误
|
||||
if !errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 如果是最后一次尝试,返回错误
|
||||
if attempt == maxRetries {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
}
|
||||
|
||||
// 立即重试,不等待
|
||||
}
|
||||
|
||||
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,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"
|
||||
)
|
||||
|
||||
// ProcessJRZQ1E7BRequest JRZQ1E7B API处理方法 - 消费交易特征
|
||||
func ProcessJRZQ1E7BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQ1E7BReq
|
||||
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, "ZCI034", 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,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"
|
||||
)
|
||||
|
||||
// ProcessJRZQ2F8ARequest JRZQ2F8A API处理方法 - 探针A
|
||||
func ProcessJRZQ2F8ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQ2F8AReq
|
||||
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, "ZCI009", 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{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"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"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
@@ -37,9 +36,9 @@ func ProcessJRZQ4B6CRequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
|
||||
@@ -54,4 +54,4 @@ func ProcessJRZQ8203Request(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 需要的字段名
|
||||
reqData := map[string]interface{}{
|
||||
"phoneNumber": paramsDto.MobileNo,
|
||||
"idCardNum": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
"idCardNum": paramsDto.IDCard,
|
||||
"name": paramsDto.Name,
|
||||
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// ProcessJRZQDCBERequest JRZQDCBE API处理方法
|
||||
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 {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
@@ -43,10 +43,10 @@ func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idcard": encryptedIDCard,
|
||||
"mobile": encryptedMobileNo,
|
||||
"acc_no": encryptedBankCard,
|
||||
"name": encryptedName,
|
||||
"idcard": encryptedIDCard,
|
||||
"mobile": encryptedMobileNo,
|
||||
"acc_no": encryptedBankCard,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -60,4 +60,4 @@ func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessQCXG6B4ERequest QCXG6B4E API处理方法 - 车辆出险记录查验
|
||||
func ProcessQCXG6B4ERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG6B4EReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"vin": paramsDto.VINCode,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI049", 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
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/yushan"
|
||||
)
|
||||
|
||||
// ProcessQCXG7A2BRequest QCXG7A2B API处理方法
|
||||
@@ -27,7 +27,7 @@ func ProcessQCXG7A2BRequest(ctx context.Context, params []byte, deps *processors
|
||||
|
||||
respBytes, err := deps.YushanService.CallAPI(ctx, "CAR061", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
if errors.Is(err, yushan.ErrDatasource) {
|
||||
return nil, errors.Join(processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package qcxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
)
|
||||
|
||||
// ProcessQCXG8A3DRequest QCXG8A3D API处理方法 - 车辆七项信息核验
|
||||
func ProcessQCXG8A3DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QCXG8A3DReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"plate": paramsDto.PlateNo,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
// 如果传了车牌类型,则添加到请求数据中
|
||||
if paramsDto.PlateType != "" {
|
||||
reqData["vehType"] = paramsDto.PlateType
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI048", 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 (
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// ProcessQYGL23T7Request QYGL23T7 API处理方法 - 企业三要素验证
|
||||
// ProcessQYGL23T7Request QYGL23T7 API处理方法 - 企业四要素验证
|
||||
func ProcessQYGL23T7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGL23T7Req
|
||||
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{}{
|
||||
"data": map[string]interface{}{
|
||||
"entname": encryptedEntName,
|
||||
"realname": encryptedLegalPerson,
|
||||
"idcard": encryptedEntCode,
|
||||
"name": encryptedEntName,
|
||||
"oper_name": encryptedLegalPerson,
|
||||
"keyword": encryptedEntCode,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -54,4 +54,4 @@ func ProcessQYGL2ACDRequest(ctx context.Context, params []byte, deps *processors
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
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/zhicha"
|
||||
)
|
||||
|
||||
// ProcessQYGL2B5CRequest QYGL2B5C API处理方法 - 企业联系人实际经营地址
|
||||
func ProcessQYGL2B5CRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGL2B5CReq
|
||||
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{}{
|
||||
"keyword": keyword,
|
||||
"authorized": paramsDto.Authorized,
|
||||
}
|
||||
|
||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI050", 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,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
|
||||
if pageSize == 0 {
|
||||
pageSize = 20
|
||||
pageSize = int64(20)
|
||||
}
|
||||
pageNum := paramsDto.PageNum
|
||||
if pageNum == 0 {
|
||||
pageNum = 1
|
||||
pageNum = int64(1)
|
||||
}
|
||||
|
||||
// 构建API调用参数
|
||||
apiParams := map[string]string{
|
||||
"keyword": paramsDto.EntCode,
|
||||
"pageSize": strconv.Itoa(pageSize),
|
||||
"pageNum": strconv.Itoa(pageNum),
|
||||
"pageSize": strconv.FormatInt(pageSize, 10),
|
||||
"pageNum": strconv.FormatInt(pageNum, 10),
|
||||
}
|
||||
|
||||
// 调用天眼查API - 税收违法
|
||||
|
||||
@@ -24,18 +24,18 @@ func ProcessQYGL5A3CRequest(ctx context.Context, params []byte, deps *processors
|
||||
// 设置默认值
|
||||
pageSize := paramsDto.PageSize
|
||||
if pageSize == 0 {
|
||||
pageSize = 20
|
||||
pageSize = int64(20)
|
||||
}
|
||||
pageNum := paramsDto.PageNum
|
||||
if pageNum == 0 {
|
||||
pageNum = 1
|
||||
pageNum = int64(1)
|
||||
}
|
||||
|
||||
// 构建API调用参数
|
||||
apiParams := map[string]string{
|
||||
"keyword": paramsDto.EntCode,
|
||||
"pageSize": strconv.Itoa(pageSize),
|
||||
"pageNum": strconv.Itoa(pageNum),
|
||||
"pageSize": strconv.FormatInt(pageSize, 10),
|
||||
"pageNum": strconv.FormatInt(pageNum, 10),
|
||||
}
|
||||
|
||||
// 调用天眼查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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user