Compare commits
30 Commits
report-rou
...
8098c13de3
| Author | SHA1 | Date | |
|---|---|---|---|
| 8098c13de3 | |||
| e61f03a2dd | |||
| 4fcf370dcd | |||
| b0ed5b04ee | |||
| 269ff38604 | |||
| 23909c44f1 | |||
| aabe34b7d5 | |||
| c262894372 | |||
| 39f799bc41 | |||
| 1d4411a940 | |||
| fe44b452e3 | |||
| f1ec9bfe7f | |||
| a70e736cdd | |||
| 53d2c75a9c | |||
| 0bfa7b4f50 | |||
| e2e729eec8 | |||
| 5f7fb43804 | |||
| 89c5c0f9ad | |||
| 6c949a4a1c | |||
| 8556e7331d | |||
| 311d7a9b01 | |||
| ce45ce3ed0 | |||
| 34e2c1bc41 | |||
| 2618105140 | |||
| 6b41f3833a | |||
| 446a5c7661 | |||
| 7f8554fa12 | |||
| 65a61d0336 | |||
| 8dd6f71baf | |||
| e96653751d |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -40,8 +40,13 @@ internal/shared/pdf/fonts/*.ttf
|
|||||||
internal/shared/pdf/fonts/*.ttc
|
internal/shared/pdf/fonts/*.ttc
|
||||||
internal/shared/pdf/fonts/*.otf
|
internal/shared/pdf/fonts/*.otf
|
||||||
|
|
||||||
|
# Pure Component 目录(用于持久化存储,不进行版本控制)
|
||||||
|
resources/Pure_Component/
|
||||||
|
|
||||||
# 其他
|
# 其他
|
||||||
*.exe
|
*.exe
|
||||||
|
*.exe*
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
cmd/api/__debug_bin*
|
||||||
@@ -54,7 +54,8 @@ COPY config.yaml .
|
|||||||
COPY configs/ ./configs/
|
COPY configs/ ./configs/
|
||||||
|
|
||||||
# 复制资源文件(直接从构建上下文复制,与配置文件一致)
|
# 复制资源文件(直接从构建上下文复制,与配置文件一致)
|
||||||
COPY resources ./resources
|
COPY resources/etc ./resources/etc
|
||||||
|
COPY resources/pdf ./resources/pdf
|
||||||
|
|
||||||
# 暴露端口
|
# 暴露端口
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|||||||
40
config.yaml
40
config.yaml
@@ -124,7 +124,7 @@ sms:
|
|||||||
access_key_id: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
access_key_id: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
||||||
access_key_secret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
|
access_key_secret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
|
||||||
endpoint_url: "dysmsapi.aliyuncs.com"
|
endpoint_url: "dysmsapi.aliyuncs.com"
|
||||||
sign_name: "天远查"
|
sign_name: "海南海宇大数据"
|
||||||
template_code: "SMS_302641455"
|
template_code: "SMS_302641455"
|
||||||
code_length: 6
|
code_length: 6
|
||||||
expire_time: 5m
|
expire_time: 5m
|
||||||
@@ -437,7 +437,7 @@ zhicha:
|
|||||||
# 🌐 木子数据配置
|
# 🌐 木子数据配置
|
||||||
# ===========================================
|
# ===========================================
|
||||||
muzi:
|
muzi:
|
||||||
url: "https://carv.m0101.com/magic/carv/pubin/service/academic"
|
url: "https://carv.m0101.com/magic/carv/pubin/service"
|
||||||
app_id: "713014138179585"
|
app_id: "713014138179585"
|
||||||
app_secret: "bd4090ac652c404c80e90ebbdcd6ba1d"
|
app_secret: "bd4090ac652c404c80e90ebbdcd6ba1d"
|
||||||
timeout: 60s
|
timeout: 60s
|
||||||
@@ -494,3 +494,39 @@ xingwei:
|
|||||||
max_backups: 5
|
max_backups: 5
|
||||||
max_age: 30
|
max_age: 30
|
||||||
compress: true
|
compress: true
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# ✨ 极光配置
|
||||||
|
# ===========================================
|
||||||
|
jiguang:
|
||||||
|
url: "http://api.jiguangcloud.com/jg-open-api-gateway/api"
|
||||||
|
app_id: "66ZA28w5" # 请替换为实际的 appId
|
||||||
|
app_secret: "e5261d0f6f003ae7b9fc1b0255b21761bb618d56" # 请替换为实际的 appSecret
|
||||||
|
sign_method: "hmac" # 签名方法:md5 或 hmac,默认 hmac
|
||||||
|
timeout: 60s # 请求超时时间,默认 60 秒
|
||||||
|
|
||||||
|
# 极光日志配置
|
||||||
|
logging:
|
||||||
|
enabled: true
|
||||||
|
log_dir: "logs/external_services"
|
||||||
|
service_name: "jiguang"
|
||||||
|
use_daily: true
|
||||||
|
enable_level_separation: true
|
||||||
|
|
||||||
|
# 各级别配置
|
||||||
|
level_configs:
|
||||||
|
info:
|
||||||
|
max_size: 100
|
||||||
|
max_backups: 5
|
||||||
|
max_age: 30
|
||||||
|
compress: true
|
||||||
|
error:
|
||||||
|
max_size: 200
|
||||||
|
max_backups: 10
|
||||||
|
max_age: 90
|
||||||
|
compress: true
|
||||||
|
warn:
|
||||||
|
max_size: 100
|
||||||
|
max_backups: 5
|
||||||
|
max_age: 30
|
||||||
|
compress: true
|
||||||
|
|||||||
@@ -122,38 +122,38 @@ wallet:
|
|||||||
# 🚦 频率限制配置 - 生产环境
|
# 🚦 频率限制配置 - 生产环境
|
||||||
# ===========================================
|
# ===========================================
|
||||||
daily_ratelimit:
|
daily_ratelimit:
|
||||||
max_requests_per_day: 50000 # 生产环境每日最大请求次数
|
max_requests_per_day: 50000 # 生产环境每日最大请求次数
|
||||||
max_requests_per_ip: 5000 # 生产环境每个IP每日最大请求次数
|
max_requests_per_ip: 5000 # 生产环境每个IP每日最大请求次数
|
||||||
max_concurrent: 200 # 生产环境最大并发请求数
|
max_concurrent: 200 # 生产环境最大并发请求数
|
||||||
|
|
||||||
# 排除频率限制的路径
|
# 排除频率限制的路径
|
||||||
exclude_paths:
|
exclude_paths:
|
||||||
- "/health" # 健康检查接口
|
- "/health" # 健康检查接口
|
||||||
- "/metrics" # 监控指标接口
|
- "/metrics" # 监控指标接口
|
||||||
|
|
||||||
# 排除频率限制的域名
|
# 排除频率限制的域名
|
||||||
exclude_domains:
|
exclude_domains:
|
||||||
- "api.*" # API二级域名不受频率限制
|
- "api.*" # API二级域名不受频率限制
|
||||||
- "*.api.*" # 支持多级API域名
|
- "*.api.*" # 支持多级API域名
|
||||||
|
|
||||||
# 生产环境安全配置(严格限制)
|
# 生产环境安全配置(严格限制)
|
||||||
enable_ip_whitelist: false # 生产环境不启用IP白名单
|
enable_ip_whitelist: false # 生产环境不启用IP白名单
|
||||||
enable_ip_blacklist: true # 启用IP黑名单
|
enable_ip_blacklist: true # 启用IP黑名单
|
||||||
ip_blacklist: # 生产环境IP黑名单
|
ip_blacklist: # 生产环境IP黑名单
|
||||||
- "192.168.1.100" # 示例:被禁止的IP
|
- "192.168.1.100" # 示例:被禁止的IP
|
||||||
- "10.0.0.50" # 示例:被禁止的IP
|
- "10.0.0.50" # 示例:被禁止的IP
|
||||||
|
|
||||||
enable_user_agent: true # 启用User-Agent检查
|
enable_user_agent: true # 启用User-Agent检查
|
||||||
blocked_user_agents: # 被阻止的User-Agent
|
blocked_user_agents: # 被阻止的User-Agent
|
||||||
- "curl" # 阻止curl请求
|
- "curl" # 阻止curl请求
|
||||||
- "wget" # 阻止wget请求
|
- "wget" # 阻止wget请求
|
||||||
- "python-requests" # 阻止Python requests
|
- "python-requests" # 阻止Python requests
|
||||||
|
|
||||||
enable_referer: true # 启用Referer检查
|
enable_referer: true # 启用Referer检查
|
||||||
allowed_referers: # 允许的Referer
|
allowed_referers: # 允许的Referer
|
||||||
- "https://console.tianyuanapi.com"
|
- "https://console.tianyuanapi.com"
|
||||||
- "https://consoletest.tianyuanapi.com"
|
- "https://consoletest.tianyuanapi.com"
|
||||||
|
|
||||||
enable_geo_block: false # 生产环境暂时不启用地理位置阻止
|
enable_geo_block: false # 生产环境暂时不启用地理位置阻止
|
||||||
enable_proxy_check: true # 启用代理检查
|
enable_proxy_check: true # 启用代理检查
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- tyapi-network
|
- tyapi-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U tyapi_user -d tyapi -h localhost"]
|
test:
|
||||||
|
["CMD-SHELL", "pg_isready -U tyapi_user -d tyapi -h localhost"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -88,6 +89,7 @@ services:
|
|||||||
- "25000:8080"
|
- "25000:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- ./logs:/app/logs
|
- ./logs:/app/logs
|
||||||
|
- ./resources/Pure_Component:/app/resources/Pure_Component
|
||||||
# user: "1001:1001" # 注释掉,使用root权限运行
|
# user: "1001:1001" # 注释掉,使用root权限运行
|
||||||
networks:
|
networks:
|
||||||
- tyapi-network
|
- tyapi-network
|
||||||
@@ -169,7 +171,15 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
|
test:
|
||||||
|
[
|
||||||
|
"CMD",
|
||||||
|
"wget",
|
||||||
|
"--no-verbose",
|
||||||
|
"--tries=1",
|
||||||
|
"--spider",
|
||||||
|
"http://localhost:8080/health",
|
||||||
|
]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -189,6 +199,8 @@ volumes:
|
|||||||
driver: local
|
driver: local
|
||||||
redis_data:
|
redis_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
pure_component:
|
||||||
|
driver: local
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
tyapi-network:
|
tyapi-network:
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
## 一、功能概述
|
## 一、功能概述
|
||||||
|
|
||||||
在产品详情页面添加"下载示例报告"功能,允许用户下载与产品对应的前端组件报告。报告文件位于 `resources/Pure Component/src/ui` 目录下,通过产品编号(product_code)匹配对应的文件夹或文件。
|
在产品详情页面添加"下载示例报告"功能,允许用户下载与产品对应的前端组件报告。报告文件位于 `resources/Pure_Component/src/ui` 目录下,通过产品编号(product_code)匹配对应的文件夹或文件。
|
||||||
|
|
||||||
## 二、核心需求
|
## 二、核心需求
|
||||||
|
|
||||||
### 2.1 基本功能
|
### 2.1 基本功能
|
||||||
|
|
||||||
1. **报告匹配**:根据子产品的 `product_code` 模糊匹配 `resources/Pure Component/src/ui` 下的文件夹或文件
|
1. **报告匹配**:根据子产品的 `product_code` 模糊匹配 `resources/Pure_Component/src/ui` 下的文件夹或文件
|
||||||
- 支持前缀匹配(如产品编号为 `DWBG6A2C`,文件夹可能是 `DWBG6A2C` 或 `多cDWBG6A2C`)
|
- 支持前缀匹配(如产品编号为 `DWBG6A2C`,文件夹可能是 `DWBG6A2C` 或 `多cDWBG6A2C`)
|
||||||
- 匹配规则:文件夹名称包含产品编号,或产品编号包含文件夹名称的核心部分
|
- 匹配规则:文件夹名称包含产品编号,或产品编号包含文件夹名称的核心部分
|
||||||
|
|
||||||
@@ -537,7 +537,7 @@ func (s *ComponentReportServiceImpl) MatchProductCodeToPath(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 扫描目录
|
// 2. 扫描目录
|
||||||
basePath := "resources/Pure Component/src/ui"
|
basePath := "resources/Pure_Component/src/ui"
|
||||||
entries, err := os.ReadDir(basePath)
|
entries, err := os.ReadDir(basePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
@@ -807,7 +807,7 @@ func (s *ComponentReportServiceImpl) GenerateZipFile(ctx context.Context, produc
|
|||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
|
|
||||||
// 3. 遍历子产品,添加UI组件文件到ZIP
|
// 3. 遍历子产品,添加UI组件文件到ZIP
|
||||||
basePath := "resources/Pure Component/src/ui"
|
basePath := "resources/Pure_Component/src/ui"
|
||||||
for _, productCode := range subProductCodes {
|
for _, productCode := range subProductCodes {
|
||||||
path, fileType, err := s.MatchProductCodeToPath(ctx, productCode)
|
path, fileType, err := s.MatchProductCodeToPath(ctx, productCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -847,7 +847,7 @@ func (s *ComponentReportServiceImpl) GenerateZipFile(ctx context.Context, produc
|
|||||||
|
|
||||||
// 5. 添加其他必要的文件(如果需要)
|
// 5. 添加其他必要的文件(如果需要)
|
||||||
// 例如:复制 public 目录下的其他文件(如果有)
|
// 例如:复制 public 目录下的其他文件(如果有)
|
||||||
publicBasePath := "resources/Pure Component/public"
|
publicBasePath := "resources/Pure_Component/public"
|
||||||
publicFiles, err := os.ReadDir(publicBasePath)
|
publicFiles, err := os.ReadDir(publicBasePath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, file := range publicFiles {
|
for _, file := range publicFiles {
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
|||||||
&financeEntities.AlipayOrder{},
|
&financeEntities.AlipayOrder{},
|
||||||
&financeEntities.InvoiceApplication{},
|
&financeEntities.InvoiceApplication{},
|
||||||
&financeEntities.UserInvoiceInfo{},
|
&financeEntities.UserInvoiceInfo{},
|
||||||
|
&financeEntities.PurchaseOrder{}, //购买组件订单表
|
||||||
|
|
||||||
// 产品域
|
// 产品域
|
||||||
&productEntities.Product{},
|
&productEntities.Product{},
|
||||||
|
|||||||
@@ -218,6 +218,12 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
|
|||||||
return nil, ErrFrozenAccount
|
return nil, ErrFrozenAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证产品是否启用
|
||||||
|
if !product.IsEnabled {
|
||||||
|
s.logger.Error("产品未启用", zap.String("product_code", product.Code))
|
||||||
|
return nil, ErrProductDisabled
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 验证IP白名单(非开发环境)
|
// 4. 验证IP白名单(非开发环境)
|
||||||
if !s.config.App.IsDevelopment() && !cmd.Options.IsDebug {
|
if !s.config.App.IsDevelopment() && !cmd.Options.IsDebug {
|
||||||
if !apiUser.IsWhiteListed(cmd.ClientIP) {
|
if !apiUser.IsWhiteListed(cmd.ClientIP) {
|
||||||
@@ -583,12 +589,50 @@ func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID
|
|||||||
// 转换为响应DTO
|
// 转换为响应DTO
|
||||||
var items []dto.ApiCallRecordResponse
|
var items []dto.ApiCallRecordResponse
|
||||||
for _, call := range calls {
|
for _, call := range calls {
|
||||||
|
// 解密请求参数
|
||||||
|
var requestParamsStr string = call.RequestParams // 默认使用原始值
|
||||||
|
if call.UserId != nil && *call.UserId != "" {
|
||||||
|
// 获取用户的API密钥信息
|
||||||
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取用户API信息失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("call_id", call.ID),
|
||||||
|
zap.String("user_id", *call.UserId))
|
||||||
|
// 获取失败时使用原始值
|
||||||
|
} else if apiUser.SecretKey != "" {
|
||||||
|
// 使用用户的SecretKey解密请求参数
|
||||||
|
decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{
|
||||||
|
EncryptedData: call.RequestParams,
|
||||||
|
SecretKey: apiUser.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("解密请求参数失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("call_id", call.ID),
|
||||||
|
zap.String("user_id", *call.UserId))
|
||||||
|
// 解密失败时使用原始值
|
||||||
|
} else {
|
||||||
|
// 将解密后的数据转换为JSON字符串
|
||||||
|
if jsonBytes, err := json.Marshal(decryptedParams); err == nil {
|
||||||
|
requestParamsStr = string(jsonBytes)
|
||||||
|
} else {
|
||||||
|
s.logger.Error("序列化解密参数失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("call_id", call.ID))
|
||||||
|
// 序列化失败时使用原始值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item := dto.ApiCallRecordResponse{
|
item := dto.ApiCallRecordResponse{
|
||||||
ID: call.ID,
|
ID: call.ID,
|
||||||
AccessId: call.AccessId,
|
AccessId: call.AccessId,
|
||||||
UserId: *call.UserId,
|
UserId: *call.UserId,
|
||||||
TransactionId: call.TransactionId,
|
TransactionId: call.TransactionId,
|
||||||
ClientIp: call.ClientIp,
|
ClientIp: call.ClientIp,
|
||||||
|
RequestParams: requestParamsStr,
|
||||||
Status: call.Status,
|
Status: call.Status,
|
||||||
StartAt: call.StartAt.Format("2006-01-02 15:04:05"),
|
StartAt: call.StartAt.Format("2006-01-02 15:04:05"),
|
||||||
CreatedAt: call.CreatedAt.Format("2006-01-02 15:04:05"),
|
CreatedAt: call.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
@@ -649,11 +693,49 @@ func (s *ApiApplicationServiceImpl) GetAdminApiCalls(ctx context.Context, filter
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解密请求参数
|
||||||
|
var requestParamsStr string = call.RequestParams // 默认使用原始值
|
||||||
|
if call.UserId != nil && *call.UserId != "" {
|
||||||
|
// 获取用户的API密钥信息
|
||||||
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("获取用户API信息失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("call_id", call.ID),
|
||||||
|
zap.String("user_id", *call.UserId))
|
||||||
|
// 获取失败时使用原始值
|
||||||
|
} else if apiUser.SecretKey != "" {
|
||||||
|
// 使用用户的SecretKey解密请求参数
|
||||||
|
decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{
|
||||||
|
EncryptedData: call.RequestParams,
|
||||||
|
SecretKey: apiUser.SecretKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("解密请求参数失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("call_id", call.ID),
|
||||||
|
zap.String("user_id", *call.UserId))
|
||||||
|
// 解密失败时使用原始值
|
||||||
|
} else {
|
||||||
|
// 将解密后的数据转换为JSON字符串
|
||||||
|
if jsonBytes, err := json.Marshal(decryptedParams); err == nil {
|
||||||
|
requestParamsStr = string(jsonBytes)
|
||||||
|
} else {
|
||||||
|
s.logger.Error("序列化解密参数失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("call_id", call.ID))
|
||||||
|
// 序列化失败时使用原始值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item := dto.ApiCallRecordResponse{
|
item := dto.ApiCallRecordResponse{
|
||||||
ID: call.ID,
|
ID: call.ID,
|
||||||
AccessId: call.AccessId,
|
AccessId: call.AccessId,
|
||||||
TransactionId: call.TransactionId,
|
TransactionId: call.TransactionId,
|
||||||
ClientIp: call.ClientIp,
|
ClientIp: call.ClientIp,
|
||||||
|
RequestParams: requestParamsStr,
|
||||||
Status: call.Status,
|
Status: call.Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1292,7 +1374,7 @@ func (s *ApiApplicationServiceImpl) UpdateUserBalanceAlertSettings(ctx context.C
|
|||||||
// TestBalanceAlertSms 测试余额预警短信
|
// TestBalanceAlertSms 测试余额预警短信
|
||||||
func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, userID string, phone string, balance float64, alertType string) error {
|
func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, userID string, phone string, balance float64, alertType string) error {
|
||||||
// 获取用户信息以获取企业名称
|
// 获取用户信息以获取企业名称
|
||||||
user, err := s.userRepo.GetByID(ctx, userID)
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取用户信息失败",
|
s.logger.Error("获取用户信息失败",
|
||||||
zap.String("user_id", userID),
|
zap.String("user_id", userID),
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type ApiCallRecordResponse struct {
|
|||||||
ProductName *string `json:"product_name,omitempty"`
|
ProductName *string `json:"product_name,omitempty"`
|
||||||
TransactionId string `json:"transaction_id"`
|
TransactionId string `json:"transaction_id"`
|
||||||
ClientIp string `json:"client_ip"`
|
ClientIp string `json:"client_ip"`
|
||||||
|
RequestParams string `json:"request_params"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
StartAt string `json:"start_at"`
|
StartAt string `json:"start_at"`
|
||||||
EndAt *string `json:"end_at,omitempty"`
|
EndAt *string `json:"end_at,omitempty"`
|
||||||
|
|||||||
@@ -109,13 +109,16 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 验证验证码
|
// 验证验证码
|
||||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.LegalPersonPhone, cmd.VerificationCode, user_entities.SMSSceneCertification); err != nil {
|
// 特殊验证码"768005"直接跳过验证环节
|
||||||
record.MarkAsFailed(err.Error())
|
if cmd.VerificationCode != "768005" {
|
||||||
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
if err := s.smsCodeService.VerifyCode(ctx, cmd.LegalPersonPhone, cmd.VerificationCode, user_entities.SMSSceneCertification); err != nil {
|
||||||
if saveErr != nil {
|
record.MarkAsFailed(err.Error())
|
||||||
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
||||||
|
if saveErr != nil {
|
||||||
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("验证码错误或已过期")
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("验证码错误或已过期")
|
|
||||||
}
|
}
|
||||||
s.logger.Info("开始处理企业信息提交",
|
s.logger.Info("开始处理企业信息提交",
|
||||||
zap.String("user_id", cmd.UserID))
|
zap.String("user_id", cmd.UserID))
|
||||||
|
|||||||
@@ -125,3 +125,45 @@ type UserSimpleResponse struct {
|
|||||||
CompanyName string `json:"company_name"`
|
CompanyName string `json:"company_name"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PurchaseRecordResponse 购买记录响应
|
||||||
|
type PurchaseRecordResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
OrderNo string `json:"order_no"`
|
||||||
|
TradeNo *string `json:"trade_no,omitempty"`
|
||||||
|
ProductID string `json:"product_id"`
|
||||||
|
ProductCode string `json:"product_code"`
|
||||||
|
ProductName string `json:"product_name"`
|
||||||
|
Category string `json:"category,omitempty"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Amount decimal.Decimal `json:"amount"`
|
||||||
|
PayAmount *decimal.Decimal `json:"pay_amount,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
PayChannel string `json:"pay_channel"`
|
||||||
|
PaymentType string `json:"payment_type"`
|
||||||
|
BuyerID string `json:"buyer_id,omitempty"`
|
||||||
|
SellerID string `json:"seller_id,omitempty"`
|
||||||
|
ReceiptAmount decimal.Decimal `json:"receipt_amount,omitempty"`
|
||||||
|
NotifyTime *time.Time `json:"notify_time,omitempty"`
|
||||||
|
ReturnTime *time.Time `json:"return_time,omitempty"`
|
||||||
|
PayTime *time.Time `json:"pay_time,omitempty"`
|
||||||
|
FilePath *string `json:"file_path,omitempty"`
|
||||||
|
FileSize *int64 `json:"file_size,omitempty"`
|
||||||
|
Remark string `json:"remark,omitempty"`
|
||||||
|
ErrorCode string `json:"error_code,omitempty"`
|
||||||
|
ErrorMessage string `json:"error_message,omitempty"`
|
||||||
|
CompanyName string `json:"company_name,omitempty"`
|
||||||
|
User *UserSimpleResponse `json:"user,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurchaseRecordListResponse 购买记录列表响应
|
||||||
|
type PurchaseRecordListResponse struct {
|
||||||
|
Items []PurchaseRecordResponse `json:"items"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ type FinanceApplicationService interface {
|
|||||||
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
||||||
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
||||||
|
|
||||||
|
// 购买记录
|
||||||
|
GetUserPurchaseRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
|
||||||
|
GetAdminPurchaseRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
|
||||||
|
|
||||||
// 获取充值配置
|
// 获取充值配置
|
||||||
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
|
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
"tyapi-server/internal/application/finance/dto/commands"
|
"tyapi-server/internal/application/finance/dto/commands"
|
||||||
"tyapi-server/internal/application/finance/dto/queries"
|
"tyapi-server/internal/application/finance/dto/queries"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
finance_services "tyapi-server/internal/domains/finance/services"
|
finance_services "tyapi-server/internal/domains/finance/services"
|
||||||
product_repositories "tyapi-server/internal/domains/product/repositories"
|
product_repositories "tyapi-server/internal/domains/product/repositories"
|
||||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||||
|
"tyapi-server/internal/shared/component_report"
|
||||||
"tyapi-server/internal/shared/database"
|
"tyapi-server/internal/shared/database"
|
||||||
"tyapi-server/internal/shared/export"
|
"tyapi-server/internal/shared/export"
|
||||||
"tyapi-server/internal/shared/interfaces"
|
"tyapi-server/internal/shared/interfaces"
|
||||||
@@ -36,6 +36,7 @@ type FinanceApplicationServiceImpl struct {
|
|||||||
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
||||||
wechatOrderRepo finance_repositories.WechatOrderRepository
|
wechatOrderRepo finance_repositories.WechatOrderRepository
|
||||||
rechargeRecordRepo finance_repositories.RechargeRecordRepository
|
rechargeRecordRepo finance_repositories.RechargeRecordRepository
|
||||||
|
purchaseOrderRepo finance_repositories.PurchaseOrderRepository
|
||||||
componentReportRepo product_repositories.ComponentReportRepository
|
componentReportRepo product_repositories.ComponentReportRepository
|
||||||
userRepo user_repositories.UserRepository
|
userRepo user_repositories.UserRepository
|
||||||
txManager *database.TransactionManager
|
txManager *database.TransactionManager
|
||||||
@@ -54,6 +55,7 @@ func NewFinanceApplicationService(
|
|||||||
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
||||||
wechatOrderRepo finance_repositories.WechatOrderRepository,
|
wechatOrderRepo finance_repositories.WechatOrderRepository,
|
||||||
rechargeRecordRepo finance_repositories.RechargeRecordRepository,
|
rechargeRecordRepo finance_repositories.RechargeRecordRepository,
|
||||||
|
purchaseOrderRepo finance_repositories.PurchaseOrderRepository,
|
||||||
componentReportRepo product_repositories.ComponentReportRepository,
|
componentReportRepo product_repositories.ComponentReportRepository,
|
||||||
userRepo user_repositories.UserRepository,
|
userRepo user_repositories.UserRepository,
|
||||||
txManager *database.TransactionManager,
|
txManager *database.TransactionManager,
|
||||||
@@ -70,6 +72,7 @@ func NewFinanceApplicationService(
|
|||||||
alipayOrderRepo: alipayOrderRepo,
|
alipayOrderRepo: alipayOrderRepo,
|
||||||
wechatOrderRepo: wechatOrderRepo,
|
wechatOrderRepo: wechatOrderRepo,
|
||||||
rechargeRecordRepo: rechargeRecordRepo,
|
rechargeRecordRepo: rechargeRecordRepo,
|
||||||
|
purchaseOrderRepo: purchaseOrderRepo,
|
||||||
componentReportRepo: componentReportRepo,
|
componentReportRepo: componentReportRepo,
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
txManager: txManager,
|
txManager: txManager,
|
||||||
@@ -854,13 +857,7 @@ func (s *FinanceApplicationServiceImpl) HandleAlipayCallback(ctx context.Context
|
|||||||
zap.String("trade_no", notification.TradeNo),
|
zap.String("trade_no", notification.TradeNo),
|
||||||
)
|
)
|
||||||
|
|
||||||
// 先检查是否是组件报告下载的支付订单
|
// 处理支付宝支付成功逻辑
|
||||||
s.logger.Info("步骤1: 检查是否是组件报告下载订单",
|
|
||||||
zap.String("out_trade_no", notification.OutTradeNo),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 使用公共方法处理支付成功逻辑(包括更新充值记录状态)
|
|
||||||
// 无论是组件报告下载订单还是普通充值订单,都需要更新充值记录状态
|
|
||||||
err = s.processAlipayPaymentSuccess(ctx, notification.OutTradeNo, notification.TradeNo, notification.TotalAmount, notification.BuyerId, notification.SellerId)
|
err = s.processAlipayPaymentSuccess(ctx, notification.OutTradeNo, notification.TradeNo, notification.TotalAmount, notification.BuyerId, notification.SellerId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("处理支付宝支付成功失败",
|
s.logger.Error("处理支付宝支付成功失败",
|
||||||
@@ -886,20 +883,52 @@ func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接调用充值记录服务处理支付成功逻辑
|
// 查找支付宝订单
|
||||||
// 该服务内部会处理所有必要的检查、事务和更新操作
|
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||||
// 如果是组件报告下载订单,服务会自动跳过钱包余额增加
|
|
||||||
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("处理支付宝支付成功失败",
|
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查并更新组件报告下载记录状态(如果存在)
|
if alipayOrder == nil {
|
||||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
|
||||||
|
return fmt.Errorf("支付宝订单不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为充值订单还是购买订单
|
||||||
|
_, err = s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
|
||||||
|
if err == nil {
|
||||||
|
// 这是充值订单,调用充值记录服务处理支付成功逻辑
|
||||||
|
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("处理支付宝充值支付成功失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 尝试查找购买订单
|
||||||
|
_, err = s.purchaseOrderRepo.GetByID(ctx, alipayOrder.RechargeID)
|
||||||
|
if err == nil {
|
||||||
|
// 这是购买订单(可能是示例报告购买订单),调用处理购买订单支付成功逻辑
|
||||||
|
err = s.processPurchaseOrderPaymentSuccess(ctx, alipayOrder.RechargeID, tradeNo, amount, buyerID, sellerID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("处理支付宝购买订单支付成功失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("purchase_order_id", alipayOrder.RechargeID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.logger.Error("无法确定订单类型",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("recharge_id", alipayOrder.RechargeID),
|
||||||
|
)
|
||||||
|
return fmt.Errorf("无法确定订单类型")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.logger.Info("支付宝支付成功处理完成",
|
s.logger.Info("支付宝支付成功处理完成",
|
||||||
zap.String("out_trade_no", outTradeNo),
|
zap.String("out_trade_no", outTradeNo),
|
||||||
@@ -1477,30 +1506,7 @@ func (s *FinanceApplicationServiceImpl) HandleWechatPayCallback(ctx context.Cont
|
|||||||
zap.String("transaction_id", transactionID),
|
zap.String("transaction_id", transactionID),
|
||||||
)
|
)
|
||||||
|
|
||||||
// 先检查是否是组件报告下载的支付订单
|
// 处理微信支付成功逻辑(充值流程)
|
||||||
s.logger.Info("步骤1: 检查是否是组件报告下载订单",
|
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 检查组件报告下载记录
|
|
||||||
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, outTradeNo)
|
|
||||||
if err == nil && download != nil {
|
|
||||||
s.logger.Info("步骤2: 发现组件报告下载订单,直接更新下载记录状态",
|
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
zap.String("download_id", download.ID),
|
|
||||||
zap.String("product_id", download.ProductID),
|
|
||||||
zap.String("current_status", download.PaymentStatus),
|
|
||||||
)
|
|
||||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
|
||||||
s.logger.Info("========== 组件报告下载订单处理完成 ==========")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.logger.Info("步骤3: 不是组件报告下载订单,按充值流程处理",
|
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 处理支付成功逻辑(充值流程)
|
|
||||||
err = s.processWechatPaymentSuccess(ctx, outTradeNo, transactionID, totalAmount)
|
err = s.processWechatPaymentSuccess(ctx, outTradeNo, transactionID, totalAmount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("处理微信支付成功失败",
|
s.logger.Error("处理微信支付成功失败",
|
||||||
@@ -1535,26 +1541,34 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
|||||||
return fmt.Errorf("微信订单不存在")
|
return fmt.Errorf("微信订单不存在")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找对应的充值记录
|
// 判断是否为充值订单还是购买订单
|
||||||
rechargeRecord, err := s.rechargeRecordService.GetByID(ctx, wechatOrder.RechargeID)
|
rechargeRecord, err := s.rechargeRecordService.GetByID(ctx, wechatOrder.RechargeID)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
s.logger.Error("查找充值记录失败",
|
// 这是充值订单,继续原有的处理逻辑
|
||||||
zap.String("out_trade_no", outTradeNo),
|
} else {
|
||||||
zap.String("recharge_id", wechatOrder.RechargeID),
|
// 尝试查找购买订单
|
||||||
zap.Error(err),
|
_, err = s.purchaseOrderRepo.GetByID(ctx, wechatOrder.RechargeID)
|
||||||
)
|
if err == nil {
|
||||||
return fmt.Errorf("查找充值记录失败: %w", err)
|
// 这是购买订单(可能是示例报告购买订单),调用处理购买订单支付成功逻辑
|
||||||
|
err = s.processPurchaseOrderPaymentSuccess(ctx, wechatOrder.RechargeID, transactionID, amount, "", "")
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("处理微信购买订单支付成功失败",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("purchase_order_id", wechatOrder.RechargeID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
s.logger.Error("无法确定订单类型",
|
||||||
|
zap.String("out_trade_no", outTradeNo),
|
||||||
|
zap.String("recharge_id", wechatOrder.RechargeID),
|
||||||
|
)
|
||||||
|
return fmt.Errorf("无法确定订单类型")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("步骤4: 检查充值记录备注,判断是否为组件报告下载订单",
|
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
zap.String("recharge_id", rechargeRecord.ID),
|
|
||||||
zap.String("notes", rechargeRecord.Notes),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 检查是否是组件报告下载订单(通过备注判断)
|
|
||||||
isComponentReportOrder := strings.Contains(rechargeRecord.Notes, "购买") && strings.Contains(rechargeRecord.Notes, "报告示例")
|
|
||||||
|
|
||||||
// 检查订单和充值记录状态,如果都已成功则跳过(只记录一次日志)
|
// 检查订单和充值记录状态,如果都已成功则跳过(只记录一次日志)
|
||||||
if wechatOrder.Status == finance_entities.WechatOrderStatusSuccess && rechargeRecord.Status == finance_entities.RechargeStatusSuccess {
|
if wechatOrder.Status == finance_entities.WechatOrderStatusSuccess && rechargeRecord.Status == finance_entities.RechargeStatusSuccess {
|
||||||
s.logger.Info("微信支付订单已处理成功,跳过重复处理",
|
s.logger.Info("微信支付订单已处理成功,跳过重复处理",
|
||||||
@@ -1562,12 +1576,7 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
|||||||
zap.String("transaction_id", transactionID),
|
zap.String("transaction_id", transactionID),
|
||||||
zap.String("order_id", wechatOrder.ID),
|
zap.String("order_id", wechatOrder.ID),
|
||||||
zap.String("recharge_id", rechargeRecord.ID),
|
zap.String("recharge_id", rechargeRecord.ID),
|
||||||
zap.Bool("is_component_report", isComponentReportOrder),
|
|
||||||
)
|
)
|
||||||
// 如果是组件报告下载订单,确保更新下载记录状态
|
|
||||||
if isComponentReportOrder {
|
|
||||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1638,33 +1647,17 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是组件报告下载订单(通过备注判断)
|
// 充值到钱包(包含赠送金额)
|
||||||
isComponentReportOrder := strings.Contains(rechargeRecord.Notes, "购买") && strings.Contains(rechargeRecord.Notes, "报告示例")
|
totalRechargeAmount := amount.Add(bonusAmount)
|
||||||
|
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, totalRechargeAmount)
|
||||||
if isComponentReportOrder {
|
if err != nil {
|
||||||
s.logger.Info("步骤5: 检测到组件报告下载订单,不增加钱包余额",
|
s.logger.Error("充值到钱包失败",
|
||||||
zap.String("out_trade_no", outTradeNo),
|
zap.String("out_trade_no", outTradeNo),
|
||||||
zap.String("recharge_id", rechargeRecord.ID),
|
zap.String("user_id", rechargeRecord.UserID),
|
||||||
zap.String("notes", rechargeRecord.Notes),
|
zap.String("total_amount", totalRechargeAmount.String()),
|
||||||
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
// 组件报告下载订单不增加钱包余额,只更新订单和充值记录状态
|
return err
|
||||||
} else {
|
|
||||||
s.logger.Info("步骤5: 普通充值订单,增加钱包余额",
|
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
zap.String("recharge_id", rechargeRecord.ID),
|
|
||||||
)
|
|
||||||
// 充值到钱包(包含赠送金额)
|
|
||||||
totalRechargeAmount := amount.Add(bonusAmount)
|
|
||||||
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, totalRechargeAmount)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("充值到钱包失败",
|
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
zap.String("user_id", rechargeRecord.UserID),
|
|
||||||
zap.String("total_amount", totalRechargeAmount.String()),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -1680,105 +1673,129 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是组件报告下载订单,更新下载记录状态
|
|
||||||
if isComponentReportOrder {
|
|
||||||
s.logger.Info("步骤6: 更新组件报告下载记录状态",
|
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
)
|
|
||||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.logger.Info("微信支付成功处理完成",
|
s.logger.Info("微信支付成功处理完成",
|
||||||
zap.String("out_trade_no", outTradeNo),
|
zap.String("out_trade_no", outTradeNo),
|
||||||
zap.String("transaction_id", transactionID),
|
zap.String("transaction_id", transactionID),
|
||||||
zap.String("amount", amount.String()),
|
zap.String("amount", amount.String()),
|
||||||
zap.String("bonus_amount", bonusAmount.String()),
|
zap.String("bonus_amount", bonusAmount.String()),
|
||||||
zap.String("user_id", rechargeRecord.UserID),
|
zap.String("user_id", rechargeRecord.UserID),
|
||||||
zap.Bool("is_component_report", isComponentReportOrder),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateComponentReportDownloadStatus 更新组件报告下载记录状态
|
// processPurchaseOrderPaymentSuccess 处理购买订单支付成功的逻辑
|
||||||
func (s *FinanceApplicationServiceImpl) updateComponentReportDownloadStatus(ctx context.Context, outTradeNo string) {
|
func (s *FinanceApplicationServiceImpl) processPurchaseOrderPaymentSuccess(ctx context.Context, purchaseOrderID, tradeNo string, amount decimal.Decimal, buyerID, sellerID string) error {
|
||||||
s.logger.Info("========== 开始更新组件报告下载记录状态 ==========",
|
// 查找购买订单
|
||||||
zap.String("out_trade_no", outTradeNo),
|
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, purchaseOrderID)
|
||||||
)
|
|
||||||
|
|
||||||
if s.componentReportRepo == nil {
|
|
||||||
s.logger.Warn("组件报告下载Repository未初始化,跳过更新")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据支付订单号查找组件报告下载记录
|
|
||||||
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, outTradeNo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Info("未找到组件报告下载记录,可能不是组件报告下载订单",
|
s.logger.Error("查找购买订单失败",
|
||||||
zap.String("out_trade_no", outTradeNo),
|
zap.String("purchase_order_id", purchaseOrderID),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
return
|
return fmt.Errorf("查找购买订单失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if download == nil {
|
if purchaseOrder == nil {
|
||||||
s.logger.Info("组件报告下载记录为空,跳过更新",
|
s.logger.Error("购买订单不存在",
|
||||||
zap.String("out_trade_no", outTradeNo),
|
zap.String("purchase_order_id", purchaseOrderID),
|
||||||
)
|
)
|
||||||
return
|
return fmt.Errorf("购买订单不存在")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("步骤1: 找到组件报告下载记录",
|
// 检查订单状态,如果已支付则跳过
|
||||||
zap.String("out_trade_no", outTradeNo),
|
if purchaseOrder.Status == finance_entities.PurchaseOrderStatusPaid {
|
||||||
zap.String("download_id", download.ID),
|
s.logger.Info("购买订单已支付,跳过处理",
|
||||||
zap.String("product_id", download.ProductID),
|
zap.String("purchase_order_id", purchaseOrderID),
|
||||||
zap.String("current_status", download.PaymentStatus),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 如果已经是成功状态,跳过
|
|
||||||
if download.PaymentStatus == "success" {
|
|
||||||
s.logger.Info("组件报告下载记录已是成功状态,跳过更新",
|
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
zap.String("download_id", download.ID),
|
|
||||||
)
|
)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("步骤2: 更新支付状态为成功",
|
// 更新购买订单状态
|
||||||
zap.String("out_trade_no", outTradeNo),
|
purchaseOrder.MarkPaid(tradeNo, buyerID, sellerID, amount, amount)
|
||||||
zap.String("download_id", download.ID),
|
err = s.purchaseOrderRepo.Update(ctx, purchaseOrder)
|
||||||
)
|
|
||||||
|
|
||||||
// 更新支付状态为成功
|
|
||||||
download.PaymentStatus = "success"
|
|
||||||
|
|
||||||
// 设置过期时间(30天后)
|
|
||||||
expiresAt := time.Now().Add(30 * 24 * time.Hour)
|
|
||||||
download.ExpiresAt = &expiresAt
|
|
||||||
|
|
||||||
s.logger.Info("步骤3: 保存更新后的下载记录",
|
|
||||||
zap.String("out_trade_no", outTradeNo),
|
|
||||||
zap.String("download_id", download.ID),
|
|
||||||
zap.String("expires_at", expiresAt.Format("2006-01-02 15:04:05")),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 更新记录
|
|
||||||
err = s.componentReportRepo.UpdateDownload(ctx, download)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("更新组件报告下载记录状态失败",
|
s.logger.Error("更新购买订单状态失败",
|
||||||
zap.String("out_trade_no", outTradeNo),
|
zap.String("purchase_order_id", purchaseOrderID),
|
||||||
zap.String("download_id", download.ID),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
return
|
return fmt.Errorf("更新购买订单状态失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("========== 组件报告下载记录状态更新成功 ==========",
|
// 更新对应的支付订单状态(微信或支付宝)
|
||||||
zap.String("out_trade_no", outTradeNo),
|
if purchaseOrder.PayChannel == "alipay" {
|
||||||
zap.String("download_id", download.ID),
|
alipayOrder, err := s.alipayOrderRepo.GetByRechargeID(ctx, purchaseOrderID)
|
||||||
zap.String("product_id", download.ProductID),
|
if err == nil && alipayOrder != nil {
|
||||||
zap.String("payment_status", download.PaymentStatus),
|
alipayOrder.MarkSuccess(tradeNo, buyerID, sellerID, amount, amount)
|
||||||
|
err = s.alipayOrderRepo.Update(ctx, *alipayOrder)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("更新支付宝订单状态失败",
|
||||||
|
zap.String("out_trade_no", alipayOrder.OutTradeNo),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if purchaseOrder.PayChannel == "wechat" {
|
||||||
|
wechatOrder, err := s.wechatOrderRepo.GetByRechargeID(ctx, purchaseOrderID)
|
||||||
|
if err == nil && wechatOrder != nil {
|
||||||
|
wechatOrder.MarkSuccess(tradeNo, buyerID, sellerID, amount, amount)
|
||||||
|
err = s.wechatOrderRepo.Update(ctx, *wechatOrder)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("更新微信订单状态失败",
|
||||||
|
zap.String("out_trade_no", wechatOrder.OutTradeNo),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是组件报告购买,需要生成并更新报告文件
|
||||||
|
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, purchaseOrderID)
|
||||||
|
if err == nil && download != nil {
|
||||||
|
// 创建报告生成器
|
||||||
|
zipGenerator := component_report.NewZipGenerator(s.logger)
|
||||||
|
|
||||||
|
// 生成报告文件
|
||||||
|
zipPath, err := zipGenerator.GenerateZipFile(
|
||||||
|
ctx,
|
||||||
|
download.ProductID,
|
||||||
|
[]string{download.ProductCode}, // 使用简化后的只包含主产品编号的列表
|
||||||
|
nil, // 使用默认的JSON生成器
|
||||||
|
"", // 使用默认路径
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("生成组件报告文件失败",
|
||||||
|
zap.String("download_id", download.ID),
|
||||||
|
zap.String("purchase_order_id", purchaseOrderID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
// 不中断流程,即使生成文件失败也继续处理
|
||||||
|
} else {
|
||||||
|
// 更新下载记录的文件路径
|
||||||
|
download.FilePath = &zipPath
|
||||||
|
err = s.componentReportRepo.UpdateDownload(ctx, download)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("更新下载记录文件路径失败",
|
||||||
|
zap.String("download_id", download.ID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
s.logger.Info("组件报告文件生成成功",
|
||||||
|
zap.String("download_id", download.ID),
|
||||||
|
zap.String("file_path", zipPath),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("购买订单支付成功处理完成",
|
||||||
|
zap.String("purchase_order_id", purchaseOrderID),
|
||||||
|
zap.String("trade_no", tradeNo),
|
||||||
|
zap.String("amount", amount.String()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleWechatRefundCallback 处理微信退款回调
|
// HandleWechatRefundCallback 处理微信退款回调
|
||||||
@@ -1842,3 +1859,163 @@ func (s *FinanceApplicationServiceImpl) HandleWechatRefundCallback(ctx context.C
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserPurchaseRecords 获取用户购买记录
|
||||||
|
func (s *FinanceApplicationServiceImpl) GetUserPurchaseRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error) {
|
||||||
|
// 确保 filters 不为 nil
|
||||||
|
if filters == nil {
|
||||||
|
filters = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 user_id 筛选条件,确保只能查询当前用户的记录
|
||||||
|
filters["user_id"] = userID
|
||||||
|
|
||||||
|
// 获取总数
|
||||||
|
total, err := s.purchaseOrderRepo.CountByFilters(ctx, filters)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("统计用户购买记录失败", zap.Error(err), zap.String("userID", userID))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户购买记录(使用筛选和分页功能)
|
||||||
|
orders, err := s.purchaseOrderRepo.GetByFilters(ctx, filters, options)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("查询用户购买记录失败", zap.Error(err), zap.String("userID", userID))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为响应DTO
|
||||||
|
var items []responses.PurchaseRecordResponse
|
||||||
|
for _, order := range orders {
|
||||||
|
item := responses.PurchaseRecordResponse{
|
||||||
|
ID: order.ID,
|
||||||
|
UserID: order.UserID,
|
||||||
|
OrderNo: order.OrderNo,
|
||||||
|
TradeNo: order.TradeNo,
|
||||||
|
ProductID: order.ProductID,
|
||||||
|
ProductCode: order.ProductCode,
|
||||||
|
ProductName: order.ProductName,
|
||||||
|
Category: order.Category,
|
||||||
|
Subject: order.Subject,
|
||||||
|
Amount: order.Amount,
|
||||||
|
PayAmount: order.PayAmount,
|
||||||
|
Status: string(order.Status),
|
||||||
|
Platform: order.Platform,
|
||||||
|
PayChannel: order.PayChannel,
|
||||||
|
PaymentType: order.PaymentType,
|
||||||
|
BuyerID: order.BuyerID,
|
||||||
|
SellerID: order.SellerID,
|
||||||
|
ReceiptAmount: order.ReceiptAmount,
|
||||||
|
NotifyTime: order.NotifyTime,
|
||||||
|
ReturnTime: order.ReturnTime,
|
||||||
|
PayTime: order.PayTime,
|
||||||
|
FilePath: order.FilePath,
|
||||||
|
FileSize: order.FileSize,
|
||||||
|
Remark: order.Remark,
|
||||||
|
ErrorCode: order.ErrorCode,
|
||||||
|
ErrorMessage: order.ErrorMessage,
|
||||||
|
CreatedAt: order.CreatedAt,
|
||||||
|
UpdatedAt: order.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息和企业名称
|
||||||
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, order.UserID)
|
||||||
|
if err == nil {
|
||||||
|
companyName := "未知企业"
|
||||||
|
if user.EnterpriseInfo != nil {
|
||||||
|
companyName = user.EnterpriseInfo.CompanyName
|
||||||
|
}
|
||||||
|
item.CompanyName = companyName
|
||||||
|
item.User = &responses.UserSimpleResponse{
|
||||||
|
ID: user.ID,
|
||||||
|
CompanyName: companyName,
|
||||||
|
Phone: user.Phone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &responses.PurchaseRecordListResponse{
|
||||||
|
Items: items,
|
||||||
|
Total: total,
|
||||||
|
Page: options.Page,
|
||||||
|
Size: options.PageSize,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdminPurchaseRecords 获取管理端购买记录
|
||||||
|
func (s *FinanceApplicationServiceImpl) GetAdminPurchaseRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error) {
|
||||||
|
// 获取总数
|
||||||
|
total, err := s.purchaseOrderRepo.CountByFilters(ctx, filters)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("统计管理端购买记录失败", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询购买记录
|
||||||
|
orders, err := s.purchaseOrderRepo.GetByFilters(ctx, filters, options)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("查询管理端购买记录失败", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为响应DTO
|
||||||
|
var items []responses.PurchaseRecordResponse
|
||||||
|
for _, order := range orders {
|
||||||
|
item := responses.PurchaseRecordResponse{
|
||||||
|
ID: order.ID,
|
||||||
|
UserID: order.UserID,
|
||||||
|
OrderNo: order.OrderNo,
|
||||||
|
TradeNo: order.TradeNo,
|
||||||
|
ProductID: order.ProductID,
|
||||||
|
ProductCode: order.ProductCode,
|
||||||
|
ProductName: order.ProductName,
|
||||||
|
Category: order.Category,
|
||||||
|
Subject: order.Subject,
|
||||||
|
Amount: order.Amount,
|
||||||
|
PayAmount: order.PayAmount,
|
||||||
|
Status: string(order.Status),
|
||||||
|
Platform: order.Platform,
|
||||||
|
PayChannel: order.PayChannel,
|
||||||
|
PaymentType: order.PaymentType,
|
||||||
|
BuyerID: order.BuyerID,
|
||||||
|
SellerID: order.SellerID,
|
||||||
|
ReceiptAmount: order.ReceiptAmount,
|
||||||
|
NotifyTime: order.NotifyTime,
|
||||||
|
ReturnTime: order.ReturnTime,
|
||||||
|
PayTime: order.PayTime,
|
||||||
|
FilePath: order.FilePath,
|
||||||
|
FileSize: order.FileSize,
|
||||||
|
Remark: order.Remark,
|
||||||
|
ErrorCode: order.ErrorCode,
|
||||||
|
ErrorMessage: order.ErrorMessage,
|
||||||
|
CreatedAt: order.CreatedAt,
|
||||||
|
UpdatedAt: order.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息和企业名称
|
||||||
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, order.UserID)
|
||||||
|
if err == nil {
|
||||||
|
companyName := "未知企业"
|
||||||
|
if user.EnterpriseInfo != nil {
|
||||||
|
companyName = user.EnterpriseInfo.CompanyName
|
||||||
|
}
|
||||||
|
item.CompanyName = companyName
|
||||||
|
item.User = &responses.UserSimpleResponse{
|
||||||
|
ID: user.ID,
|
||||||
|
CompanyName: companyName,
|
||||||
|
Phone: user.Phone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &responses.PurchaseRecordListResponse{
|
||||||
|
Items: items,
|
||||||
|
Total: total,
|
||||||
|
Page: options.Page,
|
||||||
|
Size: options.PageSize,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
1358
internal/application/product/component_report_order_service.go
Normal file
1358
internal/application/product/component_report_order_service.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,10 @@ type CreateProductCommand struct {
|
|||||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
|
|
||||||
|
// UI组件相关字段
|
||||||
|
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||||
|
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格(组合包使用)"`
|
||||||
|
|
||||||
// SEO信息
|
// SEO信息
|
||||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||||
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
||||||
@@ -35,6 +39,10 @@ type UpdateProductCommand struct {
|
|||||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
|
|
||||||
|
// UI组件相关字段
|
||||||
|
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||||
|
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格(组合包使用)"`
|
||||||
|
|
||||||
// SEO信息
|
// SEO信息
|
||||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||||
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ type CreateSubscriptionCommand struct {
|
|||||||
|
|
||||||
// UpdateSubscriptionPriceCommand 更新订阅价格命令
|
// UpdateSubscriptionPriceCommand 更新订阅价格命令
|
||||||
type UpdateSubscriptionPriceCommand struct {
|
type UpdateSubscriptionPriceCommand struct {
|
||||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"订阅ID"`
|
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"订阅ID"`
|
||||||
Price float64 `json:"price" binding:"price,min=0" comment:"订阅价格"`
|
Price float64 `json:"price" binding:"price,min=0" comment:"订阅价格"`
|
||||||
|
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件价格(组合包使用)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BatchUpdateSubscriptionPricesCommand 批量更新订阅价格命令
|
// BatchUpdateSubscriptionPricesCommand 批量更新订阅价格命令
|
||||||
type BatchUpdateSubscriptionPricesCommand struct {
|
type BatchUpdateSubscriptionPricesCommand struct {
|
||||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户ID"`
|
UserID string `json:"user_id" binding:"required,uuid" comment:"用户ID"`
|
||||||
AdjustmentType string `json:"adjustment_type" binding:"required,oneof=discount cost_multiple" comment:"调整方式(discount:按售价折扣,cost_multiple:按成本价倍数)"`
|
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折)"`
|
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:"成本价倍数"`
|
CostMultiple float64 `json:"cost_multiple,omitempty" binding:"omitempty,min=0.1" comment:"成本价倍数"`
|
||||||
Scope string `json:"scope" binding:"required,oneof=undiscounted all" comment:"改价范围(undiscounted:仅未打折,all:所有)"`
|
Scope string `json:"scope" binding:"required,oneof=undiscounted all" comment:"改价范围(undiscounted:仅未打折,all:所有)"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,17 +15,20 @@ type PackageItemResponse struct {
|
|||||||
|
|
||||||
// ProductInfoResponse 产品详情响应
|
// ProductInfoResponse 产品详情响应
|
||||||
type ProductInfoResponse struct {
|
type ProductInfoResponse struct {
|
||||||
ID string `json:"id" comment:"产品ID"`
|
ID string `json:"id" comment:"产品ID"`
|
||||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||||
Name string `json:"name" comment:"产品名称"`
|
Name string `json:"name" comment:"产品名称"`
|
||||||
Code string `json:"code" comment:"产品编号"`
|
Code string `json:"code" comment:"产品编号"`
|
||||||
Description string `json:"description" comment:"产品简介"`
|
Description string `json:"description" comment:"产品简介"`
|
||||||
Content string `json:"content" comment:"产品内容"`
|
Content string `json:"content" comment:"产品内容"`
|
||||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||||
Price float64 `json:"price" comment:"产品价格"`
|
Price float64 `json:"price" comment:"产品价格"`
|
||||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||||
|
// UI组件相关字段
|
||||||
|
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||||
|
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格(组合包使用)"`
|
||||||
|
|
||||||
// SEO信息
|
// SEO信息
|
||||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||||
@@ -60,21 +63,22 @@ type ProductSearchResponse struct {
|
|||||||
|
|
||||||
// ProductSimpleResponse 产品简单信息响应
|
// ProductSimpleResponse 产品简单信息响应
|
||||||
type ProductSimpleResponse struct {
|
type ProductSimpleResponse struct {
|
||||||
ID string `json:"id" comment:"产品ID"`
|
ID string `json:"id" comment:"产品ID"`
|
||||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||||
Name string `json:"name" comment:"产品名称"`
|
Name string `json:"name" comment:"产品名称"`
|
||||||
Code string `json:"code" comment:"产品编号"`
|
Code string `json:"code" comment:"产品编号"`
|
||||||
Description string `json:"description" comment:"产品简介"`
|
Description string `json:"description" comment:"产品简介"`
|
||||||
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
|
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
|
||||||
Price float64 `json:"price" comment:"产品价格"`
|
Price float64 `json:"price" comment:"产品价格"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProductSimpleAdminResponse 管理员产品简单信息响应(包含成本价)
|
// ProductSimpleAdminResponse 管理员产品简单信息响应(包含成本价)
|
||||||
type ProductSimpleAdminResponse struct {
|
type ProductSimpleAdminResponse struct {
|
||||||
ProductSimpleResponse
|
ProductSimpleResponse
|
||||||
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
CostPrice float64 `json:"cost_price" comment:"成本价"`
|
||||||
|
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件价格(组合包使用)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProductStatsResponse 产品统计响应
|
// ProductStatsResponse 产品统计响应
|
||||||
@@ -101,6 +105,10 @@ type ProductAdminInfoResponse struct {
|
|||||||
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
||||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||||
|
|
||||||
|
// UI组件相关字段
|
||||||
|
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||||
|
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格(组合包使用)"`
|
||||||
|
|
||||||
// SEO信息
|
// SEO信息
|
||||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||||
|
|||||||
@@ -13,47 +13,48 @@ type UserSimpleResponse struct {
|
|||||||
|
|
||||||
// SubscriptionInfoResponse 订阅详情响应
|
// SubscriptionInfoResponse 订阅详情响应
|
||||||
type SubscriptionInfoResponse struct {
|
type SubscriptionInfoResponse struct {
|
||||||
ID string `json:"id" comment:"订阅ID"`
|
ID string `json:"id" comment:"订阅ID"`
|
||||||
UserID string `json:"user_id" comment:"用户ID"`
|
UserID string `json:"user_id" comment:"用户ID"`
|
||||||
ProductID string `json:"product_id" comment:"产品ID"`
|
ProductID string `json:"product_id" comment:"产品ID"`
|
||||||
Price float64 `json:"price" comment:"订阅价格"`
|
Price float64 `json:"price" comment:"订阅价格"`
|
||||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件价格(组合包使用)"`
|
||||||
|
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||||
|
|
||||||
// 关联信息
|
// 关联信息
|
||||||
User *UserSimpleResponse `json:"user,omitempty" comment:"用户信息"`
|
User *UserSimpleResponse `json:"user,omitempty" comment:"用户信息"`
|
||||||
Product *ProductSimpleResponse `json:"product,omitempty" comment:"产品信息"`
|
Product *ProductSimpleResponse `json:"product,omitempty" comment:"产品信息"`
|
||||||
// 管理员端使用,包含成本价的产品信息
|
// 管理员端使用,包含成本价的产品信息
|
||||||
ProductAdmin *ProductSimpleAdminResponse `json:"product_admin,omitempty" comment:"产品信息(管理员端,包含成本价)"`
|
ProductAdmin *ProductSimpleAdminResponse `json:"product_admin,omitempty" comment:"产品信息(管理员端,包含成本价)"`
|
||||||
|
|
||||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubscriptionListResponse 订阅列表响应
|
// SubscriptionListResponse 订阅列表响应
|
||||||
type SubscriptionListResponse struct {
|
type SubscriptionListResponse struct {
|
||||||
Total int64 `json:"total" comment:"总数"`
|
Total int64 `json:"total" comment:"总数"`
|
||||||
Page int `json:"page" comment:"页码"`
|
Page int `json:"page" comment:"页码"`
|
||||||
Size int `json:"size" comment:"每页数量"`
|
Size int `json:"size" comment:"每页数量"`
|
||||||
Items []SubscriptionInfoResponse `json:"items" comment:"订阅列表"`
|
Items []SubscriptionInfoResponse `json:"items" comment:"订阅列表"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubscriptionSimpleResponse 订阅简单信息响应
|
// SubscriptionSimpleResponse 订阅简单信息响应
|
||||||
type SubscriptionSimpleResponse struct {
|
type SubscriptionSimpleResponse struct {
|
||||||
ID string `json:"id" comment:"订阅ID"`
|
ID string `json:"id" comment:"订阅ID"`
|
||||||
ProductID string `json:"product_id" comment:"产品ID"`
|
ProductID string `json:"product_id" comment:"产品ID"`
|
||||||
Price float64 `json:"price" comment:"订阅价格"`
|
Price float64 `json:"price" comment:"订阅价格"`
|
||||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubscriptionUsageResponse 订阅使用情况响应
|
// SubscriptionUsageResponse 订阅使用情况响应
|
||||||
type SubscriptionUsageResponse struct {
|
type SubscriptionUsageResponse struct {
|
||||||
ID string `json:"id" comment:"订阅ID"`
|
ID string `json:"id" comment:"订阅ID"`
|
||||||
ProductID string `json:"product_id" comment:"产品ID"`
|
ProductID string `json:"product_id" comment:"产品ID"`
|
||||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubscriptionStatsResponse 订阅统计响应
|
// SubscriptionStatsResponse 订阅统计响应
|
||||||
type SubscriptionStatsResponse struct {
|
type SubscriptionStatsResponse struct {
|
||||||
TotalSubscriptions int64 `json:"total_subscriptions" comment:"订阅总数"`
|
TotalSubscriptions int64 `json:"total_subscriptions" comment:"订阅总数"`
|
||||||
TotalRevenue float64 `json:"total_revenue" comment:"总收入"`
|
TotalRevenue float64 `json:"total_revenue" comment:"总收入"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,20 +54,22 @@ func NewProductApplicationService(
|
|||||||
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
|
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
|
||||||
// 1. 构建产品实体
|
// 1. 构建产品实体
|
||||||
product := &entities.Product{
|
product := &entities.Product{
|
||||||
Name: cmd.Name,
|
Name: cmd.Name,
|
||||||
Code: cmd.Code,
|
Code: cmd.Code,
|
||||||
Description: cmd.Description,
|
Description: cmd.Description,
|
||||||
Content: cmd.Content,
|
Content: cmd.Content,
|
||||||
CategoryID: cmd.CategoryID,
|
CategoryID: cmd.CategoryID,
|
||||||
Price: decimal.NewFromFloat(cmd.Price),
|
Price: decimal.NewFromFloat(cmd.Price),
|
||||||
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
||||||
Remark: cmd.Remark,
|
Remark: cmd.Remark,
|
||||||
IsEnabled: cmd.IsEnabled,
|
IsEnabled: cmd.IsEnabled,
|
||||||
IsVisible: cmd.IsVisible,
|
IsVisible: cmd.IsVisible,
|
||||||
IsPackage: cmd.IsPackage,
|
IsPackage: cmd.IsPackage,
|
||||||
SEOTitle: cmd.SEOTitle,
|
SellUIComponent: cmd.SellUIComponent,
|
||||||
SEODescription: cmd.SEODescription,
|
UIComponentPrice: decimal.NewFromFloat(cmd.UIComponentPrice),
|
||||||
SEOKeywords: cmd.SEOKeywords,
|
SEOTitle: cmd.SEOTitle,
|
||||||
|
SEODescription: cmd.SEODescription,
|
||||||
|
SEOKeywords: cmd.SEOKeywords,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 创建产品
|
// 2. 创建产品
|
||||||
@@ -101,6 +103,8 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
|
|||||||
existingProduct.IsEnabled = cmd.IsEnabled
|
existingProduct.IsEnabled = cmd.IsEnabled
|
||||||
existingProduct.IsVisible = cmd.IsVisible
|
existingProduct.IsVisible = cmd.IsVisible
|
||||||
existingProduct.IsPackage = cmd.IsPackage
|
existingProduct.IsPackage = cmd.IsPackage
|
||||||
|
existingProduct.SellUIComponent = cmd.SellUIComponent
|
||||||
|
existingProduct.UIComponentPrice = decimal.NewFromFloat(cmd.UIComponentPrice)
|
||||||
existingProduct.SEOTitle = cmd.SEOTitle
|
existingProduct.SEOTitle = cmd.SEOTitle
|
||||||
existingProduct.SEODescription = cmd.SEODescription
|
existingProduct.SEODescription = cmd.SEODescription
|
||||||
existingProduct.SEOKeywords = cmd.SEOKeywords
|
existingProduct.SEOKeywords = cmd.SEOKeywords
|
||||||
@@ -486,21 +490,23 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Contex
|
|||||||
// convertToProductInfoResponse 转换为产品信息响应
|
// convertToProductInfoResponse 转换为产品信息响应
|
||||||
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
||||||
response := &responses.ProductInfoResponse{
|
response := &responses.ProductInfoResponse{
|
||||||
ID: product.ID,
|
ID: product.ID,
|
||||||
OldID: product.OldID,
|
OldID: product.OldID,
|
||||||
Name: product.Name,
|
Name: product.Name,
|
||||||
Code: product.Code,
|
Code: product.Code,
|
||||||
Description: product.Description,
|
Description: product.Description,
|
||||||
Content: product.Content,
|
Content: product.Content,
|
||||||
CategoryID: product.CategoryID,
|
CategoryID: product.CategoryID,
|
||||||
Price: product.Price.InexactFloat64(),
|
Price: product.Price.InexactFloat64(),
|
||||||
IsEnabled: product.IsEnabled,
|
IsEnabled: product.IsEnabled,
|
||||||
IsPackage: product.IsPackage,
|
IsPackage: product.IsPackage,
|
||||||
SEOTitle: product.SEOTitle,
|
SellUIComponent: product.SellUIComponent,
|
||||||
SEODescription: product.SEODescription,
|
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||||
SEOKeywords: product.SEOKeywords,
|
SEOTitle: product.SEOTitle,
|
||||||
CreatedAt: product.CreatedAt,
|
SEODescription: product.SEODescription,
|
||||||
UpdatedAt: product.UpdatedAt,
|
SEOKeywords: product.SEOKeywords,
|
||||||
|
CreatedAt: product.CreatedAt,
|
||||||
|
UpdatedAt: product.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加分类信息
|
// 添加分类信息
|
||||||
@@ -530,24 +536,26 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
|
|||||||
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
|
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
|
||||||
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
|
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
|
||||||
response := &responses.ProductAdminInfoResponse{
|
response := &responses.ProductAdminInfoResponse{
|
||||||
ID: product.ID,
|
ID: product.ID,
|
||||||
OldID: product.OldID,
|
OldID: product.OldID,
|
||||||
Name: product.Name,
|
Name: product.Name,
|
||||||
Code: product.Code,
|
Code: product.Code,
|
||||||
Description: product.Description,
|
Description: product.Description,
|
||||||
Content: product.Content,
|
Content: product.Content,
|
||||||
CategoryID: product.CategoryID,
|
CategoryID: product.CategoryID,
|
||||||
Price: product.Price.InexactFloat64(),
|
Price: product.Price.InexactFloat64(),
|
||||||
CostPrice: product.CostPrice.InexactFloat64(),
|
CostPrice: product.CostPrice.InexactFloat64(),
|
||||||
Remark: product.Remark,
|
Remark: product.Remark,
|
||||||
IsEnabled: product.IsEnabled,
|
IsEnabled: product.IsEnabled,
|
||||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||||
IsPackage: product.IsPackage,
|
IsPackage: product.IsPackage,
|
||||||
SEOTitle: product.SEOTitle,
|
SellUIComponent: product.SellUIComponent,
|
||||||
SEODescription: product.SEODescription,
|
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||||
SEOKeywords: product.SEOKeywords,
|
SEOTitle: product.SEOTitle,
|
||||||
CreatedAt: product.CreatedAt,
|
SEODescription: product.SEODescription,
|
||||||
UpdatedAt: product.UpdatedAt,
|
SEOKeywords: product.SEOKeywords,
|
||||||
|
CreatedAt: product.CreatedAt,
|
||||||
|
UpdatedAt: product.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加分类信息
|
// 添加分类信息
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func NewSubscriptionApplicationService(
|
|||||||
// UpdateSubscriptionPrice 更新订阅价格
|
// UpdateSubscriptionPrice 更新订阅价格
|
||||||
// 业务流程:1. 获取订阅 2. 更新价格 3. 保存订阅
|
// 业务流程:1. 获取订阅 2. 更新价格 3. 保存订阅
|
||||||
func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error {
|
func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error {
|
||||||
return s.productSubscriptionService.UpdateSubscriptionPrice(ctx, cmd.ID, cmd.Price)
|
return s.productSubscriptionService.UpdateSubscriptionPriceWithUIComponent(ctx, cmd.ID, cmd.Price, cmd.UIComponentPrice)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BatchUpdateSubscriptionPrices 一键改价
|
// BatchUpdateSubscriptionPrices 一键改价
|
||||||
@@ -377,16 +377,23 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(s
|
|||||||
productResponse = s.convertToProductSimpleResponse(subscription.Product)
|
productResponse = s.convertToProductSimpleResponse(subscription.Product)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取UI组件价格,如果订阅中没有设置,则从产品中获取
|
||||||
|
uiComponentPrice := subscription.UIComponentPrice.InexactFloat64()
|
||||||
|
if uiComponentPrice == 0 && subscription.Product != nil && (subscription.Product.IsPackage) {
|
||||||
|
uiComponentPrice = subscription.Product.UIComponentPrice.InexactFloat64()
|
||||||
|
}
|
||||||
|
|
||||||
return &responses.SubscriptionInfoResponse{
|
return &responses.SubscriptionInfoResponse{
|
||||||
ID: subscription.ID,
|
ID: subscription.ID,
|
||||||
UserID: subscription.UserID,
|
UserID: subscription.UserID,
|
||||||
ProductID: subscription.ProductID,
|
ProductID: subscription.ProductID,
|
||||||
Price: subscription.Price.InexactFloat64(),
|
Price: subscription.Price.InexactFloat64(),
|
||||||
User: userInfo,
|
UIComponentPrice: uiComponentPrice,
|
||||||
Product: productResponse,
|
User: userInfo,
|
||||||
APIUsed: subscription.APIUsed,
|
Product: productResponse,
|
||||||
CreatedAt: subscription.CreatedAt,
|
APIUsed: subscription.APIUsed,
|
||||||
UpdatedAt: subscription.UpdatedAt,
|
CreatedAt: subscription.CreatedAt,
|
||||||
|
UpdatedAt: subscription.UpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,16 +440,23 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponseFo
|
|||||||
productAdminResponse = s.convertToProductSimpleAdminResponse(subscription.Product)
|
productAdminResponse = s.convertToProductSimpleAdminResponse(subscription.Product)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取UI组件价格,如果订阅中没有设置,则从产品中获取
|
||||||
|
uiComponentPrice := subscription.UIComponentPrice.InexactFloat64()
|
||||||
|
if uiComponentPrice == 0 && subscription.Product != nil && (subscription.Product.IsPackage) {
|
||||||
|
uiComponentPrice = subscription.Product.UIComponentPrice.InexactFloat64()
|
||||||
|
}
|
||||||
|
|
||||||
return &responses.SubscriptionInfoResponse{
|
return &responses.SubscriptionInfoResponse{
|
||||||
ID: subscription.ID,
|
ID: subscription.ID,
|
||||||
UserID: subscription.UserID,
|
UserID: subscription.UserID,
|
||||||
ProductID: subscription.ProductID,
|
ProductID: subscription.ProductID,
|
||||||
Price: subscription.Price.InexactFloat64(),
|
Price: subscription.Price.InexactFloat64(),
|
||||||
User: userInfo,
|
UIComponentPrice: uiComponentPrice,
|
||||||
ProductAdmin: productAdminResponse,
|
User: userInfo,
|
||||||
APIUsed: subscription.APIUsed,
|
ProductAdmin: productAdminResponse,
|
||||||
CreatedAt: subscription.CreatedAt,
|
APIUsed: subscription.APIUsed,
|
||||||
UpdatedAt: subscription.UpdatedAt,
|
CreatedAt: subscription.CreatedAt,
|
||||||
|
UpdatedAt: subscription.UpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +478,8 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleAdminResponse
|
|||||||
Category: categoryResponse,
|
Category: categoryResponse,
|
||||||
IsPackage: product.IsPackage,
|
IsPackage: product.IsPackage,
|
||||||
},
|
},
|
||||||
CostPrice: product.CostPrice.InexactFloat64(),
|
CostPrice: product.CostPrice.InexactFloat64(),
|
||||||
|
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import (
|
|||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"tyapi-server/internal/domains/product/entities"
|
"tyapi-server/internal/domains/product/entities"
|
||||||
"tyapi-server/internal/domains/product/repositories"
|
"tyapi-server/internal/domains/product/repositories"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UIComponentApplicationService UI组件应用服务接口
|
// UIComponentApplicationService UI组件应用服务接口
|
||||||
@@ -93,6 +95,7 @@ type UIComponentApplicationServiceImpl struct {
|
|||||||
productUIComponentRepo repositories.ProductUIComponentRepository
|
productUIComponentRepo repositories.ProductUIComponentRepository
|
||||||
fileStorageService FileStorageService
|
fileStorageService FileStorageService
|
||||||
fileService UIComponentFileService
|
fileService UIComponentFileService
|
||||||
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileStorageService 文件存储服务接口
|
// FileStorageService 文件存储服务接口
|
||||||
@@ -108,12 +111,14 @@ func NewUIComponentApplicationService(
|
|||||||
productUIComponentRepo repositories.ProductUIComponentRepository,
|
productUIComponentRepo repositories.ProductUIComponentRepository,
|
||||||
fileStorageService FileStorageService,
|
fileStorageService FileStorageService,
|
||||||
fileService UIComponentFileService,
|
fileService UIComponentFileService,
|
||||||
|
logger *zap.Logger,
|
||||||
) UIComponentApplicationService {
|
) UIComponentApplicationService {
|
||||||
return &UIComponentApplicationServiceImpl{
|
return &UIComponentApplicationServiceImpl{
|
||||||
uiComponentRepo: uiComponentRepo,
|
uiComponentRepo: uiComponentRepo,
|
||||||
productUIComponentRepo: productUIComponentRepo,
|
productUIComponentRepo: productUIComponentRepo,
|
||||||
fileStorageService: fileStorageService,
|
fileStorageService: fileStorageService,
|
||||||
fileService: fileService,
|
fileService: fileService,
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,10 +187,14 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFile(ctx contex
|
|||||||
fileType := strings.ToLower(filepath.Ext(file.Filename))
|
fileType := strings.ToLower(filepath.Ext(file.Filename))
|
||||||
|
|
||||||
// 更新组件信息
|
// 更新组件信息
|
||||||
folderPath := "resources/Pure Component/src/ui"
|
folderPath := "resources/Pure_Component/src/ui"
|
||||||
createdComponent.FolderPath = &folderPath
|
createdComponent.FolderPath = &folderPath
|
||||||
createdComponent.FileType = &fileType
|
createdComponent.FileType = &fileType
|
||||||
|
|
||||||
|
// 记录文件上传时间
|
||||||
|
now := time.Now()
|
||||||
|
createdComponent.FileUploadTime = &now
|
||||||
|
|
||||||
// 仅对ZIP文件设置已解压标记
|
// 仅对ZIP文件设置已解压标记
|
||||||
if fileType == ".zip" {
|
if fileType == ".zip" {
|
||||||
createdComponent.IsExtracted = true
|
createdComponent.IsExtracted = true
|
||||||
@@ -255,9 +264,13 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFiles(ctx conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新组件信息
|
// 更新组件信息
|
||||||
folderPath := "resources/Pure Component/src/ui"
|
folderPath := "resources/Pure_Component/src/ui"
|
||||||
createdComponent.FolderPath = &folderPath
|
createdComponent.FolderPath = &folderPath
|
||||||
|
|
||||||
|
// 记录文件上传时间
|
||||||
|
now := time.Now()
|
||||||
|
createdComponent.FileUploadTime = &now
|
||||||
|
|
||||||
// 检查是否有ZIP文件
|
// 检查是否有ZIP文件
|
||||||
hasZipFile := false
|
hasZipFile := false
|
||||||
for _, fileHeader := range files {
|
for _, fileHeader := range files {
|
||||||
@@ -363,9 +376,13 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFilesAndPaths(c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新组件信息
|
// 更新组件信息
|
||||||
folderPath := "resources/Pure Component/src/ui"
|
folderPath := "resources/Pure_Component/src/ui"
|
||||||
createdComponent.FolderPath = &folderPath
|
createdComponent.FolderPath = &folderPath
|
||||||
|
|
||||||
|
// 记录文件上传时间
|
||||||
|
now := time.Now()
|
||||||
|
createdComponent.FileUploadTime = &now
|
||||||
|
|
||||||
// 检查是否有ZIP文件
|
// 检查是否有ZIP文件
|
||||||
hasZipFile := false
|
hasZipFile := false
|
||||||
for _, fileHeader := range files {
|
for _, fileHeader := range files {
|
||||||
@@ -442,20 +459,56 @@ func (s *UIComponentApplicationServiceImpl) UpdateUIComponent(ctx context.Contex
|
|||||||
|
|
||||||
// DeleteUIComponent 删除UI组件
|
// DeleteUIComponent 删除UI组件
|
||||||
func (s *UIComponentApplicationServiceImpl) DeleteUIComponent(ctx context.Context, id string) error {
|
func (s *UIComponentApplicationServiceImpl) DeleteUIComponent(ctx context.Context, id string) error {
|
||||||
|
// 获取组件信息
|
||||||
component, err := s.uiComponentRepo.GetByID(ctx, id)
|
component, err := s.uiComponentRepo.GetByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
s.logger.Error("获取UI组件失败", zap.Error(err), zap.String("id", id))
|
||||||
|
return fmt.Errorf("获取UI组件失败: %w", err)
|
||||||
}
|
}
|
||||||
if component == nil {
|
if component == nil {
|
||||||
|
s.logger.Warn("UI组件不存在", zap.String("id", id))
|
||||||
return ErrComponentNotFound
|
return ErrComponentNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除关联的文件
|
// 记录组件信息
|
||||||
if component.FilePath != nil {
|
s.logger.Info("开始删除UI组件",
|
||||||
_ = s.fileStorageService.DeleteFile(ctx, *component.FilePath)
|
zap.String("id", id),
|
||||||
|
zap.String("componentCode", component.ComponentCode),
|
||||||
|
zap.String("componentName", component.ComponentName),
|
||||||
|
zap.Bool("isExtracted", component.IsExtracted),
|
||||||
|
zap.Any("filePath", component.FilePath),
|
||||||
|
zap.Any("folderPath", component.FolderPath))
|
||||||
|
|
||||||
|
// 使用智能删除方法,根据组件编码和上传时间删除相关文件
|
||||||
|
if err := s.fileService.DeleteFilesByComponentCode(component.ComponentCode, component.FileUploadTime); err != nil {
|
||||||
|
// 记录错误但不阻止删除数据库记录
|
||||||
|
s.logger.Error("删除组件文件失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("componentCode", component.ComponentCode),
|
||||||
|
zap.Any("fileUploadTime", component.FileUploadTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.uiComponentRepo.Delete(ctx, id)
|
// 删除关联的文件(FilePath指向的文件)
|
||||||
|
if component.FilePath != nil {
|
||||||
|
if err := s.fileStorageService.DeleteFile(ctx, *component.FilePath); err != nil {
|
||||||
|
s.logger.Error("删除文件失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("filePath", *component.FilePath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除数据库记录
|
||||||
|
if err := s.uiComponentRepo.Delete(ctx, id); err != nil {
|
||||||
|
s.logger.Error("删除UI组件数据库记录失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("id", id))
|
||||||
|
return fmt.Errorf("删除UI组件数据库记录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("UI组件删除成功",
|
||||||
|
zap.String("id", id),
|
||||||
|
zap.String("componentCode", component.ComponentCode))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListUIComponents 获取UI组件列表
|
// ListUIComponents 获取UI组件列表
|
||||||
@@ -634,10 +687,14 @@ func (s *UIComponentApplicationServiceImpl) UploadAndExtractUIComponentFile(ctx
|
|||||||
fileType := strings.ToLower(filepath.Ext(file.Filename))
|
fileType := strings.ToLower(filepath.Ext(file.Filename))
|
||||||
|
|
||||||
// 更新组件信息
|
// 更新组件信息
|
||||||
folderPath := "resources/Pure Component/src/ui"
|
folderPath := "resources/Pure_Component/src/ui"
|
||||||
component.FolderPath = &folderPath
|
component.FolderPath = &folderPath
|
||||||
component.FileType = &fileType
|
component.FileType = &fileType
|
||||||
|
|
||||||
|
// 记录文件上传时间
|
||||||
|
now := time.Now()
|
||||||
|
component.FileUploadTime = &now
|
||||||
|
|
||||||
// 仅对ZIP文件设置已解压标记
|
// 仅对ZIP文件设置已解压标记
|
||||||
if fileType == ".zip" {
|
if fileType == ".zip" {
|
||||||
component.IsExtracted = true
|
component.IsExtracted = true
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ type UIComponentFileService interface {
|
|||||||
|
|
||||||
// 获取文件夹内容
|
// 获取文件夹内容
|
||||||
GetFolderContent(folderPath string) ([]FileInfo, error)
|
GetFolderContent(folderPath string) ([]FileInfo, error)
|
||||||
|
|
||||||
|
// 根据组件编码和上传时间智能删除组件相关文件
|
||||||
|
DeleteFilesByComponentCode(componentCode string, uploadTime *time.Time) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileInfo 文件信息
|
// FileInfo 文件信息
|
||||||
@@ -222,11 +225,34 @@ func (s *UIComponentFileServiceImpl) CreateFolderByCode(componentCode string) (s
|
|||||||
|
|
||||||
// DeleteFolder 删除组件文件夹
|
// DeleteFolder 删除组件文件夹
|
||||||
func (s *UIComponentFileServiceImpl) DeleteFolder(folderPath string) error {
|
func (s *UIComponentFileServiceImpl) DeleteFolder(folderPath string) error {
|
||||||
|
// 记录尝试删除的文件夹路径
|
||||||
|
s.logger.Info("尝试删除文件夹", zap.String("folderPath", folderPath))
|
||||||
|
|
||||||
|
// 获取文件夹信息,用于调试
|
||||||
|
if info, err := os.Stat(folderPath); err == nil {
|
||||||
|
s.logger.Info("文件夹信息",
|
||||||
|
zap.String("folderPath", folderPath),
|
||||||
|
zap.Bool("isDir", info.IsDir()),
|
||||||
|
zap.Int64("size", info.Size()),
|
||||||
|
zap.Time("modTime", info.ModTime()))
|
||||||
|
} else {
|
||||||
|
s.logger.Error("获取文件夹信息失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("folderPath", folderPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件夹是否存在
|
||||||
if !s.FolderExists(folderPath) {
|
if !s.FolderExists(folderPath) {
|
||||||
|
s.logger.Info("文件夹不存在", zap.String("folderPath", folderPath))
|
||||||
return nil // 文件夹不存在,不视为错误
|
return nil // 文件夹不存在,不视为错误
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 尝试删除文件夹
|
||||||
|
s.logger.Info("开始删除文件夹", zap.String("folderPath", folderPath))
|
||||||
if err := os.RemoveAll(folderPath); err != nil {
|
if err := os.RemoveAll(folderPath); err != nil {
|
||||||
|
s.logger.Error("删除文件夹失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("folderPath", folderPath))
|
||||||
return fmt.Errorf("删除文件夹失败: %w", err)
|
return fmt.Errorf("删除文件夹失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,3 +365,95 @@ func (s *UIComponentFileServiceImpl) extractZipFile(zipPath, destPath string) er
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteFilesByComponentCode 根据组件编码和上传时间智能删除组件相关文件
|
||||||
|
func (s *UIComponentFileServiceImpl) DeleteFilesByComponentCode(componentCode string, uploadTime *time.Time) error {
|
||||||
|
// 记录基础路径和组件编码
|
||||||
|
s.logger.Info("开始删除组件文件",
|
||||||
|
zap.String("basePath", s.basePath),
|
||||||
|
zap.String("componentCode", componentCode),
|
||||||
|
zap.Any("uploadTime", uploadTime))
|
||||||
|
|
||||||
|
// 1. 查找名为组件编码的文件夹
|
||||||
|
componentDir := filepath.Join(s.basePath, componentCode)
|
||||||
|
s.logger.Info("检查组件文件夹", zap.String("componentDir", componentDir))
|
||||||
|
|
||||||
|
if s.FolderExists(componentDir) {
|
||||||
|
s.logger.Info("找到组件文件夹,开始删除", zap.String("componentDir", componentDir))
|
||||||
|
if err := s.DeleteFolder(componentDir); err != nil {
|
||||||
|
s.logger.Error("删除组件文件夹失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("componentCode", componentCode),
|
||||||
|
zap.String("componentDir", componentDir))
|
||||||
|
return fmt.Errorf("删除组件文件夹失败: %w", err)
|
||||||
|
}
|
||||||
|
s.logger.Info("成功删除组件文件夹", zap.String("componentCode", componentCode))
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
s.logger.Info("组件文件夹不存在", zap.String("componentDir", componentDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查找文件名包含组件编码的文件
|
||||||
|
pattern := filepath.Join(s.basePath, "*"+componentCode+"*")
|
||||||
|
s.logger.Info("查找匹配文件", zap.String("pattern", pattern))
|
||||||
|
|
||||||
|
files, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("查找组件文件失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("pattern", pattern))
|
||||||
|
return fmt.Errorf("查找组件文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("找到匹配文件",
|
||||||
|
zap.Strings("files", files),
|
||||||
|
zap.Int("count", len(files)))
|
||||||
|
|
||||||
|
// 3. 如果没有上传时间,删除所有匹配的文件
|
||||||
|
if uploadTime == nil {
|
||||||
|
for _, file := range files {
|
||||||
|
if err := os.Remove(file); err != nil {
|
||||||
|
s.logger.Error("删除文件失败", zap.String("file", file), zap.Error(err))
|
||||||
|
} else {
|
||||||
|
s.logger.Info("成功删除文件", zap.String("file", file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 如果有上传时间,根据文件修改时间和上传时间的匹配度来删除文件
|
||||||
|
var deletedFiles []string
|
||||||
|
for _, file := range files {
|
||||||
|
// 获取文件信息
|
||||||
|
fileInfo, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("获取文件信息失败", zap.String("file", file), zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算文件修改时间与上传时间的差异(以秒为单位)
|
||||||
|
timeDiff := fileInfo.ModTime().Sub(*uploadTime).Seconds()
|
||||||
|
|
||||||
|
// 如果时间差在60秒内,认为是最匹配的文件
|
||||||
|
if timeDiff < 60 && timeDiff > -60 {
|
||||||
|
if err := os.Remove(file); err != nil {
|
||||||
|
s.logger.Warn("删除文件失败", zap.String("file", file), zap.Error(err))
|
||||||
|
} else {
|
||||||
|
deletedFiles = append(deletedFiles, file)
|
||||||
|
s.logger.Info("成功删除文件", zap.String("file", file),
|
||||||
|
zap.Time("uploadTime", *uploadTime),
|
||||||
|
zap.Time("fileModTime", fileInfo.ModTime()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到匹配的文件,记录警告但返回成功
|
||||||
|
if len(deletedFiles) == 0 && len(files) > 0 {
|
||||||
|
s.logger.Warn("没有找到匹配时间戳的文件",
|
||||||
|
zap.String("componentCode", componentCode),
|
||||||
|
zap.Time("uploadTime", *uploadTime),
|
||||||
|
zap.Int("foundFiles", len(files)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2772,18 +2772,19 @@ func (s *StatisticsApplicationServiceImpl) AdminGetTodayCertifiedEnterprises(ctx
|
|||||||
var enterprises []map[string]interface{}
|
var enterprises []map[string]interface{}
|
||||||
for _, cert := range completedCertifications {
|
for _, cert := range completedCertifications {
|
||||||
// 获取企业信息
|
// 获取企业信息
|
||||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, cert.UserID)
|
// 使用预加载方法一次性获取用户和企业信息
|
||||||
if err != nil {
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, cert.UserID)
|
||||||
s.logger.Warn("获取企业信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取用户基本信息(仅需要用户名)
|
|
||||||
user, err := s.userRepo.GetByID(ctx, cert.UserID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("获取用户信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
s.logger.Warn("获取用户信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取企业信息
|
||||||
|
enterpriseInfo := user.EnterpriseInfo
|
||||||
|
if enterpriseInfo == nil {
|
||||||
|
s.logger.Warn("用户没有企业信息", zap.String("user_id", cert.UserID))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
enterprise := map[string]interface{}{
|
enterprise := map[string]interface{}{
|
||||||
"id": cert.ID,
|
"id": cert.ID,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type Config struct {
|
|||||||
TianYanCha TianYanChaConfig `mapstructure:"tianyancha"`
|
TianYanCha TianYanChaConfig `mapstructure:"tianyancha"`
|
||||||
Alicloud AlicloudConfig `mapstructure:"alicloud"`
|
Alicloud AlicloudConfig `mapstructure:"alicloud"`
|
||||||
Xingwei XingweiConfig `mapstructure:"xingwei"`
|
Xingwei XingweiConfig `mapstructure:"xingwei"`
|
||||||
|
Jiguang JiguangConfig `mapstructure:"jiguang"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig HTTP服务器配置
|
// ServerConfig HTTP服务器配置
|
||||||
@@ -520,6 +521,35 @@ type XingweiLevelFileConfig struct {
|
|||||||
Compress bool `mapstructure:"compress"`
|
Compress bool `mapstructure:"compress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JiguangConfig 极光配置
|
||||||
|
type JiguangConfig struct {
|
||||||
|
URL string `mapstructure:"url"`
|
||||||
|
AppID string `mapstructure:"app_id"`
|
||||||
|
AppSecret string `mapstructure:"app_secret"`
|
||||||
|
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac,默认 hmac
|
||||||
|
Timeout time.Duration `mapstructure:"timeout"`
|
||||||
|
|
||||||
|
// 极光日志配置
|
||||||
|
Logging JiguangLoggingConfig `mapstructure:"logging"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JiguangLoggingConfig 极光日志配置
|
||||||
|
type JiguangLoggingConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
LogDir string `mapstructure:"log_dir"`
|
||||||
|
UseDaily bool `mapstructure:"use_daily"`
|
||||||
|
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
|
||||||
|
LevelConfigs map[string]JiguangLevelFileConfig `mapstructure:"level_configs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JiguangLevelFileConfig 极光级别文件配置
|
||||||
|
type JiguangLevelFileConfig struct {
|
||||||
|
MaxSize int `mapstructure:"max_size"`
|
||||||
|
MaxBackups int `mapstructure:"max_backups"`
|
||||||
|
MaxAge int `mapstructure:"max_age"`
|
||||||
|
Compress bool `mapstructure:"compress"`
|
||||||
|
}
|
||||||
|
|
||||||
// DomainConfig 域名配置
|
// DomainConfig 域名配置
|
||||||
type DomainConfig struct {
|
type DomainConfig struct {
|
||||||
API string `mapstructure:"api"` // API域名
|
API string `mapstructure:"api"` // API域名
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import (
|
|||||||
infra_events "tyapi-server/internal/infrastructure/events"
|
infra_events "tyapi-server/internal/infrastructure/events"
|
||||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||||
"tyapi-server/internal/infrastructure/external/email"
|
"tyapi-server/internal/infrastructure/external/email"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
"tyapi-server/internal/infrastructure/external/muzi"
|
"tyapi-server/internal/infrastructure/external/muzi"
|
||||||
"tyapi-server/internal/infrastructure/external/ocr"
|
"tyapi-server/internal/infrastructure/external/ocr"
|
||||||
"tyapi-server/internal/infrastructure/external/sms"
|
"tyapi-server/internal/infrastructure/external/sms"
|
||||||
@@ -366,6 +367,10 @@ func NewContainer() *Container {
|
|||||||
func(cfg *config.Config) (*xingwei.XingweiService, error) {
|
func(cfg *config.Config) (*xingwei.XingweiService, error) {
|
||||||
return xingwei.NewXingweiServiceWithConfig(cfg)
|
return xingwei.NewXingweiServiceWithConfig(cfg)
|
||||||
},
|
},
|
||||||
|
// JiguangService - 极光服务
|
||||||
|
func(cfg *config.Config) (*jiguang.JiguangService, error) {
|
||||||
|
return jiguang.NewJiguangServiceWithConfig(cfg)
|
||||||
|
},
|
||||||
func(cfg *config.Config) *yushan.YushanService {
|
func(cfg *config.Config) *yushan.YushanService {
|
||||||
return yushan.NewYushanService(
|
return yushan.NewYushanService(
|
||||||
cfg.Yushan.URL,
|
cfg.Yushan.URL,
|
||||||
@@ -571,6 +576,11 @@ func NewContainer() *Container {
|
|||||||
product_repo.NewGormComponentReportRepository,
|
product_repo.NewGormComponentReportRepository,
|
||||||
fx.As(new(domain_product_repo.ComponentReportRepository)),
|
fx.As(new(domain_product_repo.ComponentReportRepository)),
|
||||||
),
|
),
|
||||||
|
// 购买订单仓储
|
||||||
|
fx.Annotate(
|
||||||
|
finance_repo.NewGormPurchaseOrderRepository,
|
||||||
|
fx.As(new(domain_finance_repo.PurchaseOrderRepository)),
|
||||||
|
),
|
||||||
// UI组件仓储 - 同时注册具体类型和接口类型
|
// UI组件仓储 - 同时注册具体类型和接口类型
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
product_repo.NewGormUIComponentRepository,
|
product_repo.NewGormUIComponentRepository,
|
||||||
@@ -893,6 +903,7 @@ func NewContainer() *Container {
|
|||||||
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
||||||
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
||||||
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
||||||
|
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
|
||||||
userRepo domain_user_repo.UserRepository,
|
userRepo domain_user_repo.UserRepository,
|
||||||
txManager *shared_database.TransactionManager,
|
txManager *shared_database.TransactionManager,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
@@ -909,6 +920,7 @@ func NewContainer() *Container {
|
|||||||
alipayOrderRepo,
|
alipayOrderRepo,
|
||||||
wechatOrderRepo,
|
wechatOrderRepo,
|
||||||
rechargeRecordRepo,
|
rechargeRecordRepo,
|
||||||
|
purchaseOrderRepo,
|
||||||
componentReportRepo,
|
componentReportRepo,
|
||||||
userRepo,
|
userRepo,
|
||||||
txManager,
|
txManager,
|
||||||
@@ -950,6 +962,36 @@ func NewContainer() *Container {
|
|||||||
},
|
},
|
||||||
fx.As(new(product.ProductApplicationService)),
|
fx.As(new(product.ProductApplicationService)),
|
||||||
),
|
),
|
||||||
|
// 组件报告订单服务
|
||||||
|
func(
|
||||||
|
productRepo domain_product_repo.ProductRepository,
|
||||||
|
docRepo domain_product_repo.ProductDocumentationRepository,
|
||||||
|
apiConfigRepo domain_product_repo.ProductApiConfigRepository,
|
||||||
|
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
|
||||||
|
componentReportRepo domain_product_repo.ComponentReportRepository,
|
||||||
|
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
||||||
|
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
||||||
|
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
||||||
|
subscriptionRepo domain_product_repo.SubscriptionRepository,
|
||||||
|
aliPayService *payment.AliPayService,
|
||||||
|
wechatPayService *payment.WechatPayService,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) *product.ComponentReportOrderService {
|
||||||
|
return product.NewComponentReportOrderService(
|
||||||
|
productRepo,
|
||||||
|
docRepo,
|
||||||
|
apiConfigRepo,
|
||||||
|
purchaseOrderRepo,
|
||||||
|
componentReportRepo,
|
||||||
|
rechargeRecordRepo,
|
||||||
|
alipayOrderRepo,
|
||||||
|
wechatOrderRepo,
|
||||||
|
subscriptionRepo,
|
||||||
|
aliPayService,
|
||||||
|
wechatPayService,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
},
|
||||||
// 产品API配置应用服务 - 绑定到接口
|
// 产品API配置应用服务 - 绑定到接口
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
product.NewProductApiConfigApplicationService,
|
product.NewProductApiConfigApplicationService,
|
||||||
@@ -1055,7 +1097,7 @@ func NewContainer() *Container {
|
|||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) product.UIComponentApplicationService {
|
) product.UIComponentApplicationService {
|
||||||
// 创建UI组件文件服务
|
// 创建UI组件文件服务
|
||||||
basePath := "resources/Pure Component/src/ui"
|
basePath := "resources/Pure_Component/src/ui"
|
||||||
fileService := product.NewUIComponentFileService(basePath, logger)
|
fileService := product.NewUIComponentFileService(basePath, logger)
|
||||||
|
|
||||||
return product.NewUIComponentApplicationService(
|
return product.NewUIComponentApplicationService(
|
||||||
@@ -1063,6 +1105,7 @@ func NewContainer() *Container {
|
|||||||
productUIComponentRepo,
|
productUIComponentRepo,
|
||||||
fileStorageService,
|
fileStorageService,
|
||||||
fileService,
|
fileService,
|
||||||
|
logger,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
fx.As(new(product.UIComponentApplicationService)),
|
fx.As(new(product.UIComponentApplicationService)),
|
||||||
@@ -1183,6 +1226,7 @@ func NewContainer() *Container {
|
|||||||
docRepo domain_product_repo.ProductDocumentationRepository,
|
docRepo domain_product_repo.ProductDocumentationRepository,
|
||||||
apiConfigRepo domain_product_repo.ProductApiConfigRepository,
|
apiConfigRepo domain_product_repo.ProductApiConfigRepository,
|
||||||
componentReportRepo domain_product_repo.ComponentReportRepository,
|
componentReportRepo domain_product_repo.ComponentReportRepository,
|
||||||
|
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
|
||||||
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
|
||||||
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
|
||||||
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
|
||||||
@@ -1190,7 +1234,14 @@ func NewContainer() *Container {
|
|||||||
wechatPayService *payment.WechatPayService,
|
wechatPayService *payment.WechatPayService,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) *component_report.ComponentReportHandler {
|
) *component_report.ComponentReportHandler {
|
||||||
return component_report.NewComponentReportHandler(productRepo, docRepo, apiConfigRepo, componentReportRepo, rechargeRecordRepo, alipayOrderRepo, wechatOrderRepo, aliPayService, wechatPayService, logger)
|
return component_report.NewComponentReportHandler(productRepo, docRepo, apiConfigRepo, componentReportRepo, purchaseOrderRepo, rechargeRecordRepo, alipayOrderRepo, wechatOrderRepo, aliPayService, wechatPayService, logger)
|
||||||
|
},
|
||||||
|
// 组件报告订单处理器
|
||||||
|
func(
|
||||||
|
componentReportOrderService *product.ComponentReportOrderService,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) *handlers.ComponentReportOrderHandler {
|
||||||
|
return handlers.NewComponentReportOrderHandler(componentReportOrderService, logger)
|
||||||
},
|
},
|
||||||
// UI组件HTTP处理器
|
// UI组件HTTP处理器
|
||||||
func(
|
func(
|
||||||
@@ -1215,6 +1266,8 @@ func NewContainer() *Container {
|
|||||||
routes.NewProductRoutes,
|
routes.NewProductRoutes,
|
||||||
// 产品管理员路由
|
// 产品管理员路由
|
||||||
routes.NewProductAdminRoutes,
|
routes.NewProductAdminRoutes,
|
||||||
|
// 组件报告订单路由
|
||||||
|
routes.NewComponentReportOrderRoutes,
|
||||||
// UI组件路由
|
// UI组件路由
|
||||||
routes.NewUIComponentRoutes,
|
routes.NewUIComponentRoutes,
|
||||||
// 文章路由
|
// 文章路由
|
||||||
@@ -1331,6 +1384,7 @@ func RegisterRoutes(
|
|||||||
financeRoutes *routes.FinanceRoutes,
|
financeRoutes *routes.FinanceRoutes,
|
||||||
productRoutes *routes.ProductRoutes,
|
productRoutes *routes.ProductRoutes,
|
||||||
productAdminRoutes *routes.ProductAdminRoutes,
|
productAdminRoutes *routes.ProductAdminRoutes,
|
||||||
|
componentReportOrderRoutes *routes.ComponentReportOrderRoutes,
|
||||||
uiComponentRoutes *routes.UIComponentRoutes,
|
uiComponentRoutes *routes.UIComponentRoutes,
|
||||||
articleRoutes *routes.ArticleRoutes,
|
articleRoutes *routes.ArticleRoutes,
|
||||||
announcementRoutes *routes.AnnouncementRoutes,
|
announcementRoutes *routes.AnnouncementRoutes,
|
||||||
@@ -1352,12 +1406,8 @@ func RegisterRoutes(
|
|||||||
financeRoutes.Register(router)
|
financeRoutes.Register(router)
|
||||||
productRoutes.Register(router)
|
productRoutes.Register(router)
|
||||||
productAdminRoutes.Register(router)
|
productAdminRoutes.Register(router)
|
||||||
|
componentReportOrderRoutes.Register(router)
|
||||||
// UI组件路由需要特殊处理,因为它需要管理员中间件
|
uiComponentRoutes.Register(router)
|
||||||
engine := router.GetEngine()
|
|
||||||
adminGroup := engine.Group("/api/v1/admin")
|
|
||||||
adminGroup.Use(adminAuth.Handle())
|
|
||||||
uiComponentRoutes.RegisterRoutes(adminGroup, adminAuth)
|
|
||||||
|
|
||||||
articleRoutes.Register(router)
|
articleRoutes.Register(router)
|
||||||
announcementRoutes.Register(router)
|
announcementRoutes.Register(router)
|
||||||
|
|||||||
@@ -170,6 +170,24 @@ type YYSYD50FReq struct {
|
|||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IVYZZQT3Req struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IVYZSFELReq struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||||
|
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
|
||||||
|
}
|
||||||
|
type IVYZBPQ2Req struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
|
||||||
|
}
|
||||||
type YYSYF7DBReq struct {
|
type YYSYF7DBReq struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
StartDate string `json:"start_date" validate:"required,validDate" encrypt:"false"`
|
StartDate string `json:"start_date" validate:"required,validDate" encrypt:"false"`
|
||||||
@@ -250,6 +268,90 @@ type QCXG7A2BReq struct {
|
|||||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QCXG4896Req struct {
|
||||||
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
|
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
|
||||||
|
}
|
||||||
|
type QCXG5F3AReq struct {
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXGGB2QReq struct {
|
||||||
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
CarPlateType string `json:"carplate_type" validate:"required"`
|
||||||
|
}
|
||||||
|
type QCXGJJ2AReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
EngineNumber string `json:"engine_number" validate:"omitempty"`
|
||||||
|
NoticeModel string `json:"notice_model" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXG4I1ZReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXG1H7YReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXG1U4UReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||||
|
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||||
|
ImageURL string `json:"image_url" validate:"omitempty,url"`
|
||||||
|
RegURL string `json:"reg_url" validate:"omitempty,url"`
|
||||||
|
EngineNumber string `json:"engine_number" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IVYZ0S0DReq struct {
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IVYZ1J7HReq struct {
|
||||||
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
CarPlateType string `json:"carplate_type" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXG3Z3LReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||||
|
ImageURL string `json:"image_url" validate:"omitempty,url"`
|
||||||
|
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||||
|
EngineNumber string `json:"engine_number" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXG2T6SReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||||
|
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||||
|
ImageURL string `json:"image_url" validate:"required,url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXGYTS2Req struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"omitempty"`
|
||||||
|
PlateNo string `json:"plate_no" validate:"omitempty"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXGGJ3AReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXGP00WReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
PlateNo string `json:"plate_no" validate:"required"`
|
||||||
|
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
|
||||||
|
VlPhotoData string `json:"vlphoto_data" validate:"required,validBase64Image"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QCXG4D2EReq struct {
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
UserType string `json:"user_type" validate:"required,oneof=1 2 3"`
|
||||||
|
}
|
||||||
type COMENT01Req struct {
|
type COMENT01Req struct {
|
||||||
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
@@ -361,6 +463,23 @@ type QYGL5A3CReq struct {
|
|||||||
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QYGL2naoReq struct {
|
||||||
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
|
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||||
|
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
|
||||||
|
}
|
||||||
|
type QYGLNIO8Req struct {
|
||||||
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QYGLP0HTReq struct {
|
||||||
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
|
Flag string `json:"flag" validate:"omitempty"`
|
||||||
|
Dir string `json:"dir" validate:"omitempty,oneof=up down"`
|
||||||
|
MinPercent string `json:"min_percent" validate:"omitempty"`
|
||||||
|
MaxPercent string `json:"max_percent" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type QYGL8B4DReq struct {
|
type QYGL8B4DReq struct {
|
||||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||||
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
|
||||||
@@ -614,6 +733,13 @@ type QYGL2S0WReq struct {
|
|||||||
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全国企业司法模型服务查询_V1
|
||||||
|
type QYGL66SLReq struct {
|
||||||
|
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
|
||||||
|
AuthDate string `json:"auth_date" validate:"required,validAuthDate"`
|
||||||
|
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
|
||||||
|
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
|
||||||
|
}
|
||||||
type JRZQ2F8AReq struct {
|
type JRZQ2F8AReq struct {
|
||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
@@ -701,6 +827,11 @@ type IVYZ6M8PReq struct {
|
|||||||
Name string `json:"name" validate:"required,min=1,validName"`
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IVYZ9H2MReq struct {
|
||||||
|
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,validName"`
|
||||||
|
}
|
||||||
|
|
||||||
type YYSY9E4AReq struct {
|
type YYSY9E4AReq struct {
|
||||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"tyapi-server/internal/domains/api/services/processors/yysy"
|
"tyapi-server/internal/domains/api/services/processors/yysy"
|
||||||
"tyapi-server/internal/domains/product/services"
|
"tyapi-server/internal/domains/product/services"
|
||||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
"tyapi-server/internal/infrastructure/external/muzi"
|
"tyapi-server/internal/infrastructure/external/muzi"
|
||||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
@@ -54,6 +55,7 @@ func NewApiRequestService(
|
|||||||
alicloudService *alicloud.AlicloudService,
|
alicloudService *alicloud.AlicloudService,
|
||||||
zhichaService *zhicha.ZhichaService,
|
zhichaService *zhicha.ZhichaService,
|
||||||
xingweiService *xingwei.XingweiService,
|
xingweiService *xingwei.XingweiService,
|
||||||
|
jiguangService *jiguang.JiguangService,
|
||||||
validator interfaces.RequestValidator,
|
validator interfaces.RequestValidator,
|
||||||
productManagementService *services.ProductManagementService,
|
productManagementService *services.ProductManagementService,
|
||||||
) *ApiRequestService {
|
) *ApiRequestService {
|
||||||
@@ -61,7 +63,7 @@ func NewApiRequestService(
|
|||||||
combService := comb.NewCombService(productManagementService)
|
combService := comb.NewCombService(productManagementService)
|
||||||
|
|
||||||
// 创建处理器依赖容器
|
// 创建处理器依赖容器
|
||||||
processorDeps := processors.NewProcessorDependencies(westDexService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, validator, combService)
|
processorDeps := processors.NewProcessorDependencies(westDexService, muziService, yushanService, tianYanChaService, alicloudService, zhichaService, xingweiService, jiguangService, validator, combService)
|
||||||
|
|
||||||
// 统一注册所有处理器
|
// 统一注册所有处理器
|
||||||
registerAllProcessors(combService)
|
registerAllProcessors(combService)
|
||||||
@@ -157,6 +159,10 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"QYGL5A9T": qygl.ProcessQYGL5A9TRequest, //全国企业各类工商风险统计数量查询
|
"QYGL5A9T": qygl.ProcessQYGL5A9TRequest, //全国企业各类工商风险统计数量查询
|
||||||
"QYGL2S0W": qygl.ProcessQYGL2S0WRequest, //失信被执行企业个人查询
|
"QYGL2S0W": qygl.ProcessQYGL2S0WRequest, //失信被执行企业个人查询
|
||||||
"QYGL5CMP": qygl.ProcessQYGL5CMPRequest, //企业五要素验证
|
"QYGL5CMP": qygl.ProcessQYGL5CMPRequest, //企业五要素验证
|
||||||
|
"QYGL66SL": qygl.ProcessQYGL66SLRequest, //全国企业司法模型服务查询_V1
|
||||||
|
"QYGL2NAO": qygl.ProcessQYGL2naoRequest, //股权变更
|
||||||
|
"QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息
|
||||||
|
"QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透
|
||||||
|
|
||||||
// YYSY系列处理器
|
// YYSY系列处理器
|
||||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||||
@@ -206,6 +212,12 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
|
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
|
||||||
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
|
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
|
||||||
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
|
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
|
||||||
|
"IVYZ9H2M": ivyz.ProcessIVYZ9H2MRequest, //极光个人婚姻查询(V2版)
|
||||||
|
"IVYZZQT3": ivyz.ProcessIVYZZQT3Request, //人脸比对V3
|
||||||
|
"IVYZBPQ2": ivyz.ProcessIVYZBPQ2Request, //人脸比对V2
|
||||||
|
"IVYZSFEL": ivyz.ProcessIVYZSFELRequest, //全国自然人人像三要素核验_V1
|
||||||
|
"IVYZ0S0D": ivyz.ProcessIVYZ0S0DRequest, //劳动仲裁信息查询(个人版)
|
||||||
|
"IVYZ1J7H": ivyz.ProcessIVYZ1J7HRequest, //行驶证核查v2
|
||||||
|
|
||||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||||
@@ -217,6 +229,20 @@ func registerAllProcessors(combService *comb.CombService) {
|
|||||||
"QCXG9P1C": qcxg.ProcessQCXG9P1CRequest,
|
"QCXG9P1C": qcxg.ProcessQCXG9P1CRequest,
|
||||||
"QCXG8A3D": qcxg.ProcessQCXG8A3DRequest,
|
"QCXG8A3D": qcxg.ProcessQCXG8A3DRequest,
|
||||||
"QCXG6B4E": qcxg.ProcessQCXG6B4ERequest,
|
"QCXG6B4E": qcxg.ProcessQCXG6B4ERequest,
|
||||||
|
"QCXG4896": qcxg.ProcessQCXG4896Request,
|
||||||
|
"QCXG5F3A": qcxg.ProcessQCXG5F3ARequest, // 极光个人车辆查询
|
||||||
|
"QCXG4D2E": qcxg.ProcessQCXG4D2ERequest, // 极光名下车辆数量查询
|
||||||
|
"QCXGJJ2A": qcxg.ProcessQCXGJJ2ARequest, // vin码查车辆信息(一对多)
|
||||||
|
"QCXGGJ3A": qcxg.ProcessQCXGGJ3ARequest, // 车辆vin码查询号牌
|
||||||
|
"QCXGYTS2": qcxg.ProcessQCXGYTS2Request, // 车辆二要素核验v2
|
||||||
|
"QCXGP00W": qcxg.ProcessQCXGP00WRequest, // 车辆出险详版查询
|
||||||
|
"QCXGGB2Q": qcxg.ProcessQCXGGB2QRequest, // 车辆二要素核验V1
|
||||||
|
"QCXG4I1Z": qcxg.ProcessQCXG4I1ZRequest, // 车辆过户详版查询
|
||||||
|
"QCXG1H7Y": qcxg.ProcessQCXG1H7YRequest, // 车辆过户简版查询
|
||||||
|
"QCXG3Z3L": qcxg.ProcessQCXG3Z3LRequest, // 车辆维保详细版查询
|
||||||
|
"QCXG3Y6B": qcxg.ProcessQCXG3Y6BRequest, // 车辆维保简版查询
|
||||||
|
"QCXG2T6S": qcxg.ProcessQCXG2T6SRequest, // 车辆里程记录(品牌查询)
|
||||||
|
"QCXG1U4U": qcxg.ProcessQCXG1U4URequest, // 车辆里程记录(混合查询)
|
||||||
|
|
||||||
// DWBG系列处理器 - 多维报告
|
// DWBG系列处理器 - 多维报告
|
||||||
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
|
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
|
||||||
|
|||||||
@@ -198,7 +198,33 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
|||||||
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
|
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
|
||||||
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
|
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
|
||||||
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
|
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
|
||||||
|
"IVYZ9H2M": &dto.IVYZ9H2MReq{}, //极光个人婚姻查询(V2版)
|
||||||
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
|
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
|
||||||
|
"QCXG4896": &dto.QCXG4896Req{}, //网约车风险查询
|
||||||
|
"IVYZZQT3": &dto.IVYZZQT3Req{}, //人脸比对V3
|
||||||
|
"IVYZBPQ2": &dto.IVYZBPQ2Req{}, //人脸比对V2
|
||||||
|
"IVYZSFEL": &dto.IVYZSFELReq{}, //全国自然人人像三要素核验_V1
|
||||||
|
"QYGL66SL": &dto.QYGL66SLReq{}, //全国企业司法模型服务查询_V1
|
||||||
|
"QCXG5F3A": &dto.QCXG5F3AReq{}, //极光个人车辆查询
|
||||||
|
"QCXG4D2E": &dto.QCXG4D2EReq{}, //极光名下车辆数量查询
|
||||||
|
"QYGLP0HT": &dto.QYGLP0HTReq{}, //股权穿透
|
||||||
|
"QYGL2NAO": &dto.QYGL2naoReq{}, //股权变更
|
||||||
|
"QYGLNIO8": &dto.QYGLNIO8Req{}, //企业基本信息
|
||||||
|
"QYGL4B2E": &dto.QYGL5A3CReq{}, //税收违法
|
||||||
|
"QYGL7D9A": &dto.QYGL5A3CReq{}, //欠税公告
|
||||||
|
"IVYZ0S0D": &dto.IVYZ0S0DReq{}, //劳动仲裁信息查询(个人版)
|
||||||
|
"IVYZ1J7H": &dto.IVYZ1J7HReq{}, //行驶证核查v2
|
||||||
|
"QCXGJJ2A": &dto.QCXGJJ2AReq{}, //vin码查车辆信息(一对多)
|
||||||
|
"QCXGGJ3A": &dto.QCXGGJ3AReq{}, //车辆vin码查询号牌
|
||||||
|
"QCXGYTS2": &dto.QCXGYTS2Req{}, //车辆二要素核验v2
|
||||||
|
"QCXGP00W": &dto.QCXGP00WReq{}, //车辆出险详版查询
|
||||||
|
"QCXGGB2Q": &dto.QCXGGB2QReq{}, //车辆二要素核验V1
|
||||||
|
"QCXG4I1Z": &dto.QCXG4I1ZReq{}, //车辆过户详版查询
|
||||||
|
"QCXG1H7Y": &dto.QCXG1H7YReq{}, //车辆过户简版查询
|
||||||
|
"QCXG3Z3L": &dto.QCXG3Z3LReq{}, //车辆维保详细版查询
|
||||||
|
"QCXG3Y6B": &dto.QCXG1U4UReq{}, //车辆维保简版查询
|
||||||
|
"QCXG2T6S": &dto.QCXG2T6SReq{}, //车辆里程记录(品牌查询)
|
||||||
|
"QCXG1U4U": &dto.QCXG1U4UReq{}, //车辆里程记录(混合查询)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先返回已配置的DTO
|
// 优先返回已配置的DTO
|
||||||
@@ -321,6 +347,7 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
|
|||||||
case strings.HasPrefix(rule, "oneof="):
|
case strings.HasPrefix(rule, "oneof="):
|
||||||
values := strings.TrimPrefix(rule, "oneof=")
|
values := strings.TrimPrefix(rule, "oneof=")
|
||||||
frontendRules = append(frontendRules, "可选值: "+values)
|
frontendRules = append(frontendRules, "可选值: "+values)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -350,6 +377,8 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
|
|||||||
return "select"
|
return "select"
|
||||||
} else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "base64") {
|
} else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "base64") {
|
||||||
return "textarea"
|
return "textarea"
|
||||||
|
} else if strings.Contains(validation, "图片地址") {
|
||||||
|
return "url"
|
||||||
}
|
}
|
||||||
return "text"
|
return "text"
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
@@ -399,6 +428,16 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
|||||||
"owner_type": "企业主类型",
|
"owner_type": "企业主类型",
|
||||||
"type": "查询类型",
|
"type": "查询类型",
|
||||||
"query_reason_id": "查询原因ID",
|
"query_reason_id": "查询原因ID",
|
||||||
|
"flag": "层次",
|
||||||
|
"dir": "方向",
|
||||||
|
"min_percent": "股权穿透比例下限",
|
||||||
|
"max_percent": "股权穿透比例上限",
|
||||||
|
"engine_number": "发动机号码",
|
||||||
|
"notice_model": "车辆型号",
|
||||||
|
"vlphoto_data": "行驶证图片",
|
||||||
|
"carplate_type": "车辆号牌类型",
|
||||||
|
"image_url": "行驶证图片地址",
|
||||||
|
"reg_url": "车辆登记证图片地址",
|
||||||
}
|
}
|
||||||
|
|
||||||
if label, exists := labelMap[jsonTag]; exists {
|
if label, exists := labelMap[jsonTag]; exists {
|
||||||
@@ -444,6 +483,16 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
|||||||
"ownerType": "1",
|
"ownerType": "1",
|
||||||
"type": "per",
|
"type": "per",
|
||||||
"query_reason_id": "1",
|
"query_reason_id": "1",
|
||||||
|
"flag": "4",
|
||||||
|
"dir": "down",
|
||||||
|
"min_percent": "0",
|
||||||
|
"max_percent": "1",
|
||||||
|
"engine_number": "1234567890",
|
||||||
|
"notice_model": "1",
|
||||||
|
"vlphoto_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||||
|
"carplate_type": "01",
|
||||||
|
"image_url": "https://example.com/images/driving_license.jpg",
|
||||||
|
"reg_url": "https://example.com/images/vehicle_registration.jpg",
|
||||||
}
|
}
|
||||||
|
|
||||||
if example, exists := exampleMap[jsonTag]; exists {
|
if example, exists := exampleMap[jsonTag]; exists {
|
||||||
@@ -498,6 +547,16 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
|||||||
"ownerType": "请选择企业主类型",
|
"ownerType": "请选择企业主类型",
|
||||||
"type": "请选择查询类型",
|
"type": "请选择查询类型",
|
||||||
"query_reason_id": "请选择查询原因ID",
|
"query_reason_id": "请选择查询原因ID",
|
||||||
|
"flag": "请输入层次(最大4)",
|
||||||
|
"dir": "请选择方向(up-向上,down-向下)",
|
||||||
|
"min_percent": "请输入股权穿透比例下限(默认0)",
|
||||||
|
"max_percent": "请输入股权穿透比例上限(默认1)",
|
||||||
|
"engine_number": "请输入发动机号码",
|
||||||
|
"notice_model": "请输入车辆型号",
|
||||||
|
"vlphoto_data": "请输入行驶证图片",
|
||||||
|
"carplate_type": "请选择车辆号牌类型(01-大型汽车 02-小型汽车 03-使馆汽车 04-领馆汽车 05-境外汽车 06-外籍汽车 07-普通摩托车 08-轻便摩托车 09-使馆摩托车 10-领馆摩托车 11-境外摩托车 12-外籍摩托车 13-低速车 14-拖拉机 15-挂车 16-教练汽车 17-教练摩托车 20-临时入境汽车 21-临时入境摩托车 22-临时行驶车 23-警用汽车 24-警用摩托 51-新能源大型车 52-新能源小型车)",
|
||||||
|
"image_url": "请输入行驶证图片地址",
|
||||||
|
"reg_url": "请输入车辆登记证图片地址",
|
||||||
}
|
}
|
||||||
|
|
||||||
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
||||||
@@ -554,6 +613,16 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
|||||||
"owner_type": "企业主类型编码:1-法定代表人;2-主要人员;3-自然人股东;4-法定代表人及自然人股东;5-其他",
|
"owner_type": "企业主类型编码:1-法定代表人;2-主要人员;3-自然人股东;4-法定代表人及自然人股东;5-其他",
|
||||||
"type": "查询类型:per-人员,ent-企业 ",
|
"type": "查询类型:per-人员,ent-企业 ",
|
||||||
"query_reason_id": "查询原因ID:1-授信审批;2-贷中管理;3-贷后管理;4-异议处理;5-担保查询;6-租赁资质审查;7-融资租赁审批;8-借贷撮合查询;9-保险审批;10-资质审核;11-风控审核;12-企业背调",
|
"query_reason_id": "查询原因ID:1-授信审批;2-贷中管理;3-贷后管理;4-异议处理;5-担保查询;6-租赁资质审查;7-融资租赁审批;8-借贷撮合查询;9-保险审批;10-资质审核;11-风控审核;12-企业背调",
|
||||||
|
"flag": "层次,最大4",
|
||||||
|
"dir": "方向:up-向上穿透,down-向下穿透",
|
||||||
|
"min_percent": "股权穿透比例下限(大于等于),默认为0,支持小数点后两位(以小数指代百分比)",
|
||||||
|
"max_percent": "股权穿透比例上限(小于等于),默认为1,支持小数点后两位(以小数指代百分比)",
|
||||||
|
"engine_number": "发动机号码",
|
||||||
|
"notice_model": "车辆型号",
|
||||||
|
"vlphoto_data": "行驶证图片:base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||||
|
"carplate_type": "车辆号牌类型:01-大型汽车;02-小型汽车;03-使馆汽车;04-领馆汽车;05-境外汽车;06-外籍汽车;07-普通摩托车;08-轻便摩托车;09-使馆摩托车;10-领馆摩托车;11-境外摩托车;12-外籍摩托车;13-低速车;14-拖拉机;15-挂车;16-教练汽车;17-教练摩托车;20-临时入境汽车;21-临时入境摩托车;22-临时行驶车;23-警用汽车;24-警用摩托;51-新能源大型车;52-新能源小型车",
|
||||||
|
"image_url": "行驶证图片地址(必填):请提供行驶证的图片URL地址",
|
||||||
|
"reg_url": "车辆登记证图片地址(非必填):请提供车辆登记证的图片URL地址",
|
||||||
}
|
}
|
||||||
|
|
||||||
if desc, exists := descMap[jsonTag]; exists {
|
if desc, exists := descMap[jsonTag]; exists {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"tyapi-server/internal/application/api/commands"
|
"tyapi-server/internal/application/api/commands"
|
||||||
"tyapi-server/internal/infrastructure/external/alicloud"
|
"tyapi-server/internal/infrastructure/external/alicloud"
|
||||||
"tyapi-server/internal/infrastructure/external/muzi"
|
"tyapi-server/internal/infrastructure/external/muzi"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
"tyapi-server/internal/infrastructure/external/tianyancha"
|
"tyapi-server/internal/infrastructure/external/tianyancha"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
"tyapi-server/internal/infrastructure/external/xingwei"
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
@@ -32,6 +33,7 @@ type ProcessorDependencies struct {
|
|||||||
AlicloudService *alicloud.AlicloudService
|
AlicloudService *alicloud.AlicloudService
|
||||||
ZhichaService *zhicha.ZhichaService
|
ZhichaService *zhicha.ZhichaService
|
||||||
XingweiService *xingwei.XingweiService
|
XingweiService *xingwei.XingweiService
|
||||||
|
JiguangService *jiguang.JiguangService
|
||||||
Validator interfaces.RequestValidator
|
Validator interfaces.RequestValidator
|
||||||
CombService CombServiceInterface // Changed to interface to break import cycle
|
CombService CombServiceInterface // Changed to interface to break import cycle
|
||||||
Options *commands.ApiCallOptions // 添加Options支持
|
Options *commands.ApiCallOptions // 添加Options支持
|
||||||
@@ -47,6 +49,7 @@ func NewProcessorDependencies(
|
|||||||
alicloudService *alicloud.AlicloudService,
|
alicloudService *alicloud.AlicloudService,
|
||||||
zhichaService *zhicha.ZhichaService,
|
zhichaService *zhicha.ZhichaService,
|
||||||
xingweiService *xingwei.XingweiService,
|
xingweiService *xingwei.XingweiService,
|
||||||
|
jiguangService *jiguang.JiguangService,
|
||||||
validator interfaces.RequestValidator,
|
validator interfaces.RequestValidator,
|
||||||
combService CombServiceInterface, // Changed to interface
|
combService CombServiceInterface, // Changed to interface
|
||||||
) *ProcessorDependencies {
|
) *ProcessorDependencies {
|
||||||
@@ -58,6 +61,7 @@ func NewProcessorDependencies(
|
|||||||
AlicloudService: alicloudService,
|
AlicloudService: alicloudService,
|
||||||
ZhichaService: zhichaService,
|
ZhichaService: zhichaService,
|
||||||
XingweiService: xingweiService,
|
XingweiService: xingweiService,
|
||||||
|
JiguangService: jiguangService,
|
||||||
Validator: validator,
|
Validator: validator,
|
||||||
CombService: combService,
|
CombService: combService,
|
||||||
Options: nil, // 初始化为nil,在调用时设置
|
Options: nil, // 初始化为nil,在调用时设置
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"{
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||||
}
|
}
|
||||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func ProcessFLXGDEA9Request(ctx context.Context, params []byte, deps *processors
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
|
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"{
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
||||||
}
|
}
|
||||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ0S0DRequest IVYZ0S0D API处理方法 - 劳动仲裁信息查询(个人版)
|
||||||
|
func ProcessIVYZ0S0DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZ0S0DReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"id": paramsDto.IDCard,
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "labor-arbitration-information", "labor-arbitration-information", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ1J7HRequest IVYZ1J7H API处理方法 - 行驶证核查v2
|
||||||
|
func ProcessIVYZ1J7HRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZ1J7HReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"plate": paramsDto.PlateNo,
|
||||||
|
"plateType": paramsDto.CarPlateType,
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "labor-arbitration-information", "labor-arbitration-information", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -36,6 +36,11 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if returnType == "" {
|
if returnType == "" {
|
||||||
returnType = "1"
|
returnType = "1"
|
||||||
}
|
}
|
||||||
|
paramSign := map[string]interface{}{
|
||||||
|
"returnType": returnType,
|
||||||
|
"realName": encryptedName,
|
||||||
|
"certCode": encryptedCertCode,
|
||||||
|
}
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"realName": encryptedName,
|
"realName": encryptedName,
|
||||||
@@ -43,7 +48,8 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
"returnType": returnType,
|
"returnType": returnType,
|
||||||
}
|
}
|
||||||
|
|
||||||
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", reqData)
|
|
||||||
|
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", "/academic",reqData,paramSign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, muzi.ErrDatasource):
|
case errors.Is(err, muzi.ErrDatasource):
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ9H2MRequest IVYZ9H2M API处理方法 - 极光个人婚姻查询(V2版)
|
||||||
|
func ProcessIVYZ9H2MRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZ9H2MReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"id_no": paramsDto.IDCard,
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
// apiCode: marriage-single-v2 (用于请求头)
|
||||||
|
// apiPath: marriage/single-v2 (用于URL路径)
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "marriage-single-v2", "marriage/single-v2", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZBPQ2Request IVYZBPQ2 人脸比对V2API处理方法
|
||||||
|
func ProcessIVYZBPQ2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZBPQ2Req
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求数据,使用xingwei服务的正确字段名
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
"idCardNum": paramsDto.IDCard,
|
||||||
|
"image": paramsDto.PhotoData,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用行为数据API,使用指定的project_id
|
||||||
|
projectID := "CDJ-1104321425593790464"
|
||||||
|
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, xingwei.ErrNotFound) {
|
||||||
|
// 查空情况,返回特定的查空错误
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||||
|
// 数据源错误
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||||
|
// 系统错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
} else {
|
||||||
|
// 其他未知错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZSFELRequest IVYZSFEL 全国自然人人像三要素核验_V1API处理方法
|
||||||
|
func ProcessIVYZSFELRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZSFELReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求数据,使用xingwei服务的正确字段名
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
"idCardNum": paramsDto.IDCard,
|
||||||
|
"photo": paramsDto.PhotoData,
|
||||||
|
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用行为数据API,使用指定的project_id
|
||||||
|
projectID := "CDJ-1068350101927161856"
|
||||||
|
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, xingwei.ErrNotFound) {
|
||||||
|
// 查空情况,返回特定的查空错误
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||||
|
// 数据源错误
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||||
|
// 系统错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
} else {
|
||||||
|
// 其他未知错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZZQT3Request IVYZZQT3 人脸比对V3API处理方法
|
||||||
|
func ProcessIVYZZQT3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.IVYZZQT3Req
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求数据,使用xingwei服务的正确字段名
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
"idCardNum": paramsDto.IDCard,
|
||||||
|
"image": paramsDto.PhotoData,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用行为数据API,使用指定的project_id
|
||||||
|
projectID := "CDJ-1104321430396268544"
|
||||||
|
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, xingwei.ErrNotFound) {
|
||||||
|
// 查空情况,返回特定的查空错误
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||||
|
// 数据源错误
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||||
|
// 系统错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
} else {
|
||||||
|
// 其他未知错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG1H7YRequest QCXG1H7Y API处理方法 - 车辆过户简版查询
|
||||||
|
func ProcessQCXG1H7YRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG1H7YReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"vin": paramsDto.VinCode,
|
||||||
|
"plateNumber": paramsDto.PlateNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
// apiCode: car-vin (用于请求头)
|
||||||
|
// apiPath: car/car-vin (用于URL路径)
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-transfer", "vehicle/transfer", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG1U4URequest QCXG1U4U API处理方法 - 车辆里程记录(混合查询)
|
||||||
|
func ProcessQCXG1U4URequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG1U4UReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"vin": paramsDto.VinCode,
|
||||||
|
"licensePlate": paramsDto.PlateNo,
|
||||||
|
"callbackUrl": paramsDto.ReturnURL,
|
||||||
|
"imageUrl": paramsDto.ImageURL,
|
||||||
|
"regUrl": paramsDto.RegURL,
|
||||||
|
"engine": paramsDto.EngineNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-mileage-b", "vehicle/car-mileage-b", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG2T6SRequest QCXG2T6S API处理方法 - 车辆里程记录(品牌查询)
|
||||||
|
func ProcessQCXG2T6SRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG2T6SReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"vin": paramsDto.VinCode,
|
||||||
|
"licensePlate": paramsDto.PlateNo,
|
||||||
|
"callbackUrl": paramsDto.ReturnURL,
|
||||||
|
"imageUrl": paramsDto.ImageURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-mileage-a", "vehicle/car-mileage-a", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG3Y6BRequest QCXG3Y6B API处理方法 - 车辆维保简版查询
|
||||||
|
func ProcessQCXG3Y6BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG1U4UReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"vin": paramsDto.VinCode,
|
||||||
|
"licensePlate": paramsDto.PlateNo,
|
||||||
|
"notifyUrl": paramsDto.ReturnURL,
|
||||||
|
"imageUrl": paramsDto.ImageURL,
|
||||||
|
"regUrl": paramsDto.RegURL,
|
||||||
|
"engine": paramsDto.EngineNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-maintenance-info-v2", "vehicle/car-maintenance-info-v2", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG3Z3LRequest QCXG3Z3L API处理方法 - 车辆维保详细版查询
|
||||||
|
func ProcessQCXG3Z3LRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG3Z3LReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"vin": paramsDto.VinCode,
|
||||||
|
"notifyUrl": paramsDto.ReturnURL,
|
||||||
|
"imageUrl": paramsDto.ImageURL,
|
||||||
|
"plateNo": paramsDto.PlateNo,
|
||||||
|
"engine": paramsDto.EngineNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-maintenance-info-v2", "vehicle/car-maintenance-info-v2", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/muzi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG4896MRequest QCXG4896 API处理方法 - 网约车风险查询
|
||||||
|
func ProcessQCXG4896Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG4896Req
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
paramSign := map[string]interface{}{
|
||||||
|
"paramName": "licenseNo",
|
||||||
|
"paramValue": paramsDto.PlateNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"paramName": "licenseNo",
|
||||||
|
"paramValue": paramsDto.PlateNo,
|
||||||
|
"startTime": strings.Split(paramsDto.AuthDate, "-")[0],
|
||||||
|
"endTime": strings.Split(paramsDto.AuthDate, "-")[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
respData, err := deps.MuziService.CallAPI(ctx, "PC0031", "/hailingScoreBySearch", reqData,paramSign)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, muzi.ErrDatasource):
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
case errors.Is(err, muzi.ErrSystem):
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
default:
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respData, nil
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG4D2ERequest QCXG4D2E API处理方法 - 极光名下车辆数量查询
|
||||||
|
func ProcessQCXG4D2ERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG4D2EReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"idNum": paramsDto.IDCard,
|
||||||
|
"userType": paramsDto.UserType,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
// apiCode: vehicle-inquiry-under-name (用于请求头)
|
||||||
|
// apiPath: vehicle/inquiry-under-name (用于URL路径)
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-inquiry-under-name", "vehicle/inquiry-under-name", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG4I1ZRequest QCXG4I1Z API处理方法 - 车辆过户详版查询
|
||||||
|
func ProcessQCXG4I1ZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG4I1ZReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"vim": paramsDto.VinCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
// apiCode: car-vin (用于请求头)
|
||||||
|
// apiPath: car/car-vin (用于URL路径)
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "transfer-information", "vehicle/transfer-information", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXG5F3ARequest QCXG5F3A API处理方法 - 极光个人车辆查询
|
||||||
|
func ProcessQCXG5F3ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXG5F3AReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"id_card": paramsDto.IDCard,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
// apiCode: vehicle-person-vehicles (用于请求头)
|
||||||
|
// apiPath: vehicle/person-vehicles (用于URL路径)
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-person-vehicles", "vehicle/person-vehicles", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,10 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcessQCXG9P1CRequest QCXG9P1C API处理方法
|
// ProcessQCXG9P1CRequest QCXG9P1C API处理方法
|
||||||
@@ -21,44 +24,48 @@ func ProcessQCXG9P1CRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
|
// 构建请求参数
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"idCard": encryptedIDCard,
|
"id_card": paramsDto.IDCard,
|
||||||
"authorized": paramsDto.Authorized,
|
|
||||||
}
|
|
||||||
if paramsDto.Name != "" {
|
|
||||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
|
||||||
}
|
|
||||||
reqData["name"] = encryptedName
|
|
||||||
}
|
|
||||||
// 如果传了 vehicleType,则添加到请求数据中
|
|
||||||
if paramsDto.VehicleType != "" {
|
|
||||||
reqData["vehicleType"] = paramsDto.VehicleType
|
|
||||||
}
|
|
||||||
if paramsDto.UserType != "" {
|
|
||||||
reqData["userType"] = paramsDto.UserType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI051", reqData)
|
// 调用极光API
|
||||||
|
// apiCode: vehicle-person-vehicles (用于请求头)
|
||||||
|
// apiPath: vehicle/person-vehicles (用于URL路径)
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-person-vehicles", "vehicle/person-vehicles", reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, zhicha.ErrDatasource) {
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
return nil, errors.Join(processors.ErrDatasource, err)
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将响应数据转换为 JSON 字节
|
// 使用 gjson 检查并转换 vehicleCount 字段
|
||||||
respBytes, err := json.Marshal(respData)
|
vehicleCountResult := gjson.GetBytes(respBytes, "vehicleCount")
|
||||||
if err != nil {
|
if vehicleCountResult.Exists() && vehicleCountResult.Type == gjson.String {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
// 如果是字符串类型,转换为整数
|
||||||
|
vehicleCountInt, err := strconv.Atoi(vehicleCountResult.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
// 解析 JSON 并修改 vehicleCount 字段
|
||||||
|
var respData map[string]interface{}
|
||||||
|
if err := json.Unmarshal(respBytes, &respData); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
respData["vehicleCount"] = vehicleCountInt
|
||||||
|
// 重新序列化为JSON并返回
|
||||||
|
resultBytes, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
return resultBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果 vehicleCount 不存在或不是字符串,直接返回原始响应
|
||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CarPlateTypeMap 号牌类型代码到名称的映射
|
||||||
|
var CarPlateTypeMap = map[string]string{
|
||||||
|
"01": "大型汽车",
|
||||||
|
"02": "小型汽车",
|
||||||
|
"03": "使馆汽车",
|
||||||
|
"04": "领馆汽车",
|
||||||
|
"05": "境外汽车",
|
||||||
|
"06": "外籍汽车",
|
||||||
|
"07": "普通摩托车",
|
||||||
|
"08": "轻便摩托车",
|
||||||
|
"09": "使馆摩托车",
|
||||||
|
"10": "领馆摩托车",
|
||||||
|
"11": "境外摩托车",
|
||||||
|
"12": "外籍摩托车",
|
||||||
|
"13": "低速车",
|
||||||
|
"14": "拖拉机",
|
||||||
|
"15": "挂车",
|
||||||
|
"16": "教练汽车",
|
||||||
|
"17": "教练摩托车",
|
||||||
|
"20": "临时入境汽车",
|
||||||
|
"21": "临时入境摩托车",
|
||||||
|
"22": "临时行驶车",
|
||||||
|
"23": "警用汽车",
|
||||||
|
"24": "警用摩托",
|
||||||
|
"51": "新能源大型车",
|
||||||
|
"52": "新能源小型车",
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCarPlateTypeName 根据号牌类型代码获取中文名称
|
||||||
|
func getCarPlateTypeName(code string) string {
|
||||||
|
if name, exists := CarPlateTypeMap[code]; exists {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return code // 如果找不到,返回原值
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessQCXGGB2QRequest QCXGGB2Q API处理方法 - 车辆二要素核验V1
|
||||||
|
func ProcessQCXGGB2QRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXGGB2QReq
|
||||||
|
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{}{
|
||||||
|
"plateNumber": paramsDto.PlateNo,
|
||||||
|
"owner": paramsDto.Name,
|
||||||
|
"plateType": getCarPlateTypeName(paramsDto.CarPlateType),
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-factor-two-auth", "vehicle/factor-two-auth", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXGGJ3ARequest QCXGGJ3A API处理方法 - 车辆vin码查询号牌
|
||||||
|
func ProcessQCXGGJ3ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXGGJ3AReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"vin": paramsDto.VinCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
// apiCode: car-vin (用于请求头)
|
||||||
|
// apiPath: car/car-vin (用于URL路径)
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-vin", "vehicle/car-vin", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXGJJ2ARequest QCXGJJ2A API处理方法 - vin码查车辆信息(一对多)
|
||||||
|
func ProcessQCXGJJ2ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXGJJ2AReq
|
||||||
|
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,
|
||||||
|
"engineNumber": paramsDto.EngineNumber,
|
||||||
|
"noticeModel": paramsDto.NoticeModel,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用极光API
|
||||||
|
// apiCode: carInfo-vin (用于请求头)
|
||||||
|
// apiPath: car/carInfo-vin (用于URL路径)
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "carInfo-vin", "car/carInfo-vin", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,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/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXGP00WRequest QCXGP00W API处理方法 - 车辆出险详版查询
|
||||||
|
func ProcessQCXGP00WRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXGP00WReq
|
||||||
|
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,
|
||||||
|
"licenseNo": paramsDto.PlateNo,
|
||||||
|
"notifyUrl": paramsDto.ReturnURL,
|
||||||
|
"image": paramsDto.VlPhotoData,
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "car-accident-order-high", "car-accident-precision-order", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package qcxg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/jiguang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQCXGYTS2Request QCXGYTS2 API处理方法 - 车辆二要素核验v2
|
||||||
|
func ProcessQCXGYTS2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QCXGYTS2Req
|
||||||
|
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,
|
||||||
|
"plate": paramsDto.PlateNo,
|
||||||
|
"name": paramsDto.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-person-and-vehicle-verification-v2", "vehicle/person-and-vehicle-verification-v2", reqData)
|
||||||
|
if err != nil {
|
||||||
|
// 根据错误类型返回相应的错误
|
||||||
|
if errors.Is(err, jiguang.ErrNotFound) {
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, jiguang.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极光服务已经返回了 data 字段的 JSON,直接返回即可
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -119,13 +119,3 @@ func ProcessQYGL23T7Request(ctx context.Context, params []byte, deps *processors
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// createStatusResponse 创建状态响应
|
|
||||||
func createStatusResponse(status int) []byte {
|
|
||||||
response := map[string]interface{}{
|
|
||||||
"status": status,
|
|
||||||
}
|
|
||||||
|
|
||||||
respBytes, _ := json.Marshal(response)
|
|
||||||
return respBytes
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQYGL2naoRequest QYGL2NAO API处理方法 - 股权变更
|
||||||
|
func ProcessQYGL2naoRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QYGL2naoReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
// 设置默认值
|
||||||
|
pageSize := paramsDto.PageSize
|
||||||
|
if pageSize == 0 {
|
||||||
|
pageSize = int64(20)
|
||||||
|
}
|
||||||
|
pageNum := paramsDto.PageNum
|
||||||
|
if pageNum == 0 {
|
||||||
|
pageNum = int64(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建API调用参数
|
||||||
|
apiParams := map[string]string{
|
||||||
|
"keyword": paramsDto.EntCode,
|
||||||
|
"pageSize": strconv.FormatInt(pageSize, 10),
|
||||||
|
"pageNum": strconv.FormatInt(pageNum, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用天眼查API - 企业基本信息
|
||||||
|
response, err := deps.TianYanChaService.CallAPI(ctx, "holderChange", apiParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, convertTianYanChaError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查天眼查API调用是否成功
|
||||||
|
if !response.Success {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, errors.New(response.Message))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回天眼查响应数据
|
||||||
|
respBytes, err := json.Marshal(response.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -24,6 +24,26 @@ func ProcessQYGL5CMPRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 第一步:企业信息验证 - 调用天眼查API
|
||||||
|
_, err := verifyEnterpriseInfo(ctx, paramsDto, deps)
|
||||||
|
if err != nil {
|
||||||
|
// 企业信息验证失败,只返回简单的状态码
|
||||||
|
return createStatusResponse(1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业信息验证通过,继续个人信息验证
|
||||||
|
_, err = verifyPersonalInfo(ctx, paramsDto, deps)
|
||||||
|
if err != nil {
|
||||||
|
// 个人信息验证失败,只返回简单的状态码
|
||||||
|
return createStatusResponse(1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 两个验证都通过,只返回成功状态码
|
||||||
|
return createStatusResponse(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyEnterpriseInfo 验证企业信息
|
||||||
|
func verifyEnterpriseInfo(ctx context.Context, paramsDto dto.QYGL5CMPReq, deps *processors.ProcessorDependencies) (map[string]interface{}, error) {
|
||||||
// 构建API调用参数
|
// 构建API调用参数
|
||||||
apiParams := map[string]string{
|
apiParams := map[string]string{
|
||||||
"code": paramsDto.EntCode,
|
"code": paramsDto.EntCode,
|
||||||
@@ -39,45 +59,41 @@ func ProcessQYGL5CMPRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
|
|
||||||
// 检查天眼查API调用是否成功
|
// 检查天眼查API调用是否成功
|
||||||
if !response.Success {
|
if !response.Success {
|
||||||
// 天眼查API调用失败,返回企业信息校验不通过
|
return nil, fmt.Errorf("天眼查API调用失败")
|
||||||
return createStatusResponsess(1), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析天眼查响应数据
|
// 解析天眼查响应数据
|
||||||
if response.Data == nil {
|
if response.Data == nil {
|
||||||
// 天眼查响应数据为空,返回企业信息校验不通过
|
return nil, fmt.Errorf("天眼查响应数据为空")
|
||||||
return createStatusResponsess(1), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将response.Data转换为JSON字符串,然后使用gjson解析
|
// 将response.Data转换为JSON字符串,然后使用gjson解析
|
||||||
dataBytes, err := json.Marshal(response.Data)
|
dataBytes, err := json.Marshal(response.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 数据序列化失败,返回企业信息校验不通过
|
return nil, fmt.Errorf("数据序列化失败")
|
||||||
return createStatusResponsess(1), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用gjson解析嵌套的data.result.data字段
|
// 使用gjson解析嵌套的data.result.data字段
|
||||||
result := gjson.GetBytes(dataBytes, "result")
|
result := gjson.GetBytes(dataBytes, "result")
|
||||||
if !result.Exists() {
|
if !result.Exists() {
|
||||||
// 字段不存在,返回企业信息校验不通过
|
return nil, fmt.Errorf("result字段不存在")
|
||||||
return createStatusResponsess(1), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查data.result.data是否等于1
|
// 检查data.result.data是否等于1
|
||||||
if result.Int() != 1 {
|
if result.Int() != 1 {
|
||||||
// 不等于1,返回企业信息校验不通过
|
return nil, fmt.Errorf("企业信息验证不通过")
|
||||||
return createStatusResponsess(1), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 天眼查三要素验证通过,继续调用星维身份证三要素验证
|
// 构建天眼查API返回的数据结构
|
||||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
return map[string]interface{}{
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
"success": response.Success,
|
||||||
}
|
"message": response.Message,
|
||||||
|
"data": response.Data,
|
||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
}, nil
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// verifyPersonalInfo 验证个人信息并返回API数据
|
||||||
|
func verifyPersonalInfo(ctx context.Context, paramsDto dto.QYGL5CMPReq, deps *processors.ProcessorDependencies) (map[string]interface{}, error) {
|
||||||
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"name": paramsDto.LegalPerson,
|
"name": paramsDto.LegalPerson,
|
||||||
@@ -89,6 +105,7 @@ func ProcessQYGL5CMPRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
projectID := "CDJ-1100244702166183936"
|
projectID := "CDJ-1100244702166183936"
|
||||||
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// 个人信息验证失败,返回错误状态
|
||||||
if errors.Is(err, xingwei.ErrNotFound) {
|
if errors.Is(err, xingwei.ErrNotFound) {
|
||||||
return nil, errors.Join(processors.ErrNotFound, err)
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
} else if errors.Is(err, xingwei.ErrDatasource) {
|
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||||
@@ -106,42 +123,6 @@ func ProcessQYGL5CMPRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("解析星维API响应失败: %w", err))
|
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("解析星维API响应失败: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建天眼查API返回的数据结构
|
// 返回星维API的全部数据
|
||||||
tianYanChaData := map[string]interface{}{
|
return xingweiData, nil
|
||||||
"success": response.Success,
|
|
||||||
"message": response.Message,
|
|
||||||
"data": response.Data,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析status响应(将JSON字节解析为对象)
|
|
||||||
statusBytes := createStatusResponsess(0) // 验证通过,status为0
|
|
||||||
var statusData map[string]interface{}
|
|
||||||
if err := json.Unmarshal(statusBytes, &statusData); err != nil {
|
|
||||||
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("解析status响应失败: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合并两个API的返回数据
|
|
||||||
mergedData := map[string]interface{}{
|
|
||||||
"Personal Information": xingweiData, // 星维API返回的数据
|
|
||||||
"Enterprise Information": tianYanChaData, // 天眼查API返回的数据
|
|
||||||
"status": statusData, // 解析后的status对象
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将合并后的数据序列化为JSON
|
|
||||||
mergedBytes, err := json.Marshal(mergedData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("合并数据序列化失败: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergedBytes, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// createStatusResponsess 创建状态响应
|
|
||||||
func createStatusResponsess(status int) []byte {
|
|
||||||
response := map[string]interface{}{
|
|
||||||
"status": status,
|
|
||||||
}
|
|
||||||
respBytes, _ := json.Marshal(response)
|
|
||||||
return respBytes
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Processqygl66slRequest QYGL66SL API处理方法 - 全国企业司法模型服务查询_V1
|
||||||
|
func ProcessQYGL66SLRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
|
||||||
|
var paramsDto dto.QYGL66SLReq
|
||||||
|
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{}{
|
||||||
|
"orgName": paramsDto.EntName,
|
||||||
|
"inquiredAuth": "authed:" + paramsDto.AuthDate,
|
||||||
|
"uscc": paramsDto.EntCode,
|
||||||
|
"authAuthorizeFileCode": paramsDto.AuthAuthorizeFileCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用行为数据API,使用指定的project_id
|
||||||
|
projectID := "CDJ-1068350101956521984"
|
||||||
|
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, xingwei.ErrNotFound) {
|
||||||
|
// 查空情况,返回特定的查空错误
|
||||||
|
return nil, errors.Join(processors.ErrNotFound, err)
|
||||||
|
} else if errors.Is(err, xingwei.ErrDatasource) {
|
||||||
|
// 数据源错误
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||||
|
// 系统错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
} else {
|
||||||
|
// 其他未知错误
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQYGLNIO8Request QYGLNIO8 API处理方法 - 企业基本信息
|
||||||
|
func ProcessQYGLNIO8Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QYGLNIO8Req
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建API调用参数
|
||||||
|
apiParams := map[string]string{
|
||||||
|
"keyword": paramsDto.EntCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用天眼查API - 企业基本信息
|
||||||
|
response, err := deps.TianYanChaService.CallAPI(ctx, "baseinfo", apiParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, convertTianYanChaError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查天眼查API调用是否成功
|
||||||
|
if !response.Success {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, errors.New(response.Message))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回天眼查响应数据
|
||||||
|
respBytes, err := json.Marshal(response.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessQYGLP0HTRequest QYGLP0HT API处理方法 - 股权穿透
|
||||||
|
func ProcessQYGLP0HTRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
var paramsDto dto.QYGLP0HTReq
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
|
flag := paramsDto.Flag
|
||||||
|
if flag == "" {
|
||||||
|
flag = "4"
|
||||||
|
}
|
||||||
|
dir := paramsDto.Dir
|
||||||
|
if dir == "" {
|
||||||
|
dir = "down"
|
||||||
|
}
|
||||||
|
minPercent := paramsDto.MinPercent
|
||||||
|
if minPercent == "" {
|
||||||
|
minPercent = "0"
|
||||||
|
}
|
||||||
|
maxPercent := paramsDto.MaxPercent
|
||||||
|
if maxPercent == "" {
|
||||||
|
maxPercent = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建API调用参数
|
||||||
|
apiParams := map[string]string{
|
||||||
|
"keyword": paramsDto.EntCode,
|
||||||
|
"flag": flag,
|
||||||
|
"dir": dir,
|
||||||
|
"minPercent": minPercent,
|
||||||
|
"maxPercent": maxPercent,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用天眼查API - 企股权穿透
|
||||||
|
response, err := deps.TianYanChaService.CallAPI(ctx, "investtree", apiParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, convertTianYanChaError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查天眼查API调用是否成功
|
||||||
|
if !response.Success {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, errors.New(response.Message))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回天眼查响应数据
|
||||||
|
respBytes, err := json.Marshal(response.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
12
internal/domains/api/services/processors/qygl/utils.go
Normal file
12
internal/domains/api/services/processors/qygl/utils.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package qygl
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// createStatusResponse 创建状态响应
|
||||||
|
func createStatusResponse(status int) []byte {
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
}
|
||||||
|
respBytes, _ := json.Marshal(response)
|
||||||
|
return respBytes
|
||||||
|
}
|
||||||
@@ -4,12 +4,38 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"tyapi-server/internal/domains/api/dto"
|
"tyapi-server/internal/domains/api/dto"
|
||||||
"tyapi-server/internal/domains/api/services/processors"
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
"tyapi-server/internal/infrastructure/external/westdex"
|
"tyapi-server/internal/infrastructure/external/xingwei"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// XingweiResponseData 星维数据源返回的数据结构
|
||||||
|
type XingweiResponseData struct {
|
||||||
|
OrderNo string `json:"orderNo"`
|
||||||
|
HandleTime string `json:"handleTime"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Result string `json:"result"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Age string `json:"age"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// YYSY09CDResponse 原来的返回结构
|
||||||
|
type YYSY09CDResponse struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Data YYSY09CDResponseData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// YYSY09CDResponseData 原来的返回数据结构
|
||||||
|
type YYSY09CDResponseData struct {
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
PhoneType string `json:"phoneType"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
EncryptType string `json:"encryptType"`
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessYYSY09CDRequest YYSY09CD API处理方法
|
// ProcessYYSY09CDRequest YYSY09CD API处理方法
|
||||||
func ProcessYYSY09CDRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
func ProcessYYSY09CDRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
var paramsDto dto.YYSY09CDReq
|
var paramsDto dto.YYSY09CDReq
|
||||||
@@ -21,38 +47,98 @@ func ProcessYYSY09CDRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"data": map[string]interface{}{
|
"name": paramsDto.Name,
|
||||||
"name": encryptedName,
|
"idCardNum": paramsDto.IDCard,
|
||||||
"idNo": encryptedIDCard,
|
"phoneNumber": paramsDto.MobileNo,
|
||||||
"phone": encryptedMobileNo,
|
|
||||||
"phoneType": paramsDto.MobileType,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes, err := deps.WestDexService.CallAPI(ctx, "G16BJ02", reqData)
|
// 调用行为数据API,使用指定的project_id
|
||||||
|
projectID := "CDJ-1100244697766359040"
|
||||||
|
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, westdex.ErrDatasource) {
|
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)
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else if errors.Is(err, xingwei.ErrSystem) {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return respBytes, nil
|
// 解析星维返回的数据
|
||||||
|
var xingweiData XingweiResponseData
|
||||||
|
if err := json.Unmarshal(respBytes, &xingweiData); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为原来的格式
|
||||||
|
response := convertToOriginalFormat(xingweiData, paramsDto.MobileType)
|
||||||
|
|
||||||
|
// 序列化为JSON
|
||||||
|
resultBytes, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToOriginalFormat 将星维数据源返回的数据转换为原来的格式
|
||||||
|
func convertToOriginalFormat(xingweiData XingweiResponseData, mobileType string) YYSY09CDResponse {
|
||||||
|
// 转换 result 到 code
|
||||||
|
var code string
|
||||||
|
var codeInt int
|
||||||
|
switch xingweiData.Result {
|
||||||
|
case "01": // 一致
|
||||||
|
code = "1000"
|
||||||
|
codeInt = 1000
|
||||||
|
case "02": // 不一致
|
||||||
|
code = "1001"
|
||||||
|
codeInt = 1001
|
||||||
|
case "03", "04": // 不确定或失败/虚拟号 -> 查无
|
||||||
|
code = "1002"
|
||||||
|
codeInt = 1002
|
||||||
|
default:
|
||||||
|
// 默认查无
|
||||||
|
code = "1002"
|
||||||
|
codeInt = 1002
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 remark 提取 msg,去掉"认证"前缀
|
||||||
|
msg := xingweiData.Remark
|
||||||
|
if strings.HasPrefix(msg, "认证") {
|
||||||
|
msg = strings.TrimPrefix(msg, "认证")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换 type 到 phoneType
|
||||||
|
// 如果请求参数中有 mobileType,优先使用;否则从返回的 type 转换
|
||||||
|
phoneType := mobileType
|
||||||
|
if phoneType == "" {
|
||||||
|
switch xingweiData.Type {
|
||||||
|
case "1": // 移动
|
||||||
|
phoneType = "CMCC"
|
||||||
|
case "2": // 联通
|
||||||
|
phoneType = "CUCC"
|
||||||
|
case "3": // 电信
|
||||||
|
phoneType = "CTCC"
|
||||||
|
case "4": // 广电
|
||||||
|
phoneType = "CBN"
|
||||||
|
default:
|
||||||
|
phoneType = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return YYSY09CDResponse{
|
||||||
|
Code: code,
|
||||||
|
Data: YYSY09CDResponseData{
|
||||||
|
Msg: msg,
|
||||||
|
PhoneType: phoneType,
|
||||||
|
Code: codeInt,
|
||||||
|
EncryptType: "MD5",
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -42,4 +42,4 @@ func ProcessYYSY4B21Request(ctx context.Context, params []byte, deps *processors
|
|||||||
}
|
}
|
||||||
|
|
||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func ProcessYYSY6D9ARequest(ctx context.Context, params []byte, deps *processors
|
|||||||
}
|
}
|
||||||
|
|
||||||
reqData := map[string]interface{}{
|
reqData := map[string]interface{}{
|
||||||
"phone": encryptedMobileNo,
|
"phone": encryptedMobileNo,
|
||||||
}
|
}
|
||||||
|
|
||||||
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI030", reqData)
|
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI030", reqData)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func (s *EnterpriseInfoSubmitRecordService) Save(ctx context.Context, enterprise
|
|||||||
return s.repositories.Create(ctx, enterpriseInfoSubmitRecord)
|
return s.repositories.Create(ctx, enterpriseInfoSubmitRecord)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateWithWestdex 调用QYGL23T7处理器验证企业信息
|
// ValidateWithWestdex 调用QYGL5CMP处理器验证企业信息
|
||||||
func (s *EnterpriseInfoSubmitRecordService) ValidateWithWestdex(ctx context.Context, info *value_objects.EnterpriseInfo) error {
|
func (s *EnterpriseInfoSubmitRecordService) ValidateWithWestdex(ctx context.Context, info *value_objects.EnterpriseInfo) error {
|
||||||
if info == nil {
|
if info == nil {
|
||||||
return errors.New("企业信息不能为空")
|
return errors.New("企业信息不能为空")
|
||||||
@@ -89,12 +89,13 @@ func (s *EnterpriseInfoSubmitRecordService) ValidateWithWestdex(ctx context.Cont
|
|||||||
// return nil
|
// return nil
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// 构建QYGL23T7请求参数
|
// 构建QYGL5CMP请求参数
|
||||||
reqDto := dto.QYGL23T7Req{
|
reqDto := dto.QYGL5CMPReq{
|
||||||
EntName: info.CompanyName,
|
EntName: info.CompanyName,
|
||||||
LegalPerson: info.LegalPersonName,
|
LegalPerson: info.LegalPersonName,
|
||||||
EntCode: info.UnifiedSocialCode,
|
EntCode: info.UnifiedSocialCode,
|
||||||
IDCard: info.LegalPersonID,
|
IDCard: info.LegalPersonID,
|
||||||
|
MobileNo: info.LegalPersonPhone,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 序列化请求参数
|
// 序列化请求参数
|
||||||
|
|||||||
180
internal/domains/finance/entities/purchase_order.go
Normal file
180
internal/domains/finance/entities/purchase_order.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PurchaseOrderStatus 购买订单状态枚举(通用)
|
||||||
|
type PurchaseOrderStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PurchaseOrderStatusCreated PurchaseOrderStatus = "created" // 已创建
|
||||||
|
PurchaseOrderStatusPaid PurchaseOrderStatus = "paid" // 已支付
|
||||||
|
PurchaseOrderStatusFailed PurchaseOrderStatus = "failed" // 支付失败
|
||||||
|
PurchaseOrderStatusCancelled PurchaseOrderStatus = "cancelled" // 已取消
|
||||||
|
PurchaseOrderStatusRefunded PurchaseOrderStatus = "refunded" // 已退款
|
||||||
|
PurchaseOrderStatusClosed PurchaseOrderStatus = "closed" // 已关闭
|
||||||
|
)
|
||||||
|
|
||||||
|
// PurchaseOrder 购买订单实体(统一表 ty_purchase_orders,兼容多支付渠道)
|
||||||
|
type PurchaseOrder struct {
|
||||||
|
// 基础标识
|
||||||
|
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"购买订单唯一标识"`
|
||||||
|
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"购买用户ID"`
|
||||||
|
OrderNo string `gorm:"type:varchar(64);not null;uniqueIndex" json:"order_no" comment:"商户订单号"`
|
||||||
|
TradeNo *string `gorm:"type:varchar(64);uniqueIndex" json:"trade_no,omitempty" comment:"第三方支付交易号"`
|
||||||
|
|
||||||
|
// 产品信息
|
||||||
|
ProductID string `gorm:"type:varchar(36);not null;index" json:"product_id" comment:"产品ID"`
|
||||||
|
ProductCode string `gorm:"type:varchar(50);not null" json:"product_code" comment:"产品编号"`
|
||||||
|
ProductName string `gorm:"type:varchar(200);not null" json:"product_name" comment:"产品名称"`
|
||||||
|
Category string `gorm:"type:varchar(50)" json:"category,omitempty" comment:"产品分类"`
|
||||||
|
|
||||||
|
// 订单信息
|
||||||
|
Subject string `gorm:"type:varchar(200);not null" json:"subject" comment:"订单标题"`
|
||||||
|
Amount decimal.Decimal `gorm:"type:decimal(20,8);not null" json:"amount" comment:"订单金额"`
|
||||||
|
PayAmount *decimal.Decimal `gorm:"type:decimal(20,8)" json:"pay_amount,omitempty" comment:"实际支付金额"`
|
||||||
|
Status PurchaseOrderStatus `gorm:"type:varchar(20);not null;default:'created';index" json:"status" comment:"订单状态"`
|
||||||
|
Platform string `gorm:"type:varchar(20);not null" json:"platform" comment:"下单平台:app/h5/pc/wx_h5/wx_mini等"`
|
||||||
|
PayChannel string `gorm:"type:varchar(20);default:'alipay';index" json:"pay_channel" comment:"支付渠道:alipay/wechat"`
|
||||||
|
PaymentType string `gorm:"type:varchar(20);not null" json:"payment_type" comment:"支付类型:alipay, wechat, free"`
|
||||||
|
|
||||||
|
// 支付渠道返回信息
|
||||||
|
BuyerID string `gorm:"type:varchar(64)" json:"buyer_id,omitempty" comment:"买家ID(支付渠道方)"`
|
||||||
|
SellerID string `gorm:"type:varchar(64)" json:"seller_id,omitempty" comment:"卖家ID(支付渠道方)"`
|
||||||
|
ReceiptAmount decimal.Decimal `gorm:"type:decimal(20,8)" json:"receipt_amount,omitempty" comment:"实收金额"`
|
||||||
|
|
||||||
|
// 回调信息
|
||||||
|
NotifyTime *time.Time `gorm:"index" json:"notify_time,omitempty" comment:"异步通知时间"`
|
||||||
|
ReturnTime *time.Time `gorm:"index" json:"return_time,omitempty" comment:"同步返回时间"`
|
||||||
|
PayTime *time.Time `gorm:"index" json:"pay_time,omitempty" comment:"支付完成时间"`
|
||||||
|
|
||||||
|
// 文件信息
|
||||||
|
FilePath *string `gorm:"type:varchar(500)" json:"file_path,omitempty" comment:"产品文件路径"`
|
||||||
|
FileSize *int64 `gorm:"type:bigint" json:"file_size,omitempty" comment:"文件大小(字节)"`
|
||||||
|
|
||||||
|
// 备注信息
|
||||||
|
Remark string `gorm:"type:varchar(500)" json:"remark,omitempty" comment:"备注信息"`
|
||||||
|
|
||||||
|
// 错误信息
|
||||||
|
ErrorCode string `gorm:"type:varchar(64)" json:"error_code,omitempty" comment:"错误码"`
|
||||||
|
ErrorMessage string `gorm:"type:text" json:"error_message,omitempty" comment:"错误信息"`
|
||||||
|
|
||||||
|
// 时间戳字段
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定数据库表名
|
||||||
|
func (PurchaseOrder) TableName() string {
|
||||||
|
return "ty_purchase_orders"
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeCreate GORM钩子:创建前自动生成UUID和订单号
|
||||||
|
func (p *PurchaseOrder) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if p.ID == "" {
|
||||||
|
p.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
if p.OrderNo == "" {
|
||||||
|
p.OrderNo = generatePurchaseOrderNo()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generatePurchaseOrderNo 生成购买订单号
|
||||||
|
func generatePurchaseOrderNo() string {
|
||||||
|
// 使用时间戳+随机数生成唯一订单号,例如:PO202312200001
|
||||||
|
timestamp := time.Now().Format("20060102")
|
||||||
|
random := fmt.Sprintf("%04d", rand.Intn(9999))
|
||||||
|
return fmt.Sprintf("PO%s%s", timestamp, random)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCreated 检查是否为已创建状态
|
||||||
|
func (p *PurchaseOrder) IsCreated() bool {
|
||||||
|
return p.Status == PurchaseOrderStatusCreated
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPaid 检查是否为已支付状态
|
||||||
|
func (p *PurchaseOrder) IsPaid() bool {
|
||||||
|
return p.Status == PurchaseOrderStatusPaid
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFailed 检查是否为支付失败状态
|
||||||
|
func (p *PurchaseOrder) IsFailed() bool {
|
||||||
|
return p.Status == PurchaseOrderStatusFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCancelled 检查是否为已取消状态
|
||||||
|
func (p *PurchaseOrder) IsCancelled() bool {
|
||||||
|
return p.Status == PurchaseOrderStatusCancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRefunded 检查是否为已退款状态
|
||||||
|
func (p *PurchaseOrder) IsRefunded() bool {
|
||||||
|
return p.Status == PurchaseOrderStatusRefunded
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClosed 检查是否为已关闭状态
|
||||||
|
func (p *PurchaseOrder) IsClosed() bool {
|
||||||
|
return p.Status == PurchaseOrderStatusClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkPaid 标记为已支付
|
||||||
|
func (p *PurchaseOrder) MarkPaid(tradeNo, buyerID, sellerID string, payAmount, receiptAmount decimal.Decimal) {
|
||||||
|
p.Status = PurchaseOrderStatusPaid
|
||||||
|
p.TradeNo = &tradeNo
|
||||||
|
p.BuyerID = buyerID
|
||||||
|
p.SellerID = sellerID
|
||||||
|
p.PayAmount = &payAmount
|
||||||
|
p.ReceiptAmount = receiptAmount
|
||||||
|
now := time.Now()
|
||||||
|
p.PayTime = &now
|
||||||
|
p.NotifyTime = &now
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFailed 标记为支付失败
|
||||||
|
func (p *PurchaseOrder) MarkFailed(errorCode, errorMessage string) {
|
||||||
|
p.Status = PurchaseOrderStatusFailed
|
||||||
|
p.ErrorCode = errorCode
|
||||||
|
p.ErrorMessage = errorMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkCancelled 标记为已取消
|
||||||
|
func (p *PurchaseOrder) MarkCancelled() {
|
||||||
|
p.Status = PurchaseOrderStatusCancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkRefunded 标记为已退款
|
||||||
|
func (p *PurchaseOrder) MarkRefunded() {
|
||||||
|
p.Status = PurchaseOrderStatusRefunded
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkClosed 标记为已关闭
|
||||||
|
func (p *PurchaseOrder) MarkClosed() {
|
||||||
|
p.Status = PurchaseOrderStatusClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPurchaseOrder 通用工厂方法 - 创建购买订单(支持多支付渠道)
|
||||||
|
func NewPurchaseOrder(userID, productID, productCode, productName, subject string, amount decimal.Decimal, platform, payChannel, paymentType string) *PurchaseOrder {
|
||||||
|
return &PurchaseOrder{
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
UserID: userID,
|
||||||
|
OrderNo: generatePurchaseOrderNo(),
|
||||||
|
ProductID: productID,
|
||||||
|
ProductCode: productCode,
|
||||||
|
ProductName: productName,
|
||||||
|
Subject: subject,
|
||||||
|
Amount: amount,
|
||||||
|
Status: PurchaseOrderStatusCreated,
|
||||||
|
Platform: platform,
|
||||||
|
PayChannel: payChannel,
|
||||||
|
PaymentType: paymentType,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
finance_entities "tyapi-server/internal/domains/finance/entities"
|
||||||
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PurchaseOrderRepository 购买订单仓储接口
|
||||||
|
type PurchaseOrderRepository interface {
|
||||||
|
// 创建订单
|
||||||
|
Create(ctx context.Context, order *finance_entities.PurchaseOrder) (*finance_entities.PurchaseOrder, error)
|
||||||
|
|
||||||
|
// 更新订单
|
||||||
|
Update(ctx context.Context, order *finance_entities.PurchaseOrder) error
|
||||||
|
|
||||||
|
// 根据ID获取订单
|
||||||
|
GetByID(ctx context.Context, id string) (*finance_entities.PurchaseOrder, error)
|
||||||
|
|
||||||
|
// 根据订单号获取订单
|
||||||
|
GetByOrderNo(ctx context.Context, orderNo string) (*finance_entities.PurchaseOrder, error)
|
||||||
|
|
||||||
|
// 根据用户ID获取订单列表
|
||||||
|
GetByUserID(ctx context.Context, userID string, limit, offset int) ([]*finance_entities.PurchaseOrder, int64, error)
|
||||||
|
|
||||||
|
// 根据产品ID和用户ID获取订单
|
||||||
|
GetByUserIDAndProductID(ctx context.Context, userID, productID string) (*finance_entities.PurchaseOrder, error)
|
||||||
|
|
||||||
|
// 根据支付类型和第三方交易号获取订单
|
||||||
|
GetByPaymentTypeAndTransactionID(ctx context.Context, paymentType, transactionID string) (*finance_entities.PurchaseOrder, error)
|
||||||
|
|
||||||
|
// 根据交易号获取订单
|
||||||
|
GetByTradeNo(ctx context.Context, tradeNo string) (*finance_entities.PurchaseOrder, error)
|
||||||
|
|
||||||
|
// 更新支付状态
|
||||||
|
UpdatePaymentStatus(ctx context.Context, orderID string, status finance_entities.PurchaseOrderStatus, tradeNo *string, payAmount, receiptAmount *string, paymentTime *time.Time) error
|
||||||
|
|
||||||
|
// 获取用户已购买的产品编号列表
|
||||||
|
GetUserPurchasedProductCodes(ctx context.Context, userID string) ([]string, error)
|
||||||
|
|
||||||
|
// 检查用户是否已购买指定产品
|
||||||
|
HasUserPurchased(ctx context.Context, userID string, productCode string) (bool, error)
|
||||||
|
|
||||||
|
// 获取即将过期的订单(用于清理)
|
||||||
|
GetExpiringOrders(ctx context.Context, before time.Time, limit int) ([]*finance_entities.PurchaseOrder, error)
|
||||||
|
|
||||||
|
// 获取已过期订单(用于清理)
|
||||||
|
GetExpiredOrders(ctx context.Context, limit int) ([]*finance_entities.PurchaseOrder, error)
|
||||||
|
|
||||||
|
// 获取用户已支付的产品ID列表
|
||||||
|
GetUserPaidProductIDs(ctx context.Context, userID string) ([]string, error)
|
||||||
|
|
||||||
|
// 根据状态获取订单列表
|
||||||
|
GetByStatus(ctx context.Context, status finance_entities.PurchaseOrderStatus, limit, offset int) ([]*finance_entities.PurchaseOrder, int64, error)
|
||||||
|
|
||||||
|
// 根据筛选条件获取订单列表
|
||||||
|
GetByFilters(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*finance_entities.PurchaseOrder, error)
|
||||||
|
|
||||||
|
// 根据筛选条件统计订单数量
|
||||||
|
CountByFilters(ctx context.Context, filters map[string]interface{}) (int64, error)
|
||||||
|
}
|
||||||
@@ -4,29 +4,30 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ComponentReportDownload 组件报告下载记录
|
// ComponentReportDownload 组件报告下载记录
|
||||||
type ComponentReportDownload struct {
|
type ComponentReportDownload struct {
|
||||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"下载记录ID"`
|
ID string `gorm:"primaryKey;type:varchar(36)" comment:"下载记录ID"`
|
||||||
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
|
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
|
||||||
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
||||||
ProductCode string `gorm:"type:varchar(50);not null;index" comment:"产品编号"`
|
ProductCode string `gorm:"type:varchar(50);not null;index" comment:"产品编号"`
|
||||||
SubProductIDs string `gorm:"type:text" comment:"子产品ID列表(JSON数组,组合包使用)"`
|
ProductName string `gorm:"type:varchar(200);not null" comment:"产品名称"`
|
||||||
SubProductCodes string `gorm:"type:text" comment:"子产品编号列表(JSON数组)"`
|
// 直接关联购买订单
|
||||||
DownloadPrice decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"实际支付价格"`
|
OrderID *string `gorm:"type:varchar(36);index" comment:"关联的购买订单ID"`
|
||||||
OriginalPrice decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"原始总价"`
|
OrderNumber *string `gorm:"type:varchar(64);index" comment:"关联的购买订单号"`
|
||||||
DiscountAmount decimal.Decimal `gorm:"type:decimal(10,2);default:0" comment:"减免金额"`
|
|
||||||
PaymentOrderID *string `gorm:"type:varchar(64);index" comment:"支付订单号(关联充值记录)"`
|
// 组合包相关字段(从购买记录复制)
|
||||||
PaymentType *string `gorm:"type:varchar(20)" comment:"支付类型:alipay, wechat"`
|
SubProductIDs string `gorm:"type:text" comment:"子产品ID列表(JSON数组,组合包使用)"`
|
||||||
PaymentStatus string `gorm:"type:varchar(20);default:'pending';index" comment:"支付状态:pending, success, failed"`
|
SubProductCodes string `gorm:"type:text" comment:"子产品编号列表(JSON数组)"`
|
||||||
FilePath *string `gorm:"type:varchar(500)" comment:"生成的ZIP文件路径(用于二次下载)"`
|
|
||||||
FileHash *string `gorm:"type:varchar(64)" comment:"文件哈希值(用于缓存验证)"`
|
// 下载相关信息
|
||||||
DownloadCount int `gorm:"default:0" comment:"下载次数"`
|
FilePath *string `gorm:"type:varchar(500)" comment:"生成的ZIP文件路径(用于二次下载)"`
|
||||||
LastDownloadAt *time.Time `comment:"最后下载时间"`
|
FileHash *string `gorm:"type:varchar(64)" comment:"文件哈希值(用于缓存验证)"`
|
||||||
ExpiresAt *time.Time `gorm:"index" comment:"下载有效期(支付成功后30天)"`
|
DownloadCount int `gorm:"default:0" comment:"下载次数"`
|
||||||
|
LastDownloadAt *time.Time `comment:"最后下载时间"`
|
||||||
|
ExpiresAt *time.Time `gorm:"index" comment:"下载有效期(从创建日起30天)"`
|
||||||
|
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||||
@@ -46,11 +47,6 @@ func (c *ComponentReportDownload) BeforeCreate(tx *gorm.DB) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPaid 检查是否已支付
|
|
||||||
func (c *ComponentReportDownload) IsPaid() bool {
|
|
||||||
return c.PaymentStatus == "success"
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsExpired 检查是否已过期
|
// IsExpired 检查是否已过期
|
||||||
func (c *ComponentReportDownload) IsExpired() bool {
|
func (c *ComponentReportDownload) IsExpired() bool {
|
||||||
if c.ExpiresAt == nil {
|
if c.ExpiresAt == nil {
|
||||||
@@ -61,5 +57,6 @@ func (c *ComponentReportDownload) IsExpired() bool {
|
|||||||
|
|
||||||
// CanDownload 检查是否可以下载
|
// CanDownload 检查是否可以下载
|
||||||
func (c *ComponentReportDownload) CanDownload() bool {
|
func (c *ComponentReportDownload) CanDownload() bool {
|
||||||
return c.IsPaid() && !c.IsExpired()
|
// 下载记录存在即表示用户有下载权限,只需检查是否过期
|
||||||
|
return !c.IsExpired()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ import (
|
|||||||
|
|
||||||
// Subscription 订阅实体
|
// Subscription 订阅实体
|
||||||
type Subscription struct {
|
type Subscription struct {
|
||||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"订阅ID"`
|
ID string `gorm:"primaryKey;type:varchar(36)" comment:"订阅ID"`
|
||||||
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
|
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
|
||||||
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
||||||
Price decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"订阅价格"`
|
Price decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"订阅价格"`
|
||||||
APIUsed int64 `gorm:"default:0" comment:"已使用API调用次数"`
|
UIComponentPrice decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"UI组件价格(组合包使用)"`
|
||||||
Version int64 `gorm:"default:1" comment:"乐观锁版本号"`
|
APIUsed int64 `gorm:"default:0" comment:"已使用API调用次数"`
|
||||||
|
Version int64 `gorm:"default:1" comment:"乐观锁版本号"`
|
||||||
|
|
||||||
// 关联关系
|
// 关联关系
|
||||||
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
|
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
|
||||||
|
|||||||
@@ -9,22 +9,23 @@ import (
|
|||||||
|
|
||||||
// UIComponent UI组件实体
|
// UIComponent UI组件实体
|
||||||
type UIComponent struct {
|
type UIComponent struct {
|
||||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"组件ID"`
|
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"组件ID"`
|
||||||
ComponentCode string `gorm:"type:varchar(50);not null;uniqueIndex" json:"component_code" comment:"组件编码"`
|
ComponentCode string `gorm:"type:varchar(50);not null;uniqueIndex" json:"component_code" comment:"组件编码"`
|
||||||
ComponentName string `gorm:"type:varchar(100);not null" json:"component_name" comment:"组件名称"`
|
ComponentName string `gorm:"type:varchar(100);not null" json:"component_name" comment:"组件名称"`
|
||||||
Description string `gorm:"type:text" json:"description" comment:"组件描述"`
|
Description string `gorm:"type:text" json:"description" comment:"组件描述"`
|
||||||
FilePath *string `gorm:"type:varchar(500)" json:"file_path" comment:"组件文件路径"`
|
FilePath *string `gorm:"type:varchar(500)" json:"file_path" comment:"组件文件路径"`
|
||||||
FileHash *string `gorm:"type:varchar(64)" json:"file_hash" comment:"文件哈希值"`
|
FileHash *string `gorm:"type:varchar(64)" json:"file_hash" comment:"文件哈希值"`
|
||||||
FileSize *int64 `gorm:"type:bigint" json:"file_size" comment:"文件大小"`
|
FileSize *int64 `gorm:"type:bigint" json:"file_size" comment:"文件大小"`
|
||||||
FileType *string `gorm:"type:varchar(50)" json:"file_type" comment:"文件类型"`
|
FileType *string `gorm:"type:varchar(50)" json:"file_type" comment:"文件类型"`
|
||||||
FolderPath *string `gorm:"type:varchar(500)" json:"folder_path" comment:"组件文件夹路径"`
|
FolderPath *string `gorm:"type:varchar(500)" json:"folder_path" comment:"组件文件夹路径"`
|
||||||
IsExtracted bool `gorm:"default:false" json:"is_extracted" comment:"是否已解压"`
|
IsExtracted bool `gorm:"default:false" json:"is_extracted" comment:"是否已解压"`
|
||||||
Version string `gorm:"type:varchar(20)" json:"version" comment:"组件版本"`
|
FileUploadTime *time.Time `gorm:"type:timestamp" json:"file_upload_time" comment:"文件上传时间"`
|
||||||
IsActive bool `gorm:"default:true" json:"is_active" comment:"是否启用"`
|
Version string `gorm:"type:varchar(20)" json:"version" comment:"组件版本"`
|
||||||
SortOrder int `gorm:"default:0" json:"sort_order" comment:"排序"`
|
IsActive bool `gorm:"default:true" json:"is_active" comment:"是否启用"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
SortOrder int `gorm:"default:0" json:"sort_order" comment:"排序"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at" comment:"软删除时间"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at" comment:"软删除时间"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UIComponent) TableName() string {
|
func (UIComponent) TableName() string {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
// ComponentReportRepository 组件报告仓储接口
|
// ComponentReportRepository 组件报告仓储接口
|
||||||
type ComponentReportRepository interface {
|
type ComponentReportRepository interface {
|
||||||
// 创建下载记录
|
// 创建下载记录
|
||||||
CreateDownload(ctx context.Context, download *entities.ComponentReportDownload) (*entities.ComponentReportDownload, error)
|
Create(ctx context.Context, download *entities.ComponentReportDownload) error
|
||||||
|
|
||||||
// 更新下载记录
|
// 更新下载记录
|
||||||
UpdateDownload(ctx context.Context, download *entities.ComponentReportDownload) error
|
UpdateDownload(ctx context.Context, download *entities.ComponentReportDownload) error
|
||||||
@@ -20,6 +20,15 @@ type ComponentReportRepository interface {
|
|||||||
// 获取用户的下载记录列表
|
// 获取用户的下载记录列表
|
||||||
GetUserDownloads(ctx context.Context, userID string, productID *string) ([]*entities.ComponentReportDownload, error)
|
GetUserDownloads(ctx context.Context, userID string, productID *string) ([]*entities.ComponentReportDownload, error)
|
||||||
|
|
||||||
|
// 获取用户有效的下载记录
|
||||||
|
GetActiveDownload(ctx context.Context, userID, productID string) (*entities.ComponentReportDownload, error)
|
||||||
|
|
||||||
|
// 更新下载记录文件路径
|
||||||
|
UpdateFilePath(ctx context.Context, downloadID, filePath string) error
|
||||||
|
|
||||||
|
// 增加下载次数
|
||||||
|
IncrementDownloadCount(ctx context.Context, downloadID string) error
|
||||||
|
|
||||||
// 检查用户是否已下载过指定产品编号的组件
|
// 检查用户是否已下载过指定产品编号的组件
|
||||||
HasUserDownloaded(ctx context.Context, userID string, productCode string) (bool, error)
|
HasUserDownloaded(ctx context.Context, userID string, productCode string) (bool, error)
|
||||||
|
|
||||||
|
|||||||
@@ -104,9 +104,10 @@ func (s *ProductSubscriptionService) CreateSubscription(ctx context.Context, use
|
|||||||
|
|
||||||
// 创建订阅
|
// 创建订阅
|
||||||
subscription := &entities.Subscription{
|
subscription := &entities.Subscription{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ProductID: productID,
|
ProductID: productID,
|
||||||
Price: product.Price,
|
Price: product.Price,
|
||||||
|
UIComponentPrice: product.UIComponentPrice,
|
||||||
}
|
}
|
||||||
|
|
||||||
createdSubscription, err := s.subscriptionRepo.Create(ctx, *subscription)
|
createdSubscription, err := s.subscriptionRepo.Create(ctx, *subscription)
|
||||||
@@ -253,7 +254,7 @@ func (s *ProductSubscriptionService) IncrementSubscriptionAPIUsage(ctx context.C
|
|||||||
// GetSubscriptionStats 获取订阅统计信息
|
// GetSubscriptionStats 获取订阅统计信息
|
||||||
func (s *ProductSubscriptionService) GetSubscriptionStats(ctx context.Context) (map[string]interface{}, error) {
|
func (s *ProductSubscriptionService) GetSubscriptionStats(ctx context.Context) (map[string]interface{}, error) {
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]interface{})
|
||||||
|
|
||||||
// 获取总订阅数
|
// 获取总订阅数
|
||||||
totalSubscriptions, err := s.subscriptionRepo.Count(ctx, interfaces.CountOptions{})
|
totalSubscriptions, err := s.subscriptionRepo.Count(ctx, interfaces.CountOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -261,7 +262,7 @@ func (s *ProductSubscriptionService) GetSubscriptionStats(ctx context.Context) (
|
|||||||
return nil, fmt.Errorf("获取订阅总数失败: %w", err)
|
return nil, fmt.Errorf("获取订阅总数失败: %w", err)
|
||||||
}
|
}
|
||||||
stats["total_subscriptions"] = totalSubscriptions
|
stats["total_subscriptions"] = totalSubscriptions
|
||||||
|
|
||||||
// 获取总收入
|
// 获取总收入
|
||||||
totalRevenue, err := s.subscriptionRepo.GetTotalRevenue(ctx)
|
totalRevenue, err := s.subscriptionRepo.GetTotalRevenue(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -269,30 +270,30 @@ func (s *ProductSubscriptionService) GetSubscriptionStats(ctx context.Context) (
|
|||||||
return nil, fmt.Errorf("获取总收入失败: %w", err)
|
return nil, fmt.Errorf("获取总收入失败: %w", err)
|
||||||
}
|
}
|
||||||
stats["total_revenue"] = totalRevenue
|
stats["total_revenue"] = totalRevenue
|
||||||
|
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserSubscriptionStats 获取用户订阅统计信息
|
// GetUserSubscriptionStats 获取用户订阅统计信息
|
||||||
func (s *ProductSubscriptionService) GetUserSubscriptionStats(ctx context.Context, userID string) (map[string]interface{}, error) {
|
func (s *ProductSubscriptionService) GetUserSubscriptionStats(ctx context.Context, userID string) (map[string]interface{}, error) {
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]interface{})
|
||||||
|
|
||||||
// 获取用户订阅数
|
// 获取用户订阅数
|
||||||
userSubscriptions, err := s.subscriptionRepo.FindByUserID(ctx, userID)
|
userSubscriptions, err := s.subscriptionRepo.FindByUserID(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("获取用户订阅失败", zap.Error(err))
|
s.logger.Error("获取用户订阅失败", zap.Error(err))
|
||||||
return nil, fmt.Errorf("获取用户订阅失败: %w", err)
|
return nil, fmt.Errorf("获取用户订阅失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算用户总收入
|
// 计算用户总收入
|
||||||
var totalRevenue float64
|
var totalRevenue float64
|
||||||
for _, subscription := range userSubscriptions {
|
for _, subscription := range userSubscriptions {
|
||||||
totalRevenue += subscription.Price.InexactFloat64()
|
totalRevenue += subscription.Price.InexactFloat64()
|
||||||
}
|
}
|
||||||
|
|
||||||
stats["total_subscriptions"] = int64(len(userSubscriptions))
|
stats["total_subscriptions"] = int64(len(userSubscriptions))
|
||||||
stats["total_revenue"] = totalRevenue
|
stats["total_revenue"] = totalRevenue
|
||||||
|
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,20 +304,47 @@ func (s *ProductSubscriptionService) UpdateSubscriptionPrice(ctx context.Context
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("订阅不存在: %w", err)
|
return fmt.Errorf("订阅不存在: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新价格
|
// 更新价格
|
||||||
subscription.Price = decimal.NewFromFloat(newPrice)
|
subscription.Price = decimal.NewFromFloat(newPrice)
|
||||||
subscription.Version++ // 增加版本号
|
subscription.Version++ // 增加版本号
|
||||||
|
|
||||||
// 保存更新
|
// 保存更新
|
||||||
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||||
s.logger.Error("更新订阅价格失败", zap.Error(err))
|
s.logger.Error("更新订阅价格失败", zap.Error(err))
|
||||||
return fmt.Errorf("更新订阅价格失败: %w", err)
|
return fmt.Errorf("更新订阅价格失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("订阅价格更新成功",
|
s.logger.Info("订阅价格更新成功",
|
||||||
zap.String("subscription_id", subscriptionID),
|
zap.String("subscription_id", subscriptionID),
|
||||||
zap.Float64("new_price", newPrice))
|
zap.Float64("new_price", newPrice))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubscriptionPriceWithUIComponent 更新订阅价格和UI组件价格
|
||||||
|
func (s *ProductSubscriptionService) UpdateSubscriptionPriceWithUIComponent(ctx context.Context, subscriptionID string, newPrice float64, newUIComponentPrice float64) error {
|
||||||
|
// 获取订阅
|
||||||
|
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("订阅不存在: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新价格
|
||||||
|
subscription.Price = decimal.NewFromFloat(newPrice)
|
||||||
|
subscription.UIComponentPrice = decimal.NewFromFloat(newUIComponentPrice)
|
||||||
|
subscription.Version++ // 增加版本号
|
||||||
|
|
||||||
|
// 保存更新
|
||||||
|
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||||
|
s.logger.Error("更新订阅价格失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("更新订阅价格失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("订阅价格更新成功",
|
||||||
|
zap.String("subscription_id", subscriptionID),
|
||||||
|
zap.Float64("new_price", newPrice),
|
||||||
|
zap.Float64("new_ui_component_price", newUIComponentPrice))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func (s *UserAggregateServiceImpl) CreateUser(ctx context.Context, phone, passwo
|
|||||||
func (s *UserAggregateServiceImpl) LoadUser(ctx context.Context, userID string) (*entities.User, error) {
|
func (s *UserAggregateServiceImpl) LoadUser(ctx context.Context, userID string) (*entities.User, error) {
|
||||||
s.logger.Debug("加载用户聚合根", zap.String("user_id", userID))
|
s.logger.Debug("加载用户聚合根", zap.String("user_id", userID))
|
||||||
|
|
||||||
user, err := s.userRepo.GetByID(ctx, userID)
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,352 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/finance/entities"
|
||||||
|
"tyapi-server/internal/domains/finance/repositories"
|
||||||
|
"tyapi-server/internal/shared/database"
|
||||||
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PurchaseOrdersTable = "ty_purchase_orders"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GormPurchaseOrderRepository struct {
|
||||||
|
*database.CachedBaseRepositoryImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ repositories.PurchaseOrderRepository = (*GormPurchaseOrderRepository)(nil)
|
||||||
|
|
||||||
|
func NewGormPurchaseOrderRepository(db *gorm.DB, logger *zap.Logger) repositories.PurchaseOrderRepository {
|
||||||
|
return &GormPurchaseOrderRepository{
|
||||||
|
CachedBaseRepositoryImpl: database.NewCachedBaseRepositoryImpl(db, logger, PurchaseOrdersTable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) Create(ctx context.Context, order *entities.PurchaseOrder) (*entities.PurchaseOrder, error) {
|
||||||
|
err := r.CreateEntity(ctx, order)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) Update(ctx context.Context, order *entities.PurchaseOrder) error {
|
||||||
|
return r.UpdateEntity(ctx, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetByID(ctx context.Context, id string) (*entities.PurchaseOrder, error) {
|
||||||
|
var order entities.PurchaseOrder
|
||||||
|
err := r.SmartGetByID(ctx, id, &order)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetByOrderNo(ctx context.Context, orderNo string) (*entities.PurchaseOrder, error) {
|
||||||
|
var order entities.PurchaseOrder
|
||||||
|
err := r.GetDB(ctx).Where("order_no = ?", orderNo).First(&order).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetByUserID(ctx context.Context, userID string, limit, offset int) ([]*entities.PurchaseOrder, int64, error) {
|
||||||
|
var orders []entities.PurchaseOrder
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
db := r.GetDB(ctx).Where("user_id = ?", userID)
|
||||||
|
|
||||||
|
// 获取总数
|
||||||
|
err := db.Model(&entities.PurchaseOrder{}).Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分页数据
|
||||||
|
err = db.Order("created_at DESC").
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&orders).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*entities.PurchaseOrder, len(orders))
|
||||||
|
for i := range orders {
|
||||||
|
result[i] = &orders[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetByUserIDAndProductID(ctx context.Context, userID, productID string) (*entities.PurchaseOrder, error) {
|
||||||
|
var order entities.PurchaseOrder
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("user_id = ? AND product_id = ? AND status = ?", userID, productID, entities.PurchaseOrderStatusPaid).
|
||||||
|
First(&order).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetByPaymentTypeAndTransactionID(ctx context.Context, paymentType, transactionID string) (*entities.PurchaseOrder, error) {
|
||||||
|
var order entities.PurchaseOrder
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("payment_type = ? AND trade_no = ?", paymentType, transactionID).
|
||||||
|
First(&order).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetByTradeNo(ctx context.Context, tradeNo string) (*entities.PurchaseOrder, error) {
|
||||||
|
var order entities.PurchaseOrder
|
||||||
|
err := r.GetDB(ctx).Where("trade_no = ?", tradeNo).First(&order).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) UpdatePaymentStatus(ctx context.Context, orderID string, status entities.PurchaseOrderStatus, tradeNo *string, payAmount, receiptAmount *string, paymentTime *time.Time) error {
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tradeNo != nil {
|
||||||
|
updates["trade_no"] = *tradeNo
|
||||||
|
}
|
||||||
|
|
||||||
|
if payAmount != nil {
|
||||||
|
updates["pay_amount"] = *payAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
if receiptAmount != nil {
|
||||||
|
updates["receipt_amount"] = *receiptAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
if paymentTime != nil {
|
||||||
|
updates["pay_time"] = *paymentTime
|
||||||
|
updates["notify_time"] = *paymentTime
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Model(&entities.PurchaseOrder{}).
|
||||||
|
Where("id = ?", orderID).
|
||||||
|
Updates(updates).Error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetUserPurchasedProductCodes(ctx context.Context, userID string) ([]string, error) {
|
||||||
|
var orders []entities.PurchaseOrder
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Select("product_code").
|
||||||
|
Where("user_id = ? AND status = ?", userID, entities.PurchaseOrderStatusPaid).
|
||||||
|
Find(&orders).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
codesMap := make(map[string]bool)
|
||||||
|
for _, order := range orders {
|
||||||
|
// 添加主产品编号
|
||||||
|
if order.ProductCode != "" {
|
||||||
|
codesMap[order.ProductCode] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
codes := make([]string, 0, len(codesMap))
|
||||||
|
for code := range codesMap {
|
||||||
|
codes = append(codes, code)
|
||||||
|
}
|
||||||
|
return codes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetUserPaidProductIDs(ctx context.Context, userID string) ([]string, error) {
|
||||||
|
var orders []entities.PurchaseOrder
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Select("product_id").
|
||||||
|
Where("user_id = ? AND status = ?", userID, entities.PurchaseOrderStatusPaid).
|
||||||
|
Find(&orders).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
idsMap := make(map[string]bool)
|
||||||
|
for _, order := range orders {
|
||||||
|
// 添加主产品ID
|
||||||
|
if order.ProductID != "" {
|
||||||
|
idsMap[order.ProductID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := make([]string, 0, len(idsMap))
|
||||||
|
for id := range idsMap {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) HasUserPurchased(ctx context.Context, userID string, productCode string) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.GetDB(ctx).Model(&entities.PurchaseOrder{}).
|
||||||
|
Where("user_id = ? AND product_code = ? AND status = ?", userID, productCode, entities.PurchaseOrderStatusPaid).
|
||||||
|
Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetExpiringOrders(ctx context.Context, before time.Time, limit int) ([]*entities.PurchaseOrder, error) {
|
||||||
|
// 购买订单实体没有过期时间字段,此方法返回空结果
|
||||||
|
return []*entities.PurchaseOrder{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetExpiredOrders(ctx context.Context, limit int) ([]*entities.PurchaseOrder, error) {
|
||||||
|
// 购买订单实体没有过期时间字段,此方法返回空结果
|
||||||
|
return []*entities.PurchaseOrder{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetByStatus(ctx context.Context, status entities.PurchaseOrderStatus, limit, offset int) ([]*entities.PurchaseOrder, int64, error) {
|
||||||
|
var orders []entities.PurchaseOrder
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
db := r.GetDB(ctx).Where("status = ?", status)
|
||||||
|
|
||||||
|
// 获取总数
|
||||||
|
err := db.Model(&entities.PurchaseOrder{}).Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分页数据
|
||||||
|
err = db.Order("created_at DESC").
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&orders).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*entities.PurchaseOrder, len(orders))
|
||||||
|
for i := range orders {
|
||||||
|
result[i] = &orders[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) GetByFilters(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.PurchaseOrder, error) {
|
||||||
|
var orders []entities.PurchaseOrder
|
||||||
|
|
||||||
|
db := r.GetDB(ctx)
|
||||||
|
|
||||||
|
// 应用筛选条件
|
||||||
|
if filters != nil {
|
||||||
|
if userID, ok := filters["user_id"]; ok {
|
||||||
|
db = db.Where("user_id = ?", userID)
|
||||||
|
}
|
||||||
|
if status, ok := filters["status"]; ok && status != "" {
|
||||||
|
db = db.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
if paymentType, ok := filters["payment_type"]; ok && paymentType != "" {
|
||||||
|
db = db.Where("payment_type = ?", paymentType)
|
||||||
|
}
|
||||||
|
if payChannel, ok := filters["pay_channel"]; ok && payChannel != "" {
|
||||||
|
db = db.Where("pay_channel = ?", payChannel)
|
||||||
|
}
|
||||||
|
if startTime, ok := filters["start_time"]; ok && startTime != "" {
|
||||||
|
db = db.Where("created_at >= ?", startTime)
|
||||||
|
}
|
||||||
|
if endTime, ok := filters["end_time"]; ok && endTime != "" {
|
||||||
|
db = db.Where("created_at <= ?", endTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用排序和分页
|
||||||
|
// 默认按创建时间倒序
|
||||||
|
db = db.Order("created_at DESC")
|
||||||
|
|
||||||
|
// 应用分页
|
||||||
|
if options.PageSize > 0 {
|
||||||
|
db = db.Limit(options.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Page > 0 {
|
||||||
|
db = db.Offset((options.Page - 1) * options.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
err := db.Find(&orders).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为指针切片
|
||||||
|
result := make([]*entities.PurchaseOrder, len(orders))
|
||||||
|
for i := range orders {
|
||||||
|
result[i] = &orders[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormPurchaseOrderRepository) CountByFilters(ctx context.Context, filters map[string]interface{}) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
db := r.GetDB(ctx).Model(&entities.PurchaseOrder{})
|
||||||
|
|
||||||
|
// 应用筛选条件
|
||||||
|
if filters != nil {
|
||||||
|
if userID, ok := filters["user_id"]; ok {
|
||||||
|
db = db.Where("user_id = ?", userID)
|
||||||
|
}
|
||||||
|
if status, ok := filters["status"]; ok && status != "" {
|
||||||
|
db = db.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
if paymentType, ok := filters["payment_type"]; ok && paymentType != "" {
|
||||||
|
db = db.Where("payment_type = ?", paymentType)
|
||||||
|
}
|
||||||
|
if payChannel, ok := filters["pay_channel"]; ok && payChannel != "" {
|
||||||
|
db = db.Where("pay_channel = ?", payChannel)
|
||||||
|
}
|
||||||
|
if startTime, ok := filters["start_time"]; ok && startTime != "" {
|
||||||
|
db = db.Where("created_at >= ?", startTime)
|
||||||
|
}
|
||||||
|
if endTime, ok := filters["end_time"]; ok && endTime != "" {
|
||||||
|
db = db.Where("created_at <= ?", endTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行计数
|
||||||
|
err := db.Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"tyapi-server/internal/domains/product/entities"
|
"tyapi-server/internal/domains/product/entities"
|
||||||
"tyapi-server/internal/domains/product/repositories"
|
"tyapi-server/internal/domains/product/repositories"
|
||||||
@@ -29,12 +30,8 @@ func NewGormComponentReportRepository(db *gorm.DB, logger *zap.Logger) repositor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GormComponentReportRepository) CreateDownload(ctx context.Context, download *entities.ComponentReportDownload) (*entities.ComponentReportDownload, error) {
|
func (r *GormComponentReportRepository) Create(ctx context.Context, download *entities.ComponentReportDownload) error {
|
||||||
err := r.CreateEntity(ctx, download)
|
return r.CreateEntity(ctx, download)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return download, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GormComponentReportRepository) UpdateDownload(ctx context.Context, download *entities.ComponentReportDownload) error {
|
func (r *GormComponentReportRepository) UpdateDownload(ctx context.Context, download *entities.ComponentReportDownload) error {
|
||||||
@@ -55,7 +52,7 @@ func (r *GormComponentReportRepository) GetDownloadByID(ctx context.Context, id
|
|||||||
|
|
||||||
func (r *GormComponentReportRepository) GetUserDownloads(ctx context.Context, userID string, productID *string) ([]*entities.ComponentReportDownload, error) {
|
func (r *GormComponentReportRepository) GetUserDownloads(ctx context.Context, userID string, productID *string) ([]*entities.ComponentReportDownload, error) {
|
||||||
var downloads []entities.ComponentReportDownload
|
var downloads []entities.ComponentReportDownload
|
||||||
query := r.GetDB(ctx).Where("user_id = ? AND payment_status = ?", userID, "success")
|
query := r.GetDB(ctx).Where("user_id = ?", userID)
|
||||||
|
|
||||||
if productID != nil && *productID != "" {
|
if productID != nil && *productID != "" {
|
||||||
query = query.Where("product_id = ?", *productID)
|
query = query.Where("product_id = ?", *productID)
|
||||||
@@ -76,7 +73,7 @@ func (r *GormComponentReportRepository) GetUserDownloads(ctx context.Context, us
|
|||||||
func (r *GormComponentReportRepository) HasUserDownloaded(ctx context.Context, userID string, productCode string) (bool, error) {
|
func (r *GormComponentReportRepository) HasUserDownloaded(ctx context.Context, userID string, productCode string) (bool, error) {
|
||||||
var count int64
|
var count int64
|
||||||
err := r.GetDB(ctx).Model(&entities.ComponentReportDownload{}).
|
err := r.GetDB(ctx).Model(&entities.ComponentReportDownload{}).
|
||||||
Where("user_id = ? AND product_code = ? AND payment_status = ?", userID, productCode, "success").
|
Where("user_id = ? AND product_code = ?", userID, productCode).
|
||||||
Count(&count).Error
|
Count(&count).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -88,7 +85,7 @@ func (r *GormComponentReportRepository) GetUserDownloadedProductCodes(ctx contex
|
|||||||
var downloads []entities.ComponentReportDownload
|
var downloads []entities.ComponentReportDownload
|
||||||
err := r.GetDB(ctx).
|
err := r.GetDB(ctx).
|
||||||
Select("DISTINCT sub_product_codes").
|
Select("DISTINCT sub_product_codes").
|
||||||
Where("user_id = ? AND payment_status = ?", userID, "success").
|
Where("user_id = ?", userID).
|
||||||
Find(&downloads).Error
|
Find(&downloads).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -119,7 +116,7 @@ func (r *GormComponentReportRepository) GetUserDownloadedProductCodes(ctx contex
|
|||||||
|
|
||||||
func (r *GormComponentReportRepository) GetDownloadByPaymentOrderID(ctx context.Context, orderID string) (*entities.ComponentReportDownload, error) {
|
func (r *GormComponentReportRepository) GetDownloadByPaymentOrderID(ctx context.Context, orderID string) (*entities.ComponentReportDownload, error) {
|
||||||
var download entities.ComponentReportDownload
|
var download entities.ComponentReportDownload
|
||||||
err := r.GetDB(ctx).Where("payment_order_id = ?", orderID).First(&download).Error
|
err := r.GetDB(ctx).Where("order_number = ?", orderID).First(&download).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, gorm.ErrRecordNotFound
|
return nil, gorm.ErrRecordNotFound
|
||||||
@@ -128,3 +125,65 @@ func (r *GormComponentReportRepository) GetDownloadByPaymentOrderID(ctx context.
|
|||||||
}
|
}
|
||||||
return &download, nil
|
return &download, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetActiveDownload 获取用户有效的下载记录
|
||||||
|
func (r *GormComponentReportRepository) GetActiveDownload(ctx context.Context, userID, productID string) (*entities.ComponentReportDownload, error) {
|
||||||
|
var download entities.ComponentReportDownload
|
||||||
|
|
||||||
|
// 先尝试查找有支付订单号的下载记录(已支付)
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("user_id = ? AND product_id = ? AND order_number IS NOT NULL AND deleted_at IS NULL", userID, productID).
|
||||||
|
Order("created_at DESC").
|
||||||
|
First(&download).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
// 如果没有找到有支付订单号的记录,尝试查找任何有效的下载记录
|
||||||
|
err = r.GetDB(ctx).
|
||||||
|
Where("user_id = ? AND product_id = ? AND deleted_at IS NULL", userID, productID).
|
||||||
|
Order("created_at DESC").
|
||||||
|
First(&download).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找到了下载记录,检查关联的购买订单状态
|
||||||
|
if download.OrderID != nil {
|
||||||
|
// 这里需要查询购买订单状态,但当前仓库没有依赖购买订单仓库
|
||||||
|
// 所以只检查是否有过期时间设置,如果有则认为已支付
|
||||||
|
if download.ExpiresAt == nil {
|
||||||
|
return nil, nil // 没有过期时间,表示未支付
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已过期
|
||||||
|
if download.IsExpired() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &download, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFilePath 更新下载记录文件路径
|
||||||
|
func (r *GormComponentReportRepository) UpdateFilePath(ctx context.Context, downloadID, filePath string) error {
|
||||||
|
return r.GetDB(ctx).Model(&entities.ComponentReportDownload{}).Where("id = ?", downloadID).Update("file_path", filePath).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementDownloadCount 增加下载次数
|
||||||
|
func (r *GormComponentReportRepository) IncrementDownloadCount(ctx context.Context, downloadID string) error {
|
||||||
|
now := time.Now()
|
||||||
|
return r.GetDB(ctx).Model(&entities.ComponentReportDownload{}).
|
||||||
|
Where("id = ?", downloadID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"download_count": gorm.Expr("download_count + 1"),
|
||||||
|
"last_download_at": &now,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ func (r *GormUIComponentRepository) Update(ctx context.Context, component entiti
|
|||||||
|
|
||||||
// Delete 删除UI组件
|
// Delete 删除UI组件
|
||||||
func (r *GormUIComponentRepository) Delete(ctx context.Context, id string) error {
|
func (r *GormUIComponentRepository) Delete(ctx context.Context, id string) error {
|
||||||
if err := r.db.WithContext(ctx).Delete(&entities.UIComponent{}, id).Error; err != nil {
|
// 记录删除操作的详细信息
|
||||||
|
if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&entities.UIComponent{}).Error; err != nil {
|
||||||
return fmt.Errorf("删除UI组件失败: %w", err)
|
return fmt.Errorf("删除UI组件失败: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
48
internal/infrastructure/external/jiguang/crypto.go
vendored
Normal file
48
internal/infrastructure/external/jiguang/crypto.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package jiguang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignMethod 签名方法类型
|
||||||
|
type SignMethod string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SignMethodMD5 SignMethod = "md5"
|
||||||
|
SignMethodHMACMD5 SignMethod = "hmac"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateSign 生成签名
|
||||||
|
// 根据 signMethod 参数选择使用 MD5 或 HMAC-MD5 算法
|
||||||
|
// MD5: md5(timestamp + "&appSecret=" + appSecret),然后转大写十六进制
|
||||||
|
// HMAC-MD5: hmac_md5(timestamp, appSecret),然后转大写十六进制
|
||||||
|
func GenerateSign(timestamp string, appSecret string, signMethod SignMethod) (string, error) {
|
||||||
|
var hashBytes []byte
|
||||||
|
|
||||||
|
switch signMethod {
|
||||||
|
case SignMethodMD5:
|
||||||
|
// MD5算法:在待签名字符串后面加上 &appSecret=xxx 再进行计算
|
||||||
|
signStr := timestamp + "&appSecret=" + appSecret
|
||||||
|
hash := md5.Sum([]byte(signStr))
|
||||||
|
hashBytes = hash[:]
|
||||||
|
case SignMethodHMACMD5:
|
||||||
|
// HMAC-MD5算法:使用 appSecret 初始化摘要算法再进行计算
|
||||||
|
mac := hmac.New(md5.New, []byte(appSecret))
|
||||||
|
mac.Write([]byte(timestamp))
|
||||||
|
hashBytes = mac.Sum(nil)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("不支持的签名方法: %s", signMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将二进制转化为大写的十六进制(正确签名应该为32大写字符串)
|
||||||
|
return strings.ToUpper(hex.EncodeToString(hashBytes)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSignWithDefault 使用默认的 HMAC-MD5 方法生成签名
|
||||||
|
func GenerateSignWithDefault(timestamp string, appSecret string) (string, error) {
|
||||||
|
return GenerateSign(timestamp, appSecret, SignMethodHMACMD5)
|
||||||
|
}
|
||||||
149
internal/infrastructure/external/jiguang/jiguang_errors.go
vendored
Normal file
149
internal/infrastructure/external/jiguang/jiguang_errors.go
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package jiguang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JiguangError 极光服务错误
|
||||||
|
type JiguangError struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 实现error接口
|
||||||
|
func (e *JiguangError) Error() string {
|
||||||
|
return fmt.Sprintf("极光错误 [%d]: %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess 检查是否成功
|
||||||
|
func (e *JiguangError) IsSuccess() bool {
|
||||||
|
return e.Code == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsQueryFailed 检查是否查询失败
|
||||||
|
func (e *JiguangError) IsQueryFailed() bool {
|
||||||
|
return e.Code == 922
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNoRecord 检查是否查无记录
|
||||||
|
func (e *JiguangError) IsNoRecord() bool {
|
||||||
|
return e.Code == 921
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsParamError 检查是否是参数相关错误
|
||||||
|
func (e *JiguangError) IsParamError() bool {
|
||||||
|
return e.Code == 400 || e.Code == 906 || e.Code == 914 || e.Code == 918
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthError 检查是否是认证相关错误
|
||||||
|
func (e *JiguangError) IsAuthError() bool {
|
||||||
|
return e.Code == 902 || e.Code == 903 || e.Code == 904 || e.Code == 905
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSystemError 检查是否是系统错误
|
||||||
|
func (e *JiguangError) IsSystemError() bool {
|
||||||
|
return e.Code == 405 || e.Code == 911 || e.Code == 912 || e.Code == 915 || e.Code == 916 || e.Code == 917 || e.Code == 919 || e.Code == 923
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预定义错误常量
|
||||||
|
var (
|
||||||
|
// 成功状态
|
||||||
|
ErrSuccess = &JiguangError{Code: 0, Message: "请求成功"}
|
||||||
|
|
||||||
|
// 参数错误
|
||||||
|
ErrParamInvalid = &JiguangError{Code: 400, Message: "请求参数不正确"}
|
||||||
|
ErrMethodInvalid = &JiguangError{Code: 405, Message: "请求方法不正确"}
|
||||||
|
ErrParamFormInvalid = &JiguangError{Code: 906, Message: "请求参数形式不正确"}
|
||||||
|
ErrBodyIncomplete = &JiguangError{Code: 914, Message: "Body 请求参数不完整"}
|
||||||
|
ErrBodyNotSupported = &JiguangError{Code: 918, Message: "Body 请求参数不支持"}
|
||||||
|
|
||||||
|
// 认证错误
|
||||||
|
ErrAppIDInvalid = &JiguangError{Code: 902, Message: "错误的 appId/账户已删除"}
|
||||||
|
ErrTimestampInvalid = &JiguangError{Code: 903, Message: "错误的时间戳/时间误差大于 10 分钟"}
|
||||||
|
ErrSignMethodInvalid = &JiguangError{Code: 904, Message: "无法识别的签名方法"}
|
||||||
|
ErrSignInvalid = &JiguangError{Code: 905, Message: "签名不合法"}
|
||||||
|
|
||||||
|
// 系统错误
|
||||||
|
ErrAccountStatusError = &JiguangError{Code: 911, Message: "账户状态异常"}
|
||||||
|
ErrInterfaceDisabled = &JiguangError{Code: 912, Message: "接口状态不可用"}
|
||||||
|
ErrAPICallError = &JiguangError{Code: 915, Message: "API 接口调用有误"}
|
||||||
|
ErrInternalError = &JiguangError{Code: 916, Message: "内部接口调用错误,请联系相关人员"}
|
||||||
|
ErrTimeout = &JiguangError{Code: 917, Message: "请求超时"}
|
||||||
|
ErrBusinessDisabled = &JiguangError{Code: 919, Message: "业务状态不可用"}
|
||||||
|
ErrInterfaceException = &JiguangError{Code: 923, Message: "接口异常"}
|
||||||
|
|
||||||
|
// 业务错误
|
||||||
|
ErrNoRecord = &JiguangError{Code: 921, Message: "查无记录"}
|
||||||
|
ErrQueryFailed = &JiguangError{Code: 922, Message: "查询失败"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewJiguangError 创建新的极光错误
|
||||||
|
func NewJiguangError(code int, message string) *JiguangError {
|
||||||
|
return &JiguangError{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJiguangErrorFromCode 根据状态码创建错误
|
||||||
|
func NewJiguangErrorFromCode(code int) *JiguangError {
|
||||||
|
switch code {
|
||||||
|
case 0:
|
||||||
|
return ErrSuccess
|
||||||
|
case 400:
|
||||||
|
return ErrParamInvalid
|
||||||
|
case 405:
|
||||||
|
return ErrMethodInvalid
|
||||||
|
case 902:
|
||||||
|
return ErrAppIDInvalid
|
||||||
|
case 903:
|
||||||
|
return ErrTimestampInvalid
|
||||||
|
case 904:
|
||||||
|
return ErrSignMethodInvalid
|
||||||
|
case 905:
|
||||||
|
return ErrSignInvalid
|
||||||
|
case 906:
|
||||||
|
return ErrParamFormInvalid
|
||||||
|
case 911:
|
||||||
|
return ErrAccountStatusError
|
||||||
|
case 912:
|
||||||
|
return ErrInterfaceDisabled
|
||||||
|
case 914:
|
||||||
|
return ErrBodyIncomplete
|
||||||
|
case 915:
|
||||||
|
return ErrAPICallError
|
||||||
|
case 916:
|
||||||
|
return ErrInternalError
|
||||||
|
case 917:
|
||||||
|
return ErrTimeout
|
||||||
|
case 918:
|
||||||
|
return ErrBodyNotSupported
|
||||||
|
case 919:
|
||||||
|
return ErrBusinessDisabled
|
||||||
|
case 921:
|
||||||
|
return ErrNoRecord
|
||||||
|
case 922:
|
||||||
|
return ErrQueryFailed
|
||||||
|
case 923:
|
||||||
|
return ErrInterfaceException
|
||||||
|
default:
|
||||||
|
return &JiguangError{
|
||||||
|
Code: code,
|
||||||
|
Message: fmt.Sprintf("未知错误码: %d", code),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsJiguangError 检查是否是极光错误
|
||||||
|
func IsJiguangError(err error) bool {
|
||||||
|
_, ok := err.(*JiguangError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJiguangError 获取极光错误
|
||||||
|
func GetJiguangError(err error) *JiguangError {
|
||||||
|
if jiguangErr, ok := err.(*JiguangError); ok {
|
||||||
|
return jiguangErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
85
internal/infrastructure/external/jiguang/jiguang_factory.go
vendored
Normal file
85
internal/infrastructure/external/jiguang/jiguang_factory.go
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package jiguang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/config"
|
||||||
|
"tyapi-server/internal/shared/external_logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewJiguangServiceWithConfig 使用配置创建极光服务
|
||||||
|
func NewJiguangServiceWithConfig(cfg *config.Config) (*JiguangService, error) {
|
||||||
|
// 将配置类型转换为通用外部服务日志配置
|
||||||
|
loggingConfig := external_logger.ExternalServiceLoggingConfig{
|
||||||
|
Enabled: cfg.Jiguang.Logging.Enabled,
|
||||||
|
LogDir: cfg.Jiguang.Logging.LogDir,
|
||||||
|
ServiceName: "jiguang",
|
||||||
|
UseDaily: cfg.Jiguang.Logging.UseDaily,
|
||||||
|
EnableLevelSeparation: cfg.Jiguang.Logging.EnableLevelSeparation,
|
||||||
|
LevelConfigs: make(map[string]external_logger.ExternalServiceLevelFileConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换级别配置
|
||||||
|
for key, value := range cfg.Jiguang.Logging.LevelConfigs {
|
||||||
|
loggingConfig.LevelConfigs[key] = external_logger.ExternalServiceLevelFileConfig{
|
||||||
|
MaxSize: value.MaxSize,
|
||||||
|
MaxBackups: value.MaxBackups,
|
||||||
|
MaxAge: value.MaxAge,
|
||||||
|
Compress: value.Compress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建通用外部服务日志器
|
||||||
|
logger, err := external_logger.NewExternalServiceLogger(loggingConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析签名方法
|
||||||
|
var signMethod SignMethod
|
||||||
|
if cfg.Jiguang.SignMethod == "md5" {
|
||||||
|
signMethod = SignMethodMD5
|
||||||
|
} else {
|
||||||
|
signMethod = SignMethodHMACMD5 // 默认使用 HMAC-MD5
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析超时时间
|
||||||
|
timeout := 60 * time.Second
|
||||||
|
if cfg.Jiguang.Timeout > 0 {
|
||||||
|
timeout = cfg.Jiguang.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建极光服务
|
||||||
|
service := NewJiguangService(
|
||||||
|
cfg.Jiguang.URL,
|
||||||
|
cfg.Jiguang.AppID,
|
||||||
|
cfg.Jiguang.AppSecret,
|
||||||
|
signMethod,
|
||||||
|
timeout,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJiguangServiceWithLogging 使用自定义日志配置创建极光服务
|
||||||
|
func NewJiguangServiceWithLogging(url, appID, appSecret string, signMethod SignMethod, timeout time.Duration, loggingConfig external_logger.ExternalServiceLoggingConfig) (*JiguangService, error) {
|
||||||
|
// 设置服务名称
|
||||||
|
loggingConfig.ServiceName = "jiguang"
|
||||||
|
|
||||||
|
// 创建通用外部服务日志器
|
||||||
|
logger, err := external_logger.NewExternalServiceLogger(loggingConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建极光服务
|
||||||
|
service := NewJiguangService(url, appID, appSecret, signMethod, timeout, logger)
|
||||||
|
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJiguangServiceSimple 创建简单的极光服务(无日志)
|
||||||
|
func NewJiguangServiceSimple(url, appID, appSecret string, signMethod SignMethod, timeout time.Duration) *JiguangService {
|
||||||
|
return NewJiguangService(url, appID, appSecret, signMethod, timeout, nil)
|
||||||
|
}
|
||||||
270
internal/infrastructure/external/jiguang/jiguang_service.go
vendored
Normal file
270
internal/infrastructure/external/jiguang/jiguang_service.go
vendored
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
package jiguang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/shared/external_logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDatasource = errors.New("数据源异常")
|
||||||
|
ErrSystem = errors.New("系统异常")
|
||||||
|
ErrNotFound = errors.New("查询为空")
|
||||||
|
)
|
||||||
|
|
||||||
|
// JiguangResponse 极光API响应结构
|
||||||
|
type JiguangResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
OrderID string `json:"order_id"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JiguangConfig 极光服务配置
|
||||||
|
type JiguangConfig struct {
|
||||||
|
URL string
|
||||||
|
AppID string
|
||||||
|
AppSecret string
|
||||||
|
SignMethod SignMethod // 签名方法:md5 或 hmac
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// JiguangService 极光服务
|
||||||
|
type JiguangService struct {
|
||||||
|
config JiguangConfig
|
||||||
|
logger *external_logger.ExternalServiceLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJiguangService 创建一个新的极光服务实例
|
||||||
|
func NewJiguangService(url, appID, appSecret string, signMethod SignMethod, timeout time.Duration, logger *external_logger.ExternalServiceLogger) *JiguangService {
|
||||||
|
// 如果没有指定签名方法,默认使用 HMAC-MD5
|
||||||
|
if signMethod == "" {
|
||||||
|
signMethod = SignMethodHMACMD5
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有指定超时时间,默认使用 60 秒
|
||||||
|
if timeout == 0 {
|
||||||
|
timeout = 60 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JiguangService{
|
||||||
|
config: JiguangConfig{
|
||||||
|
URL: url,
|
||||||
|
AppID: appID,
|
||||||
|
AppSecret: appSecret,
|
||||||
|
SignMethod: signMethod,
|
||||||
|
Timeout: timeout,
|
||||||
|
},
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateRequestID 生成请求ID
|
||||||
|
func (j *JiguangService) generateRequestID() string {
|
||||||
|
timestamp := time.Now().UnixNano()
|
||||||
|
hash := md5.Sum([]byte(fmt.Sprintf("%d_%s", timestamp, j.config.AppID)))
|
||||||
|
return fmt.Sprintf("jiguang_%x", hash[:8])
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallAPI 调用极光API
|
||||||
|
// apiCode: API服务编码(如 marriage-single-v2),用于请求头
|
||||||
|
// apiPath: API路径(如 marriage/single-v2),用于URL路径
|
||||||
|
// params: 请求参数(会作为JSON body发送)
|
||||||
|
func (j *JiguangService) CallAPI(ctx context.Context, apiCode string, apiPath string, params map[string]interface{}) (resp []byte, err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
requestID := j.generateRequestID()
|
||||||
|
|
||||||
|
// 生成时间戳(毫秒)
|
||||||
|
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||||
|
|
||||||
|
// 从ctx中获取transactionId
|
||||||
|
var transactionID string
|
||||||
|
if ctxTransactionID, ok := ctx.Value("transaction_id").(string); ok {
|
||||||
|
transactionID = ctxTransactionID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成签名
|
||||||
|
sign, signErr := GenerateSign(timestamp, j.config.AppSecret, j.config.SignMethod)
|
||||||
|
if signErr != nil {
|
||||||
|
err = errors.Join(ErrSystem, fmt.Errorf("生成签名失败: %w", signErr))
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整的请求URL,使用apiPath作为路径
|
||||||
|
requestURL := strings.TrimSuffix(j.config.URL, "/") + "/" + strings.TrimPrefix(apiPath, "/")
|
||||||
|
|
||||||
|
// 记录请求日志
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogRequest(requestID, transactionID, apiCode, requestURL, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将请求参数转换为JSON
|
||||||
|
jsonData, marshalErr := json.Marshal(params)
|
||||||
|
if marshalErr != nil {
|
||||||
|
err = errors.Join(ErrSystem, marshalErr)
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HTTP POST请求
|
||||||
|
req, newRequestErr := http.NewRequestWithContext(ctx, "POST", requestURL, bytes.NewBuffer(jsonData))
|
||||||
|
if newRequestErr != nil {
|
||||||
|
err = errors.Join(ErrSystem, newRequestErr)
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("appId", j.config.AppID)
|
||||||
|
req.Header.Set("apiCode", apiCode)
|
||||||
|
req.Header.Set("timestamp", timestamp)
|
||||||
|
req.Header.Set("signMethod", string(j.config.SignMethod))
|
||||||
|
req.Header.Set("sign", sign)
|
||||||
|
|
||||||
|
// 创建HTTP客户端
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: j.config.Timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
httpResp, clientDoErr := client.Do(req)
|
||||||
|
if clientDoErr != nil {
|
||||||
|
// 检查是否是超时错误
|
||||||
|
isTimeout := false
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
isTimeout = true
|
||||||
|
} else if netErr, ok := clientDoErr.(interface{ Timeout() bool }); ok && netErr.Timeout() {
|
||||||
|
isTimeout = true
|
||||||
|
} else if errStr := clientDoErr.Error(); errStr == "context deadline exceeded" ||
|
||||||
|
errStr == "timeout" ||
|
||||||
|
errStr == "Client.Timeout exceeded" ||
|
||||||
|
errStr == "net/http: request canceled" {
|
||||||
|
isTimeout = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTimeout {
|
||||||
|
err = errors.Join(ErrDatasource, fmt.Errorf("API请求超时: %v", clientDoErr))
|
||||||
|
} else {
|
||||||
|
err = errors.Join(ErrSystem, clientDoErr)
|
||||||
|
}
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
closeErr := Body.Close()
|
||||||
|
if closeErr != nil {
|
||||||
|
// 记录关闭错误
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, errors.Join(ErrSystem, fmt.Errorf("关闭响应体失败: %w", closeErr)), params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(httpResp.Body)
|
||||||
|
|
||||||
|
// 计算请求耗时
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
|
||||||
|
// 读取响应体
|
||||||
|
bodyBytes, readErr := io.ReadAll(httpResp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
err = errors.Join(ErrSystem, readErr)
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查HTTP状态码
|
||||||
|
if httpResp.StatusCode != http.StatusOK {
|
||||||
|
err = errors.Join(ErrSystem, fmt.Errorf("极光请求失败,状态码: %d", httpResp.StatusCode))
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应结构
|
||||||
|
var jiguangResp JiguangResponse
|
||||||
|
if err := json.Unmarshal(bodyBytes, &jiguangResp); err != nil {
|
||||||
|
err = errors.Join(ErrSystem, fmt.Errorf("响应解析失败: %w", err))
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogError(requestID, transactionID, apiCode, err, params)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录响应日志
|
||||||
|
if j.logger != nil {
|
||||||
|
if jiguangResp.OrderID != "" {
|
||||||
|
j.logger.LogResponseWithID(requestID, transactionID, apiCode, httpResp.StatusCode, bodyBytes, duration, jiguangResp.OrderID)
|
||||||
|
} else {
|
||||||
|
j.logger.LogResponse(requestID, transactionID, apiCode, httpResp.StatusCode, bodyBytes, duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查业务状态码
|
||||||
|
if jiguangResp.Code != 0 {
|
||||||
|
// 创建极光错误
|
||||||
|
jiguangErr := NewJiguangErrorFromCode(jiguangResp.Code)
|
||||||
|
if jiguangErr.Message == fmt.Sprintf("未知错误码: %d", jiguangResp.Code) && jiguangResp.Msg != "" {
|
||||||
|
jiguangErr.Message = jiguangResp.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录错误日志
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogErrorWithResponseID(requestID, transactionID, apiCode, jiguangErr, params, jiguangResp.OrderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据错误类型返回不同的错误
|
||||||
|
if jiguangErr.IsNoRecord() {
|
||||||
|
return nil, errors.Join(ErrNotFound, jiguangErr)
|
||||||
|
} else if jiguangErr.IsQueryFailed() {
|
||||||
|
return nil, errors.Join(ErrDatasource, jiguangErr)
|
||||||
|
} else if jiguangErr.IsSystemError() {
|
||||||
|
return nil, errors.Join(ErrSystem, jiguangErr)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(ErrDatasource, jiguangErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功响应,返回data字段
|
||||||
|
if jiguangResp.Data == nil {
|
||||||
|
return []byte("{}"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将data转换为JSON字节
|
||||||
|
dataBytes, err := json.Marshal(jiguangResp.Data)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Join(ErrSystem, fmt.Errorf("data字段序列化失败: %w", err))
|
||||||
|
if j.logger != nil {
|
||||||
|
j.logger.LogErrorWithResponseID(requestID, transactionID, apiCode, err, params, jiguangResp.OrderID)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig 获取配置信息
|
||||||
|
func (j *JiguangService) GetConfig() JiguangConfig {
|
||||||
|
return j.config
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tyapi-server/internal/shared/external_logger"
|
"tyapi-server/internal/shared/external_logger"
|
||||||
@@ -90,13 +91,14 @@ func (m *MuziService) generateRequestID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CallAPI 调用木子数据接口
|
// CallAPI 调用木子数据接口
|
||||||
func (m *MuziService) CallAPI(ctx context.Context, prodCode string, params map[string]interface{}) (json.RawMessage, error) {
|
func (m *MuziService) CallAPI(ctx context.Context, prodCode string, path string, params map[string]interface{},paramSign map[string]interface{}) (json.RawMessage, error) {
|
||||||
requestID := m.generateRequestID()
|
requestID := m.generateRequestID()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
timestamp := strconv.FormatInt(now.UnixMilli(), 10)
|
timestamp := strconv.FormatInt(now.UnixMilli(), 10)
|
||||||
|
|
||||||
flatParams := flattenParams(params)
|
flatParams := flattenParams(params)
|
||||||
signParts := collectSignatureValues(params)
|
|
||||||
|
signParts := collectSignatureValues(paramSign)
|
||||||
signature := m.GenerateSignature(prodCode, timestamp, signParts...)
|
signature := m.GenerateSignature(prodCode, timestamp, signParts...)
|
||||||
|
|
||||||
// 从上下文获取链路ID
|
// 从上下文获取链路ID
|
||||||
@@ -128,7 +130,21 @@ func (m *MuziService) CallAPI(ctx context.Context, prodCode string, params map[s
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req, reqErr := http.NewRequestWithContext(ctx, http.MethodPost, m.config.URL, bytes.NewBuffer(bodyBytes))
|
// 构建完整的URL,拼接路径参数
|
||||||
|
fullURL := m.config.URL
|
||||||
|
if path != "" {
|
||||||
|
// 确保路径以/开头
|
||||||
|
if !strings.HasPrefix(path, "/") {
|
||||||
|
path = "/" + path
|
||||||
|
}
|
||||||
|
// 确保URL不以/结尾,避免双斜杠
|
||||||
|
if strings.HasSuffix(fullURL, "/") {
|
||||||
|
fullURL = fullURL[:len(fullURL)-1]
|
||||||
|
}
|
||||||
|
fullURL += path
|
||||||
|
}
|
||||||
|
|
||||||
|
req, reqErr := http.NewRequestWithContext(ctx, http.MethodPost, fullURL, bytes.NewBuffer(bodyBytes))
|
||||||
if reqErr != nil {
|
if reqErr != nil {
|
||||||
err := errors.Join(ErrSystem, reqErr)
|
err := errors.Join(ErrSystem, reqErr)
|
||||||
if m.logger != nil {
|
if m.logger != nil {
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ var APIEndpoints = map[string]string{
|
|||||||
"FinancingHistory": "/open/cd/findHistoryRongzi/2.0", // 融资历史
|
"FinancingHistory": "/open/cd/findHistoryRongzi/2.0", // 融资历史
|
||||||
"PunishmentInfo": "/open/mr/punishmentInfo/3.0", // 行政处罚
|
"PunishmentInfo": "/open/mr/punishmentInfo/3.0", // 行政处罚
|
||||||
"AbnormalInfo": "/open/mr/abnormal/2.0", // 经营异常
|
"AbnormalInfo": "/open/mr/abnormal/2.0", // 经营异常
|
||||||
"OwnTax": "/open/mr/ownTax", // 欠税公告
|
"OwnTax": "/open/mr/ownTax/2.0", // 欠税公告
|
||||||
"TaxContravention": "/open/mr/taxContravention", // 税收违法
|
"TaxContravention": "/open/mr/taxContravention/2.0", // 税收违法
|
||||||
|
"holderChange": "/open/ic/holderChange/2.0", // 股权变更
|
||||||
|
"baseinfo": "/open/ic/baseinfo/normal", // 企业基本信息
|
||||||
|
"investtree": "/v3/open/investtree", // 股权穿透
|
||||||
}
|
}
|
||||||
|
|
||||||
// TianYanChaConfig 天眼查配置
|
// TianYanChaConfig 天眼查配置
|
||||||
|
|||||||
@@ -0,0 +1,428 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"tyapi-server/internal/application/product"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ComponentReportOrderHandler 组件报告订单处理器
|
||||||
|
type ComponentReportOrderHandler struct {
|
||||||
|
service *product.ComponentReportOrderService
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewComponentReportOrderHandler 创建组件报告订单处理器
|
||||||
|
func NewComponentReportOrderHandler(
|
||||||
|
service *product.ComponentReportOrderService,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) *ComponentReportOrderHandler {
|
||||||
|
return &ComponentReportOrderHandler{
|
||||||
|
service: service,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckDownloadAvailability 检查下载可用性
|
||||||
|
// GET /api/v1/products/:id/component-report/check
|
||||||
|
func (h *ComponentReportOrderHandler) CheckDownloadAvailability(c *gin.Context) {
|
||||||
|
h.logger.Info("开始检查下载可用性")
|
||||||
|
|
||||||
|
productID := c.Param("id")
|
||||||
|
h.logger.Info("获取产品ID", zap.String("product_id", productID))
|
||||||
|
|
||||||
|
if productID == "" {
|
||||||
|
h.logger.Error("产品ID不能为空")
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "产品ID不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
h.logger.Info("获取用户ID", zap.String("user_id", userID))
|
||||||
|
|
||||||
|
if userID == "" {
|
||||||
|
h.logger.Error("用户未登录")
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"message": "用户未登录",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用服务获取订单信息,检查是否可以下载
|
||||||
|
orderInfo, err := h.service.GetOrderInfo(c.Request.Context(), userID, productID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取订单信息失败", zap.Error(err), zap.String("product_id", productID), zap.String("user_id", userID))
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "获取订单信息失败",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("获取订单信息成功", zap.Bool("can_download", orderInfo.CanDownload), zap.Bool("is_package", orderInfo.IsPackage))
|
||||||
|
|
||||||
|
// 返回检查结果
|
||||||
|
message := "需要购买"
|
||||||
|
if orderInfo.CanDownload {
|
||||||
|
message = "可以下载"
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"data": gin.H{
|
||||||
|
"can_download": orderInfo.CanDownload,
|
||||||
|
"is_package": orderInfo.IsPackage,
|
||||||
|
"message": message,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDownloadInfo 获取下载信息和价格计算
|
||||||
|
// GET /api/v1/products/:id/component-report/info
|
||||||
|
func (h *ComponentReportOrderHandler) GetDownloadInfo(c *gin.Context) {
|
||||||
|
h.logger.Info("开始获取下载信息和价格计算")
|
||||||
|
|
||||||
|
productID := c.Param("id")
|
||||||
|
h.logger.Info("获取产品ID", zap.String("product_id", productID))
|
||||||
|
|
||||||
|
if productID == "" {
|
||||||
|
h.logger.Error("产品ID不能为空")
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "产品ID不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
h.logger.Info("获取用户ID", zap.String("user_id", userID))
|
||||||
|
|
||||||
|
if userID == "" {
|
||||||
|
h.logger.Error("用户未登录")
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"message": "用户未登录",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderInfo, err := h.service.GetOrderInfo(c.Request.Context(), userID, productID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取订单信息失败", zap.Error(err), zap.String("product_id", productID), zap.String("user_id", userID))
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "获取订单信息失败",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录详细的订单信息
|
||||||
|
h.logger.Info("获取订单信息成功",
|
||||||
|
zap.String("product_id", orderInfo.ProductID),
|
||||||
|
zap.String("product_code", orderInfo.ProductCode),
|
||||||
|
zap.String("product_name", orderInfo.ProductName),
|
||||||
|
zap.Bool("is_package", orderInfo.IsPackage),
|
||||||
|
zap.Int("sub_products_count", len(orderInfo.SubProducts)),
|
||||||
|
zap.String("price", orderInfo.Price),
|
||||||
|
zap.Strings("purchased_product_codes", orderInfo.PurchasedProductCodes),
|
||||||
|
zap.Bool("can_download", orderInfo.CanDownload),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 记录子产品详情
|
||||||
|
for i, subProduct := range orderInfo.SubProducts {
|
||||||
|
h.logger.Info("子产品信息",
|
||||||
|
zap.Int("index", i),
|
||||||
|
zap.String("sub_product_id", subProduct.ProductID),
|
||||||
|
zap.String("sub_product_code", subProduct.ProductCode),
|
||||||
|
zap.String("sub_product_name", subProduct.ProductName),
|
||||||
|
zap.String("price", subProduct.Price),
|
||||||
|
zap.Bool("is_purchased", subProduct.IsPurchased),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"data": orderInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePaymentOrder 创建支付订单
|
||||||
|
// POST /api/v1/products/:id/component-report/create-order
|
||||||
|
func (h *ComponentReportOrderHandler) CreatePaymentOrder(c *gin.Context) {
|
||||||
|
h.logger.Info("开始创建支付订单")
|
||||||
|
|
||||||
|
productID := c.Param("id")
|
||||||
|
h.logger.Info("获取产品ID", zap.String("product_id", productID))
|
||||||
|
|
||||||
|
if productID == "" {
|
||||||
|
h.logger.Error("产品ID不能为空")
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "产品ID不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
h.logger.Info("获取用户ID", zap.String("user_id", userID))
|
||||||
|
|
||||||
|
if userID == "" {
|
||||||
|
h.logger.Error("用户未登录")
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"message": "用户未登录",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req product.CreatePaymentOrderRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
h.logger.Error("请求参数错误", zap.Error(err))
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "请求参数错误",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录请求参数
|
||||||
|
h.logger.Info("支付订单请求参数",
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.String("product_id", productID),
|
||||||
|
zap.String("payment_type", req.PaymentType),
|
||||||
|
zap.String("platform", req.Platform),
|
||||||
|
zap.Strings("sub_product_codes", req.SubProductCodes),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 设置用户ID和产品ID
|
||||||
|
req.UserID = userID
|
||||||
|
req.ProductID = productID
|
||||||
|
|
||||||
|
// 如果未指定支付平台,根据User-Agent判断
|
||||||
|
if req.Platform == "" {
|
||||||
|
userAgent := c.GetHeader("User-Agent")
|
||||||
|
req.Platform = h.detectPlatform(userAgent)
|
||||||
|
h.logger.Info("根据User-Agent检测平台", zap.String("user_agent", userAgent), zap.String("detected_platform", req.Platform))
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.CreatePaymentOrder(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("创建支付订单失败", zap.Error(err),
|
||||||
|
zap.String("product_id", productID),
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.String("payment_type", req.PaymentType))
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "创建支付订单失败",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录创建订单成功响应
|
||||||
|
h.logger.Info("创建支付订单成功",
|
||||||
|
zap.String("order_id", response.OrderID),
|
||||||
|
zap.String("order_no", response.OrderNo),
|
||||||
|
zap.String("payment_type", response.PaymentType),
|
||||||
|
zap.String("amount", response.Amount),
|
||||||
|
zap.String("code_url", response.CodeURL),
|
||||||
|
zap.String("pay_url", response.PayURL),
|
||||||
|
)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"data": response,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPaymentStatus 检查支付状态
|
||||||
|
// GET /api/v1/component-report/check-payment/:orderId
|
||||||
|
func (h *ComponentReportOrderHandler) CheckPaymentStatus(c *gin.Context) {
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
if userID == "" {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"message": "用户未登录",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderID := c.Param("orderId")
|
||||||
|
if orderID == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "订单ID不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.CheckPaymentStatus(c.Request.Context(), orderID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("检查支付状态失败", zap.Error(err), zap.String("order_id", orderID))
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "检查支付状态失败",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"data": response,
|
||||||
|
"message": "查询支付状态成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadFile 下载文件
|
||||||
|
// GET /api/v1/component-report/download/:orderId
|
||||||
|
func (h *ComponentReportOrderHandler) DownloadFile(c *gin.Context) {
|
||||||
|
h.logger.Info("开始处理文件下载请求")
|
||||||
|
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
if userID == "" {
|
||||||
|
h.logger.Error("用户未登录")
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"message": "用户未登录",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("获取用户ID", zap.String("user_id", userID))
|
||||||
|
|
||||||
|
orderID := c.Param("orderId")
|
||||||
|
if orderID == "" {
|
||||||
|
h.logger.Error("订单ID不能为空")
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "订单ID不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("获取订单ID", zap.String("order_id", orderID))
|
||||||
|
|
||||||
|
filePath, err := h.service.DownloadFile(c.Request.Context(), orderID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("下载文件失败", zap.Error(err), zap.String("order_id", orderID), zap.String("user_id", userID))
|
||||||
|
|
||||||
|
// 根据错误类型返回不同的状态码和消息
|
||||||
|
errorMessage := err.Error()
|
||||||
|
statusCode := http.StatusInternalServerError
|
||||||
|
|
||||||
|
// 根据错误消息判断具体错误类型
|
||||||
|
if strings.Contains(errorMessage, "购买订单不存在") {
|
||||||
|
statusCode = http.StatusNotFound
|
||||||
|
} else if strings.Contains(errorMessage, "订单未支付") || strings.Contains(errorMessage, "已过期") {
|
||||||
|
statusCode = http.StatusForbidden
|
||||||
|
} else if strings.Contains(errorMessage, "生成报告文件失败") {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(statusCode, gin.H{
|
||||||
|
"code": statusCode,
|
||||||
|
"message": "下载文件失败",
|
||||||
|
"error": errorMessage,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("成功获取文件路径",
|
||||||
|
zap.String("order_id", orderID),
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.String("file_path", filePath))
|
||||||
|
|
||||||
|
// 设置响应头
|
||||||
|
c.Header("Content-Type", "application/zip")
|
||||||
|
c.Header("Content-Disposition", "attachment; filename=component_report.zip")
|
||||||
|
|
||||||
|
// 发送文件
|
||||||
|
h.logger.Info("开始发送文件", zap.String("file_path", filePath))
|
||||||
|
c.File(filePath)
|
||||||
|
h.logger.Info("文件发送成功", zap.String("file_path", filePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserOrders 获取用户订单列表
|
||||||
|
// GET /api/v1/component-report/orders
|
||||||
|
func (h *ComponentReportOrderHandler) GetUserOrders(c *gin.Context) {
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
if userID == "" {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"message": "用户未登录",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析分页参数
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize < 1 || pageSize > 100 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
|
orders, total, err := h.service.GetUserOrders(c.Request.Context(), userID, pageSize, offset)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取用户订单列表失败", zap.Error(err), zap.String("user_id", userID))
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "获取用户订单列表失败",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"data": gin.H{
|
||||||
|
"list": orders,
|
||||||
|
"total": total,
|
||||||
|
"page": page,
|
||||||
|
"page_size": pageSize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectPlatform 根据 User-Agent 检测支付平台类型
|
||||||
|
func (h *ComponentReportOrderHandler) detectPlatform(userAgent string) string {
|
||||||
|
if userAgent == "" {
|
||||||
|
return "h5" // 默认 H5
|
||||||
|
}
|
||||||
|
|
||||||
|
ua := strings.ToLower(userAgent)
|
||||||
|
|
||||||
|
// 检测移动设备
|
||||||
|
if strings.Contains(ua, "mobile") || strings.Contains(ua, "android") ||
|
||||||
|
strings.Contains(ua, "iphone") || strings.Contains(ua, "ipad") {
|
||||||
|
// 检测是否是支付宝或微信内置浏览器
|
||||||
|
if strings.Contains(ua, "alipay") {
|
||||||
|
return "app" // 支付宝 APP
|
||||||
|
}
|
||||||
|
if strings.Contains(ua, "micromessenger") {
|
||||||
|
return "h5" // 微信 H5
|
||||||
|
}
|
||||||
|
return "h5" // 移动端默认 H5
|
||||||
|
}
|
||||||
|
|
||||||
|
// PC 端
|
||||||
|
return "pc"
|
||||||
|
}
|
||||||
@@ -1106,3 +1106,192 @@ func (h *FinanceHandler) DebugEventSystem(c *gin.Context) {
|
|||||||
|
|
||||||
h.responseBuilder.Success(c, debugInfo, "事件系统调试信息")
|
h.responseBuilder.Success(c, debugInfo, "事件系统调试信息")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserPurchaseRecords 获取用户购买记录
|
||||||
|
// @Summary 获取用户购买记录
|
||||||
|
// @Description 获取当前用户的购买记录列表,支持分页和筛选
|
||||||
|
// @Tags 财务管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param page query int false "页码" default(1)
|
||||||
|
// @Param page_size query int false "每页数量" default(10)
|
||||||
|
// @Param payment_type query string false "支付类型: alipay, wechat, free"
|
||||||
|
// @Param pay_channel query string false "支付渠道: alipay, wechat"
|
||||||
|
// @Param status query string false "订单状态: created, paid, failed, cancelled, refunded, closed"
|
||||||
|
// @Param start_time query string false "开始时间 (格式: 2006-01-02 15:04:05)"
|
||||||
|
// @Param end_time query string false "结束时间 (格式: 2006-01-02 15:04:05)"
|
||||||
|
// @Param min_amount query string false "最小金额"
|
||||||
|
// @Param max_amount query string false "最大金额"
|
||||||
|
// @Param product_code query string false "产品编号"
|
||||||
|
// @Success 200 {object} responses.PurchaseRecordListResponse "获取成功"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
|
// @Router /api/v1/finance/purchase-records [get]
|
||||||
|
func (h *FinanceHandler) GetUserPurchaseRecords(c *gin.Context) {
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
if userID == "" {
|
||||||
|
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析查询参数
|
||||||
|
page := h.getIntQuery(c, "page", 1)
|
||||||
|
pageSize := h.getIntQuery(c, "page_size", 10)
|
||||||
|
|
||||||
|
// 构建筛选条件
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
|
||||||
|
// 时间范围筛选
|
||||||
|
if startTime := c.Query("start_time"); startTime != "" {
|
||||||
|
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
|
||||||
|
filters["start_time"] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if endTime := c.Query("end_time"); endTime != "" {
|
||||||
|
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
|
||||||
|
filters["end_time"] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付类型筛选
|
||||||
|
if paymentType := c.Query("payment_type"); paymentType != "" {
|
||||||
|
filters["payment_type"] = paymentType
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付渠道筛选
|
||||||
|
if payChannel := c.Query("pay_channel"); payChannel != "" {
|
||||||
|
filters["pay_channel"] = payChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态筛选
|
||||||
|
if status := c.Query("status"); status != "" {
|
||||||
|
filters["status"] = status
|
||||||
|
}
|
||||||
|
|
||||||
|
// 产品编号筛选
|
||||||
|
if productCode := c.Query("product_code"); productCode != "" {
|
||||||
|
filters["product_code"] = productCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 金额范围筛选
|
||||||
|
if minAmount := c.Query("min_amount"); minAmount != "" {
|
||||||
|
filters["min_amount"] = minAmount
|
||||||
|
}
|
||||||
|
if maxAmount := c.Query("max_amount"); maxAmount != "" {
|
||||||
|
filters["max_amount"] = maxAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建分页选项
|
||||||
|
options := interfaces.ListOptions{
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
Sort: "created_at",
|
||||||
|
Order: "desc",
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.appService.GetUserPurchaseRecords(c.Request.Context(), userID, filters, options)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取用户购买记录失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, "获取购买记录失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.responseBuilder.Success(c, result, "获取用户购买记录成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdminPurchaseRecords 获取管理端购买记录
|
||||||
|
// @Summary 获取管理端购买记录
|
||||||
|
// @Description 获取所有用户的购买记录列表,支持分页和筛选(管理员权限)
|
||||||
|
// @Tags 管理员-财务管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param page query int false "页码" default(1)
|
||||||
|
// @Param page_size query int false "每页数量" default(10)
|
||||||
|
// @Param user_id query string false "用户ID"
|
||||||
|
// @Param payment_type query string false "支付类型: alipay, wechat, free"
|
||||||
|
// @Param pay_channel query string false "支付渠道: alipay, wechat"
|
||||||
|
// @Param status query string false "订单状态: created, paid, failed, cancelled, refunded, closed"
|
||||||
|
// @Param start_time query string false "开始时间 (格式: 2006-01-02 15:04:05)"
|
||||||
|
// @Param end_time query string false "结束时间 (格式: 2006-01-02 15:04:05)"
|
||||||
|
// @Param min_amount query string false "最小金额"
|
||||||
|
// @Param max_amount query string false "最大金额"
|
||||||
|
// @Param product_code query string false "产品编号"
|
||||||
|
// @Success 200 {object} responses.PurchaseRecordListResponse "获取成功"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||||
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
|
// @Router /api/v1/admin/finance/purchase-records [get]
|
||||||
|
func (h *FinanceHandler) GetAdminPurchaseRecords(c *gin.Context) {
|
||||||
|
// 解析查询参数
|
||||||
|
page := h.getIntQuery(c, "page", 1)
|
||||||
|
pageSize := h.getIntQuery(c, "page_size", 10)
|
||||||
|
|
||||||
|
// 构建筛选条件
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
|
||||||
|
// 用户ID筛选
|
||||||
|
if userID := c.Query("user_id"); userID != "" {
|
||||||
|
filters["user_id"] = userID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间范围筛选
|
||||||
|
if startTime := c.Query("start_time"); startTime != "" {
|
||||||
|
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
|
||||||
|
filters["start_time"] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if endTime := c.Query("end_time"); endTime != "" {
|
||||||
|
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
|
||||||
|
filters["end_time"] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付类型筛选
|
||||||
|
if paymentType := c.Query("payment_type"); paymentType != "" {
|
||||||
|
filters["payment_type"] = paymentType
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付渠道筛选
|
||||||
|
if payChannel := c.Query("pay_channel"); payChannel != "" {
|
||||||
|
filters["pay_channel"] = payChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态筛选
|
||||||
|
if status := c.Query("status"); status != "" {
|
||||||
|
filters["status"] = status
|
||||||
|
}
|
||||||
|
|
||||||
|
// 产品编号筛选
|
||||||
|
if productCode := c.Query("product_code"); productCode != "" {
|
||||||
|
filters["product_code"] = productCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 金额范围筛选
|
||||||
|
if minAmount := c.Query("min_amount"); minAmount != "" {
|
||||||
|
filters["min_amount"] = minAmount
|
||||||
|
}
|
||||||
|
if maxAmount := c.Query("max_amount"); maxAmount != "" {
|
||||||
|
filters["max_amount"] = maxAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建分页选项
|
||||||
|
options := interfaces.ListOptions{
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
Sort: "created_at",
|
||||||
|
Order: "desc",
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.appService.GetAdminPurchaseRecords(c.Request.Context(), filters, options)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取管理端购买记录失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, "获取购买记录失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.responseBuilder.Success(c, result, "获取管理端购买记录成功")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,6 +11,9 @@ import (
|
|||||||
"tyapi-server/internal/application/product/dto/queries"
|
"tyapi-server/internal/application/product/dto/queries"
|
||||||
"tyapi-server/internal/application/product/dto/responses"
|
"tyapi-server/internal/application/product/dto/responses"
|
||||||
"tyapi-server/internal/shared/interfaces"
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProductAdminHandler 产品管理员HTTP处理器
|
// ProductAdminHandler 产品管理员HTTP处理器
|
||||||
@@ -296,7 +297,6 @@ func (h *ProductAdminHandler) ListProducts(c *gin.Context) {
|
|||||||
// 解析查询参数
|
// 解析查询参数
|
||||||
page := h.getIntQuery(c, "page", 1)
|
page := h.getIntQuery(c, "page", 1)
|
||||||
pageSize := h.getIntQuery(c, "page_size", 10)
|
pageSize := h.getIntQuery(c, "page_size", 10)
|
||||||
|
|
||||||
// 构建筛选条件
|
// 构建筛选条件
|
||||||
filters := make(map[string]interface{})
|
filters := make(map[string]interface{})
|
||||||
|
|
||||||
|
|||||||
@@ -282,7 +282,8 @@ func (h *UIComponentHandler) DeleteUIComponent(c *gin.Context) {
|
|||||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.responseBuilder.InternalError(c, "删除UI组件失败")
|
// 提供更详细的错误信息
|
||||||
|
h.responseBuilder.InternalError(c, fmt.Sprintf("删除UI组件失败: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tyapi-server/internal/infrastructure/http/handlers"
|
||||||
|
sharedhttp "tyapi-server/internal/shared/http"
|
||||||
|
"tyapi-server/internal/shared/middleware"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ComponentReportOrderRoutes 组件报告订单路由
|
||||||
|
type ComponentReportOrderRoutes struct {
|
||||||
|
componentReportOrderHandler *handlers.ComponentReportOrderHandler
|
||||||
|
auth *middleware.JWTAuthMiddleware
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewComponentReportOrderRoutes 创建组件报告订单路由
|
||||||
|
func NewComponentReportOrderRoutes(
|
||||||
|
componentReportOrderHandler *handlers.ComponentReportOrderHandler,
|
||||||
|
auth *middleware.JWTAuthMiddleware,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) *ComponentReportOrderRoutes {
|
||||||
|
return &ComponentReportOrderRoutes{
|
||||||
|
componentReportOrderHandler: componentReportOrderHandler,
|
||||||
|
auth: auth,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register 注册组件报告订单相关路由
|
||||||
|
func (r *ComponentReportOrderRoutes) Register(router *sharedhttp.GinRouter) {
|
||||||
|
engine := router.GetEngine()
|
||||||
|
|
||||||
|
// 产品组件报告相关接口 - 需要认证
|
||||||
|
componentReportGroup := engine.Group("/api/v1/products/:id/component-report", r.auth.Handle())
|
||||||
|
{
|
||||||
|
// 检查下载可用性
|
||||||
|
componentReportGroup.GET("/check", r.componentReportOrderHandler.CheckDownloadAvailability)
|
||||||
|
// 获取下载信息
|
||||||
|
componentReportGroup.GET("/info", r.componentReportOrderHandler.GetDownloadInfo)
|
||||||
|
// 创建支付订单
|
||||||
|
componentReportGroup.POST("/create-order", r.componentReportOrderHandler.CreatePaymentOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件报告订单相关接口 - 需要认证
|
||||||
|
componentReportOrder := engine.Group("/api/v1/component-report", r.auth.Handle())
|
||||||
|
{
|
||||||
|
// 检查支付状态
|
||||||
|
componentReportOrder.GET("/check-payment/:orderId", r.componentReportOrderHandler.CheckPaymentStatus)
|
||||||
|
// 下载文件
|
||||||
|
componentReportOrder.GET("/download/:orderId", r.componentReportOrderHandler.DownloadFile)
|
||||||
|
// 获取用户订单列表
|
||||||
|
componentReportOrder.GET("/orders", r.componentReportOrderHandler.GetUserOrders)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.Info("组件报告订单路由注册完成")
|
||||||
|
}
|
||||||
@@ -69,6 +69,7 @@ func (r *FinanceRoutes) Register(router *sharedhttp.GinRouter) {
|
|||||||
walletGroup.GET("/recharge-records", r.financeHandler.GetUserRechargeRecords) // 用户充值记录分页
|
walletGroup.GET("/recharge-records", r.financeHandler.GetUserRechargeRecords) // 用户充值记录分页
|
||||||
walletGroup.GET("/alipay-order-status", r.financeHandler.GetAlipayOrderStatus) // 获取支付宝订单状态
|
walletGroup.GET("/alipay-order-status", r.financeHandler.GetAlipayOrderStatus) // 获取支付宝订单状态
|
||||||
walletGroup.GET("/wechat-order-status", r.financeHandler.GetWechatOrderStatus) // 获取微信订单状态
|
walletGroup.GET("/wechat-order-status", r.financeHandler.GetWechatOrderStatus) // 获取微信订单状态
|
||||||
|
financeGroup.GET("/purchase-records", r.financeHandler.GetUserPurchaseRecords) // 用户购买记录分页
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +92,7 @@ func (r *FinanceRoutes) Register(router *sharedhttp.GinRouter) {
|
|||||||
adminFinanceGroup.POST("/transfer-recharge", r.financeHandler.TransferRecharge) // 对公转账充值
|
adminFinanceGroup.POST("/transfer-recharge", r.financeHandler.TransferRecharge) // 对公转账充值
|
||||||
adminFinanceGroup.POST("/gift-recharge", r.financeHandler.GiftRecharge) // 赠送充值
|
adminFinanceGroup.POST("/gift-recharge", r.financeHandler.GiftRecharge) // 赠送充值
|
||||||
adminFinanceGroup.GET("/recharge-records", r.financeHandler.GetAdminRechargeRecords) // 管理员充值记录分页
|
adminFinanceGroup.GET("/recharge-records", r.financeHandler.GetAdminRechargeRecords) // 管理员充值记录分页
|
||||||
|
adminFinanceGroup.GET("/purchase-records", r.financeHandler.GetAdminPurchaseRecords) // 管理员购买记录分页
|
||||||
}
|
}
|
||||||
|
|
||||||
// 管理员发票相关路由组
|
// 管理员发票相关路由组
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ func (r *ProductAdminRoutes) Register(router *sharedhttp.GinRouter) {
|
|||||||
subscriptions.POST("/batch-update-prices", r.handler.BatchUpdateSubscriptionPrices)
|
subscriptions.POST("/batch-update-prices", r.handler.BatchUpdateSubscriptionPrices)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 消费记录管理
|
// 消费记录管理
|
||||||
walletTransactions := adminGroup.Group("/wallet-transactions")
|
walletTransactions := adminGroup.Group("/wallet-transactions")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -70,14 +70,7 @@ func (r *ProductRoutes) Register(router *sharedhttp.GinRouter) {
|
|||||||
componentReport.POST("/generate-and-download", r.componentReportHandler.GenerateAndDownloadZip)
|
componentReport.POST("/generate-and-download", r.componentReportHandler.GenerateAndDownloadZip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 产品组件报告相关接口 - 需要认证
|
// 产品组件报告相关接口 - 已迁移到 ComponentReportOrderRoutes
|
||||||
componentReportGroup := products.Group("/:id/component-report", r.auth.Handle())
|
|
||||||
{
|
|
||||||
componentReportGroup.GET("/check", r.componentReportHandler.CheckDownloadAvailability)
|
|
||||||
componentReportGroup.GET("/info", r.componentReportHandler.GetDownloadInfo)
|
|
||||||
componentReportGroup.POST("/create-order", r.componentReportHandler.CreatePaymentOrder)
|
|
||||||
componentReportGroup.GET("/check-payment/:orderId", r.componentReportHandler.CheckPaymentStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分类 - 公开接口
|
// 分类 - 公开接口
|
||||||
categories := engine.Group("/api/v1/categories")
|
categories := engine.Group("/api/v1/categories")
|
||||||
|
|||||||
@@ -1,34 +1,42 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"tyapi-server/internal/infrastructure/http/handlers"
|
"tyapi-server/internal/infrastructure/http/handlers"
|
||||||
"tyapi-server/internal/shared/interfaces"
|
sharedhttp "tyapi-server/internal/shared/http"
|
||||||
|
"tyapi-server/internal/shared/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UIComponentRoutes UI组件路由
|
// UIComponentRoutes UI组件路由
|
||||||
type UIComponentRoutes struct {
|
type UIComponentRoutes struct {
|
||||||
uiComponentHandler *handlers.UIComponentHandler
|
uiComponentHandler *handlers.UIComponentHandler
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
auth *middleware.JWTAuthMiddleware
|
||||||
|
admin *middleware.AdminAuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUIComponentRoutes 创建UI组件路由
|
// NewUIComponentRoutes 创建UI组件路由
|
||||||
func NewUIComponentRoutes(
|
func NewUIComponentRoutes(
|
||||||
uiComponentHandler *handlers.UIComponentHandler,
|
uiComponentHandler *handlers.UIComponentHandler,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
|
auth *middleware.JWTAuthMiddleware,
|
||||||
|
admin *middleware.AdminAuthMiddleware,
|
||||||
) *UIComponentRoutes {
|
) *UIComponentRoutes {
|
||||||
return &UIComponentRoutes{
|
return &UIComponentRoutes{
|
||||||
uiComponentHandler: uiComponentHandler,
|
uiComponentHandler: uiComponentHandler,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
auth: auth,
|
||||||
|
admin: admin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册UI组件路由
|
// RegisterRoutes 注册UI组件路由
|
||||||
func (r *UIComponentRoutes) RegisterRoutes(router *gin.RouterGroup, authMiddleware interfaces.Middleware) {
|
func (r *UIComponentRoutes) Register(router *sharedhttp.GinRouter) {
|
||||||
uiComponentGroup := router.Group("/ui-components")
|
// 管理员路由组
|
||||||
uiComponentGroup.Use(authMiddleware.Handle())
|
engine := router.GetEngine()
|
||||||
|
uiComponentGroup := engine.Group("/api/v1/admin/ui-components")
|
||||||
|
uiComponentGroup.Use(r.admin.Handle()) // 管理员权限验证
|
||||||
{
|
{
|
||||||
// UI组件管理
|
// UI组件管理
|
||||||
uiComponentGroup.POST("", r.uiComponentHandler.CreateUIComponent) // 创建UI组件
|
uiComponentGroup.POST("", r.uiComponentHandler.CreateUIComponent) // 创建UI组件
|
||||||
|
|||||||
@@ -193,6 +193,13 @@ componentReportHandler := component_report.NewComponentReportHandler(
|
|||||||
productRepo,
|
productRepo,
|
||||||
docRepo,
|
docRepo,
|
||||||
apiConfigRepo,
|
apiConfigRepo,
|
||||||
|
componentReportRepo,
|
||||||
|
purchaseOrderRepo,
|
||||||
|
rechargeRecordRepo,
|
||||||
|
alipayOrderRepo,
|
||||||
|
wechatOrderRepo,
|
||||||
|
aliPayService,
|
||||||
|
wechatPayService,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
137
internal/shared/component_report/cache_manager.go
Normal file
137
internal/shared/component_report/cache_manager.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package component_report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CacheManager 缓存管理器
|
||||||
|
type CacheManager struct {
|
||||||
|
cacheDir string
|
||||||
|
ttl time.Duration
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCacheManager 创建缓存管理器
|
||||||
|
func NewCacheManager(cacheDir string, ttl time.Duration, logger *zap.Logger) *CacheManager {
|
||||||
|
return &CacheManager{
|
||||||
|
cacheDir: cacheDir,
|
||||||
|
ttl: ttl,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanExpiredCache 清理过期缓存
|
||||||
|
func (cm *CacheManager) CleanExpiredCache() error {
|
||||||
|
// 确保缓存目录存在
|
||||||
|
if _, err := os.Stat(cm.cacheDir); os.IsNotExist(err) {
|
||||||
|
return nil // 目录不存在,无需清理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历缓存目录
|
||||||
|
err := filepath.Walk(cm.cacheDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过目录
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件是否过期
|
||||||
|
if time.Since(info.ModTime()) > cm.ttl {
|
||||||
|
// cm.logger.Debug("删除过期缓存文件",
|
||||||
|
// zap.String("path", path),
|
||||||
|
// zap.Time("mod_time", info.ModTime()),
|
||||||
|
// zap.Duration("age", time.Since(info.ModTime())))
|
||||||
|
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
cm.logger.Error("删除过期缓存文件失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("path", path))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("清理过期缓存失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cm.logger.Info("缓存清理完成", zap.String("cache_dir", cm.cacheDir))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCacheSize 获取缓存总大小
|
||||||
|
func (cm *CacheManager) GetCacheSize() (int64, error) {
|
||||||
|
var totalSize int64
|
||||||
|
|
||||||
|
err := filepath.Walk(cm.cacheDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
totalSize += info.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("计算缓存大小失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalSize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCacheCount 获取缓存文件数量
|
||||||
|
func (cm *CacheManager) GetCacheCount() (int, error) {
|
||||||
|
var count int
|
||||||
|
|
||||||
|
err := filepath.Walk(cm.cacheDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("统计缓存文件数量失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAllCache 清理所有缓存
|
||||||
|
func (cm *CacheManager) ClearAllCache() error {
|
||||||
|
// 确保缓存目录存在
|
||||||
|
if _, err := os.Stat(cm.cacheDir); os.IsNotExist(err) {
|
||||||
|
return nil // 目录不存在,无需清理
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.RemoveAll(cm.cacheDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("清理所有缓存失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新创建目录
|
||||||
|
if err := os.MkdirAll(cm.cacheDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("重新创建缓存目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cm.logger.Info("所有缓存已清理", zap.String("cache_dir", cm.cacheDir))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
102
internal/shared/component_report/check_payment_status_fix.go
Normal file
102
internal/shared/component_report/check_payment_status_fix.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package component_report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
finance_entities "tyapi-server/internal/domains/finance/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckPaymentStatusFixed 修复版检查支付状态方法
|
||||||
|
func (h *ComponentReportHandler) CheckPaymentStatusFixed(c *gin.Context) {
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
if userID == "" {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"message": "用户未登录",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderID := c.Param("orderId")
|
||||||
|
if orderID == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "订单ID不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据订单ID查询下载记录
|
||||||
|
download, err := h.componentReportRepo.GetDownloadByID(c.Request.Context(), orderID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("查询下载记录失败", zap.Error(err), zap.String("order_id", orderID))
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"code": 404,
|
||||||
|
"message": "订单不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证订单是否属于当前用户
|
||||||
|
if download.UserID != userID {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"code": 403,
|
||||||
|
"message": "无权访问此订单",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用购买订单状态来判断支付状态
|
||||||
|
var paymentStatus string
|
||||||
|
var canDownload bool
|
||||||
|
|
||||||
|
// 优先使用OrderID查询购买订单状态
|
||||||
|
if download.OrderID != nil {
|
||||||
|
// 查询购买订单状态
|
||||||
|
purchaseOrder, err := h.purchaseOrderRepo.GetByID(c.Request.Context(), *download.OrderID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("查询购买订单失败", zap.Error(err), zap.String("order_id", *download.OrderID))
|
||||||
|
paymentStatus = "unknown"
|
||||||
|
} else {
|
||||||
|
// 根据购买订单状态设置支付状态
|
||||||
|
switch purchaseOrder.Status {
|
||||||
|
case finance_entities.PurchaseOrderStatusPaid:
|
||||||
|
paymentStatus = "success"
|
||||||
|
canDownload = true
|
||||||
|
case finance_entities.PurchaseOrderStatusCreated:
|
||||||
|
paymentStatus = "pending"
|
||||||
|
canDownload = false
|
||||||
|
case finance_entities.PurchaseOrderStatusCancelled:
|
||||||
|
paymentStatus = "cancelled"
|
||||||
|
canDownload = false
|
||||||
|
case finance_entities.PurchaseOrderStatusFailed:
|
||||||
|
paymentStatus = "failed"
|
||||||
|
canDownload = false
|
||||||
|
default:
|
||||||
|
paymentStatus = "unknown"
|
||||||
|
canDownload = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if download.OrderNumber != nil {
|
||||||
|
// 兼容旧的支付订单逻辑
|
||||||
|
paymentStatus = "success" // 简化处理,有支付订单号就认为已支付
|
||||||
|
canDownload = true
|
||||||
|
} else {
|
||||||
|
paymentStatus = "pending"
|
||||||
|
canDownload = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否过期
|
||||||
|
if download.IsExpired() {
|
||||||
|
canDownload = false
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, CheckPaymentStatusResponse{
|
||||||
|
OrderID: download.ID,
|
||||||
|
PaymentStatus: paymentStatus,
|
||||||
|
CanDownload: canDownload,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,12 +2,15 @@ package component_report
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -21,6 +24,10 @@ type ExampleJSONGenerator struct {
|
|||||||
docRepo repositories.ProductDocumentationRepository
|
docRepo repositories.ProductDocumentationRepository
|
||||||
apiConfigRepo repositories.ProductApiConfigRepository
|
apiConfigRepo repositories.ProductApiConfigRepository
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
// 缓存配置
|
||||||
|
CacheEnabled bool
|
||||||
|
CacheDir string
|
||||||
|
CacheTTL time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExampleJSONGenerator 创建示例JSON生成器
|
// NewExampleJSONGenerator 创建示例JSON生成器
|
||||||
@@ -35,6 +42,30 @@ func NewExampleJSONGenerator(
|
|||||||
docRepo: docRepo,
|
docRepo: docRepo,
|
||||||
apiConfigRepo: apiConfigRepo,
|
apiConfigRepo: apiConfigRepo,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
CacheEnabled: true,
|
||||||
|
CacheDir: "storage/component-reports/cache",
|
||||||
|
CacheTTL: 24 * time.Hour, // 默认缓存24小时
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExampleJSONGeneratorWithCache 创建带有自定义缓存配置的示例JSON生成器
|
||||||
|
func NewExampleJSONGeneratorWithCache(
|
||||||
|
productRepo repositories.ProductRepository,
|
||||||
|
docRepo repositories.ProductDocumentationRepository,
|
||||||
|
apiConfigRepo repositories.ProductApiConfigRepository,
|
||||||
|
logger *zap.Logger,
|
||||||
|
cacheEnabled bool,
|
||||||
|
cacheDir string,
|
||||||
|
cacheTTL time.Duration,
|
||||||
|
) *ExampleJSONGenerator {
|
||||||
|
return &ExampleJSONGenerator{
|
||||||
|
productRepo: productRepo,
|
||||||
|
docRepo: docRepo,
|
||||||
|
apiConfigRepo: apiConfigRepo,
|
||||||
|
logger: logger,
|
||||||
|
CacheEnabled: cacheEnabled,
|
||||||
|
CacheDir: cacheDir,
|
||||||
|
CacheTTL: cacheTTL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +85,20 @@ type ExampleJSONItem struct {
|
|||||||
// productID: 产品ID(可以是组合包或单品)
|
// productID: 产品ID(可以是组合包或单品)
|
||||||
// subProductCodes: 子产品编号列表(如果为空,则处理所有子产品)
|
// subProductCodes: 子产品编号列表(如果为空,则处理所有子产品)
|
||||||
func (g *ExampleJSONGenerator) GenerateExampleJSON(ctx context.Context, productID string, subProductCodes []string) ([]byte, error) {
|
func (g *ExampleJSONGenerator) GenerateExampleJSON(ctx context.Context, productID string, subProductCodes []string) ([]byte, error) {
|
||||||
|
// 生成缓存键
|
||||||
|
cacheKey := g.generateCacheKey(productID, subProductCodes)
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
if g.CacheEnabled {
|
||||||
|
cachedData, err := g.getCachedData(cacheKey)
|
||||||
|
if err == nil && cachedData != nil {
|
||||||
|
// g.logger.Debug("使用缓存的example.json数据",
|
||||||
|
// zap.String("product_id", productID),
|
||||||
|
// zap.String("cache_key", cacheKey))
|
||||||
|
return cachedData, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. 获取产品信息
|
// 1. 获取产品信息
|
||||||
product, err := g.productRepo.GetByID(ctx, productID)
|
product, err := g.productRepo.GetByID(ctx, productID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -157,12 +202,21 @@ func (g *ExampleJSONGenerator) GenerateExampleJSON(ctx context.Context, productI
|
|||||||
return nil, fmt.Errorf("序列化example.json失败: %w", err)
|
return nil, fmt.Errorf("序列化example.json失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 缓存数据
|
||||||
|
if g.CacheEnabled {
|
||||||
|
if err := g.cacheData(cacheKey, jsonData); err != nil {
|
||||||
|
g.logger.Warn("缓存example.json数据失败", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
g.logger.Debug("example.json数据已缓存", zap.String("cache_key", cacheKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return jsonData, nil
|
return jsonData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchProductCodeToPath 根据产品编码匹配 UI 组件路径,返回路径和类型(folder/file)
|
// MatchSubProductCodeToPath 根据子产品编码匹配 UI 组件路径,返回路径和类型(folder/file)
|
||||||
func (g *ExampleJSONGenerator) MatchProductCodeToPath(ctx context.Context, productCode string) (string, string, error) {
|
func (g *ExampleJSONGenerator) MatchSubProductCodeToPath(ctx context.Context, subProductCode string) (string, string, error) {
|
||||||
basePath := filepath.Join("resources", "Pure Component", "src", "ui")
|
basePath := filepath.Join("resources", "Pure_Component", "src", "ui")
|
||||||
|
|
||||||
entries, err := os.ReadDir(basePath)
|
entries, err := os.ReadDir(basePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -172,18 +226,8 @@ func (g *ExampleJSONGenerator) MatchProductCodeToPath(ctx context.Context, produ
|
|||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
name := entry.Name()
|
name := entry.Name()
|
||||||
|
|
||||||
// 精确匹配
|
// 使用改进的相似性匹配算法
|
||||||
if name == productCode {
|
if isSimilarCode(subProductCode, name) {
|
||||||
path := filepath.Join(basePath, name)
|
|
||||||
fileType := "folder"
|
|
||||||
if !entry.IsDir() {
|
|
||||||
fileType = "file"
|
|
||||||
}
|
|
||||||
return path, fileType, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模糊匹配:文件夹名称包含产品编号,或产品编号包含文件夹名称的核心部分
|
|
||||||
if strings.Contains(name, productCode) || strings.Contains(productCode, extractCoreCode(name)) {
|
|
||||||
path := filepath.Join(basePath, name)
|
path := filepath.Join(basePath, name)
|
||||||
fileType := "folder"
|
fileType := "folder"
|
||||||
if !entry.IsDir() {
|
if !entry.IsDir() {
|
||||||
@@ -193,7 +237,7 @@ func (g *ExampleJSONGenerator) MatchProductCodeToPath(ctx context.Context, produ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", "", fmt.Errorf("未找到匹配的组件文件: %s", productCode)
|
return "", "", fmt.Errorf("未找到匹配的组件文件: %s", subProductCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractCoreCode 提取文件名中的核心编码部分
|
// extractCoreCode 提取文件名中的核心编码部分
|
||||||
@@ -206,6 +250,44 @@ func extractCoreCode(name string) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractMainCode 从子产品编码或文件夹名称中提取主要编码部分
|
||||||
|
// 处理可能的格式差异,如前缀、后缀等
|
||||||
|
func extractMainCode(code string) string {
|
||||||
|
// 移除常见的前缀,如 C
|
||||||
|
if len(code) > 0 && code[0] == 'C' {
|
||||||
|
return code[1:]
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSimilarCode 判断两个编码是否相似,考虑多种可能的格式差异
|
||||||
|
func isSimilarCode(code1, code2 string) bool {
|
||||||
|
// 直接相等
|
||||||
|
if code1 == code2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除常见前缀后比较
|
||||||
|
mainCode1 := extractMainCode(code1)
|
||||||
|
mainCode2 := extractMainCode(code2)
|
||||||
|
if mainCode1 == mainCode2 || mainCode1 == code2 || code1 == mainCode2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包含关系
|
||||||
|
if strings.Contains(code1, code2) || strings.Contains(code2, code1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除前缀后的包含关系
|
||||||
|
if strings.Contains(mainCode1, code2) || strings.Contains(code2, mainCode1) ||
|
||||||
|
strings.Contains(code1, mainCode2) || strings.Contains(mainCode2, code1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// extractResponseExample 提取产品响应示例数据(优先级:文档 > API配置 > 默认值)
|
// extractResponseExample 提取产品响应示例数据(优先级:文档 > API配置 > 默认值)
|
||||||
func (g *ExampleJSONGenerator) extractResponseExample(ctx context.Context, product *entities.Product) interface{} {
|
func (g *ExampleJSONGenerator) extractResponseExample(ctx context.Context, product *entities.Product) interface{} {
|
||||||
var responseData interface{}
|
var responseData interface{}
|
||||||
@@ -216,20 +298,20 @@ func (g *ExampleJSONGenerator) extractResponseExample(ctx context.Context, produ
|
|||||||
// 尝试直接解析为JSON
|
// 尝试直接解析为JSON
|
||||||
err := json.Unmarshal([]byte(doc.ResponseExample), &responseData)
|
err := json.Unmarshal([]byte(doc.ResponseExample), &responseData)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
g.logger.Debug("从产品文档中提取响应示例成功",
|
// g.logger.Debug("从产品文档中提取响应示例成功",
|
||||||
zap.String("product_id", product.ID),
|
// zap.String("product_id", product.ID),
|
||||||
zap.String("product_code", product.Code),
|
// zap.String("product_code", product.Code),
|
||||||
)
|
// )
|
||||||
return responseData
|
return responseData
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果解析失败,尝试从Markdown代码块中提取JSON
|
// 如果解析失败,尝试从Markdown代码块中提取JSON
|
||||||
extractedData := extractJSONFromMarkdown(doc.ResponseExample)
|
extractedData := extractJSONFromMarkdown(doc.ResponseExample)
|
||||||
if extractedData != nil {
|
if extractedData != nil {
|
||||||
g.logger.Debug("从Markdown代码块中提取响应示例成功",
|
// g.logger.Debug("从Markdown代码块中提取响应示例成功",
|
||||||
zap.String("product_id", product.ID),
|
// zap.String("product_id", product.ID),
|
||||||
zap.String("product_code", product.Code),
|
// zap.String("product_code", product.Code),
|
||||||
)
|
// )
|
||||||
return extractedData
|
return extractedData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,10 +322,10 @@ func (g *ExampleJSONGenerator) extractResponseExample(ctx context.Context, produ
|
|||||||
// API配置的响应示例通常是 JSON 字符串
|
// API配置的响应示例通常是 JSON 字符串
|
||||||
err := json.Unmarshal([]byte(apiConfig.ResponseExample), &responseData)
|
err := json.Unmarshal([]byte(apiConfig.ResponseExample), &responseData)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
g.logger.Debug("从产品API配置中提取响应示例成功",
|
// g.logger.Debug("从产品API配置中提取响应示例成功",
|
||||||
zap.String("product_id", product.ID),
|
// zap.String("product_id", product.ID),
|
||||||
zap.String("product_code", product.Code),
|
// zap.String("product_code", product.Code),
|
||||||
)
|
// )
|
||||||
return responseData
|
return responseData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,3 +366,57 @@ func extractJSONFromMarkdown(markdown string) interface{} {
|
|||||||
// 如果提取失败,返回 nil(由调用者决定默认值)
|
// 如果提取失败,返回 nil(由调用者决定默认值)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateCacheKey 生成缓存键
|
||||||
|
func (g *ExampleJSONGenerator) generateCacheKey(productID string, subProductCodes []string) string {
|
||||||
|
// 使用产品ID和子产品编码列表生成MD5哈希
|
||||||
|
data := productID
|
||||||
|
for _, code := range subProductCodes {
|
||||||
|
data += "|" + code
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := md5.Sum([]byte(data))
|
||||||
|
return hex.EncodeToString(hash[:]) + ".json"
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCachedData 获取缓存数据
|
||||||
|
func (g *ExampleJSONGenerator) getCachedData(cacheKey string) ([]byte, error) {
|
||||||
|
// 确保缓存目录存在
|
||||||
|
if err := os.MkdirAll(g.CacheDir, 0755); err != nil {
|
||||||
|
return nil, fmt.Errorf("创建缓存目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFilePath := filepath.Join(g.CacheDir, cacheKey)
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
fileInfo, err := os.Stat(cacheFilePath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil // 文件不存在,但不是错误
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件是否过期
|
||||||
|
if time.Since(fileInfo.ModTime()) > g.CacheTTL {
|
||||||
|
// 文件过期,删除
|
||||||
|
os.Remove(cacheFilePath)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取文件内容
|
||||||
|
return os.ReadFile(cacheFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheData 缓存数据
|
||||||
|
func (g *ExampleJSONGenerator) cacheData(cacheKey string, data []byte) error {
|
||||||
|
// 确保缓存目录存在
|
||||||
|
if err := os.MkdirAll(g.CacheDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("创建缓存目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFilePath := filepath.Join(g.CacheDir, cacheKey)
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
return os.WriteFile(cacheFilePath, data, 0644)
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
172
internal/shared/component_report/handler_fixed.go
Normal file
172
internal/shared/component_report/handler_fixed.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package component_report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
finance_entities "tyapi-server/internal/domains/finance/entities"
|
||||||
|
financeRepositories "tyapi-server/internal/domains/finance/repositories"
|
||||||
|
"tyapi-server/internal/domains/product/repositories"
|
||||||
|
"tyapi-server/internal/shared/payment"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ComponentReportHandler 组件报告处理器
|
||||||
|
type ComponentReportHandlerFixed struct {
|
||||||
|
exampleJSONGenerator *ExampleJSONGenerator
|
||||||
|
zipGenerator *ZipGenerator
|
||||||
|
productRepo repositories.ProductRepository
|
||||||
|
componentReportRepo repositories.ComponentReportRepository
|
||||||
|
purchaseOrderRepo financeRepositories.PurchaseOrderRepository
|
||||||
|
rechargeRecordRepo interface {
|
||||||
|
Create(ctx context.Context, record finance_entities.RechargeRecord) (finance_entities.RechargeRecord, error)
|
||||||
|
}
|
||||||
|
alipayOrderRepo interface {
|
||||||
|
Create(ctx context.Context, order finance_entities.AlipayOrder) (finance_entities.AlipayOrder, error)
|
||||||
|
GetByOutTradeNo(ctx context.Context, outTradeNo string) (*finance_entities.AlipayOrder, error)
|
||||||
|
Update(ctx context.Context, order finance_entities.AlipayOrder) error
|
||||||
|
}
|
||||||
|
wechatOrderRepo interface {
|
||||||
|
Create(ctx context.Context, order finance_entities.WechatOrder) (finance_entities.WechatOrder, error)
|
||||||
|
GetByOutTradeNo(ctx context.Context, outTradeNo string) (*finance_entities.WechatOrder, error)
|
||||||
|
Update(ctx context.Context, order finance_entities.WechatOrder) error
|
||||||
|
}
|
||||||
|
aliPayService *payment.AliPayService
|
||||||
|
wechatPayService *payment.WechatPayService
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewComponentReportHandlerFixed 创建组件报告处理器(修复版)
|
||||||
|
func NewComponentReportHandlerFixed(
|
||||||
|
productRepo repositories.ProductRepository,
|
||||||
|
docRepo repositories.ProductDocumentationRepository,
|
||||||
|
apiConfigRepo repositories.ProductApiConfigRepository,
|
||||||
|
componentReportRepo repositories.ComponentReportRepository,
|
||||||
|
purchaseOrderRepo financeRepositories.PurchaseOrderRepository,
|
||||||
|
rechargeRecordRepo interface {
|
||||||
|
Create(ctx context.Context, record finance_entities.RechargeRecord) (finance_entities.RechargeRecord, error)
|
||||||
|
},
|
||||||
|
alipayOrderRepo interface {
|
||||||
|
Create(ctx context.Context, order finance_entities.AlipayOrder) (finance_entities.AlipayOrder, error)
|
||||||
|
GetByOutTradeNo(ctx context.Context, outTradeNo string) (*finance_entities.AlipayOrder, error)
|
||||||
|
Update(ctx context.Context, order finance_entities.AlipayOrder) error
|
||||||
|
},
|
||||||
|
wechatOrderRepo interface {
|
||||||
|
Create(ctx context.Context, order finance_entities.WechatOrder) (finance_entities.WechatOrder, error)
|
||||||
|
GetByOutTradeNo(ctx context.Context, outTradeNo string) (*finance_entities.WechatOrder, error)
|
||||||
|
Update(ctx context.Context, order finance_entities.WechatOrder) error
|
||||||
|
},
|
||||||
|
aliPayService *payment.AliPayService,
|
||||||
|
wechatPayService *payment.WechatPayService,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) *ComponentReportHandlerFixed {
|
||||||
|
exampleJSONGenerator := NewExampleJSONGenerator(productRepo, docRepo, apiConfigRepo, logger)
|
||||||
|
zipGenerator := NewZipGenerator(logger)
|
||||||
|
|
||||||
|
return &ComponentReportHandlerFixed{
|
||||||
|
exampleJSONGenerator: exampleJSONGenerator,
|
||||||
|
zipGenerator: zipGenerator,
|
||||||
|
productRepo: productRepo,
|
||||||
|
componentReportRepo: componentReportRepo,
|
||||||
|
purchaseOrderRepo: purchaseOrderRepo,
|
||||||
|
rechargeRecordRepo: rechargeRecordRepo,
|
||||||
|
alipayOrderRepo: alipayOrderRepo,
|
||||||
|
wechatOrderRepo: wechatOrderRepo,
|
||||||
|
aliPayService: aliPayService,
|
||||||
|
wechatPayService: wechatPayService,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPaymentStatusFixed 检查支付状态(修复版)
|
||||||
|
func (h *ComponentReportHandlerFixed) CheckPaymentStatusFixed(c *gin.Context) {
|
||||||
|
userID := c.GetString("user_id")
|
||||||
|
if userID == "" {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"message": "用户未登录",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderID := c.Param("orderId")
|
||||||
|
if orderID == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "订单ID不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据订单ID查询下载记录
|
||||||
|
download, err := h.componentReportRepo.GetDownloadByID(c.Request.Context(), orderID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("查询下载记录失败", zap.Error(err), zap.String("order_id", orderID))
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"code": 404,
|
||||||
|
"message": "订单不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证订单是否属于当前用户
|
||||||
|
if download.UserID != userID {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"code": 403,
|
||||||
|
"message": "无权访问此订单",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用购买订单状态来判断支付状态
|
||||||
|
var paymentStatus string
|
||||||
|
var canDownload bool
|
||||||
|
|
||||||
|
if download.OrderID != nil {
|
||||||
|
// 查询购买订单状态
|
||||||
|
purchaseOrder, err := h.purchaseOrderRepo.GetByID(c.Request.Context(), *download.OrderID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("查询购买订单失败", zap.Error(err), zap.String("OrderID", *download.OrderID))
|
||||||
|
paymentStatus = "unknown"
|
||||||
|
} else {
|
||||||
|
// 根据购买订单状态设置支付状态
|
||||||
|
switch purchaseOrder.Status {
|
||||||
|
case finance_entities.PurchaseOrderStatusPaid:
|
||||||
|
paymentStatus = "success"
|
||||||
|
canDownload = true
|
||||||
|
case finance_entities.PurchaseOrderStatusCreated:
|
||||||
|
paymentStatus = "pending"
|
||||||
|
canDownload = false
|
||||||
|
case finance_entities.PurchaseOrderStatusCancelled:
|
||||||
|
paymentStatus = "cancelled"
|
||||||
|
canDownload = false
|
||||||
|
case finance_entities.PurchaseOrderStatusFailed:
|
||||||
|
paymentStatus = "failed"
|
||||||
|
canDownload = false
|
||||||
|
default:
|
||||||
|
paymentStatus = "unknown"
|
||||||
|
canDownload = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if download.OrderNumber != nil {
|
||||||
|
// 兼容旧的支付订单逻辑
|
||||||
|
paymentStatus = "success" // 简化处理,有支付订单号就认为已支付
|
||||||
|
canDownload = true
|
||||||
|
} else {
|
||||||
|
paymentStatus = "pending"
|
||||||
|
canDownload = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否过期
|
||||||
|
if download.IsExpired() {
|
||||||
|
canDownload = false
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, CheckPaymentStatusResponse{
|
||||||
|
OrderID: download.ID,
|
||||||
|
PaymentStatus: paymentStatus,
|
||||||
|
CanDownload: canDownload,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -3,11 +3,14 @@ package component_report
|
|||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -15,18 +18,35 @@ import (
|
|||||||
// ZipGenerator ZIP文件生成器
|
// ZipGenerator ZIP文件生成器
|
||||||
type ZipGenerator struct {
|
type ZipGenerator struct {
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
// 缓存配置
|
||||||
|
CacheEnabled bool
|
||||||
|
CacheDir string
|
||||||
|
CacheTTL time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewZipGenerator 创建ZIP文件生成器
|
// NewZipGenerator 创建ZIP文件生成器
|
||||||
func NewZipGenerator(logger *zap.Logger) *ZipGenerator {
|
func NewZipGenerator(logger *zap.Logger) *ZipGenerator {
|
||||||
return &ZipGenerator{
|
return &ZipGenerator{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
CacheEnabled: true,
|
||||||
|
CacheDir: "storage/component-reports/cache",
|
||||||
|
CacheTTL: 24 * time.Hour, // 默认缓存24小时
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateZipFile 生成ZIP文件,包含 example.json 和匹配的组件文件
|
// NewZipGeneratorWithCache 创建带有自定义缓存配置的ZIP文件生成器
|
||||||
|
func NewZipGeneratorWithCache(logger *zap.Logger, cacheEnabled bool, cacheDir string, cacheTTL time.Duration) *ZipGenerator {
|
||||||
|
return &ZipGenerator{
|
||||||
|
logger: logger,
|
||||||
|
CacheEnabled: cacheEnabled,
|
||||||
|
CacheDir: cacheDir,
|
||||||
|
CacheTTL: cacheTTL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateZipFile 生成ZIP文件,包含 example.json 和根据子产品编码匹配的UI组件文件
|
||||||
// productID: 产品ID
|
// productID: 产品ID
|
||||||
// subProductCodes: 子产品编号列表(如果为空,则处理所有子产品)
|
// subProductCodes: 子产品编码列表(用于过滤和下载匹配的UI组件)
|
||||||
// exampleJSONGenerator: 示例JSON生成器
|
// exampleJSONGenerator: 示例JSON生成器
|
||||||
// outputPath: 输出ZIP文件路径(如果为空,则使用默认路径)
|
// outputPath: 输出ZIP文件路径(如果为空,则使用默认路径)
|
||||||
func (g *ZipGenerator) GenerateZipFile(
|
func (g *ZipGenerator) GenerateZipFile(
|
||||||
@@ -36,6 +56,29 @@ func (g *ZipGenerator) GenerateZipFile(
|
|||||||
exampleJSONGenerator *ExampleJSONGenerator,
|
exampleJSONGenerator *ExampleJSONGenerator,
|
||||||
outputPath string,
|
outputPath string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
// 生成缓存键
|
||||||
|
cacheKey := g.generateCacheKey(productID, subProductCodes)
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
if g.CacheEnabled {
|
||||||
|
cachedPath, err := g.getCachedFile(cacheKey)
|
||||||
|
if err == nil && cachedPath != "" {
|
||||||
|
// g.logger.Debug("使用缓存的ZIP文件",
|
||||||
|
// zap.String("product_id", productID),
|
||||||
|
// zap.String("cache_path", cachedPath))
|
||||||
|
|
||||||
|
// 如果指定了输出路径,复制缓存文件到目标位置
|
||||||
|
if outputPath != "" && outputPath != cachedPath {
|
||||||
|
if err := g.copyFile(cachedPath, outputPath); err != nil {
|
||||||
|
g.logger.Error("复制缓存文件失败", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
return outputPath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cachedPath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. 生成 example.json 内容
|
// 1. 生成 example.json 内容
|
||||||
exampleJSON, err := exampleJSONGenerator.GenerateExampleJSON(ctx, productID, subProductCodes)
|
exampleJSON, err := exampleJSONGenerator.GenerateExampleJSON(ctx, productID, subProductCodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -62,8 +105,8 @@ func (g *ZipGenerator) GenerateZipFile(
|
|||||||
zipWriter := zip.NewWriter(zipFile)
|
zipWriter := zip.NewWriter(zipFile)
|
||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
|
|
||||||
// 4. 添加 example.json 到 public 目录
|
// 4. 将生成的内容添加到 Pure_Component/public 目录下的 example.json
|
||||||
exampleWriter, err := zipWriter.Create("public/example.json")
|
exampleWriter, err := zipWriter.Create("Pure_Component/public/example.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("创建example.json文件失败: %w", err)
|
return "", fmt.Errorf("创建example.json文件失败: %w", err)
|
||||||
}
|
}
|
||||||
@@ -73,14 +116,14 @@ func (g *ZipGenerator) GenerateZipFile(
|
|||||||
return "", fmt.Errorf("写入example.json失败: %w", err)
|
return "", fmt.Errorf("写入example.json失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 添加整个 src 目录,但过滤 ui 目录下的文件
|
// 5. 添加整个 Pure_Component 目录,但只包含子产品编码匹配的UI组件文件
|
||||||
srcBasePath := filepath.Join("resources", "Pure Component", "src")
|
srcBasePath := filepath.Join("resources", "Pure_Component")
|
||||||
uiBasePath := filepath.Join(srcBasePath, "ui")
|
uiBasePath := filepath.Join(srcBasePath, "src", "ui")
|
||||||
|
|
||||||
// 收集所有匹配的组件名称(文件夹名或文件名)
|
// 根据子产品编码收集所有匹配的组件名称(文件夹名或文件名)
|
||||||
matchedNames := make(map[string]bool)
|
matchedNames := make(map[string]bool)
|
||||||
for _, productCode := range subProductCodes {
|
for _, subProductCode := range subProductCodes {
|
||||||
path, _, err := exampleJSONGenerator.MatchProductCodeToPath(ctx, productCode)
|
path, _, err := exampleJSONGenerator.MatchSubProductCodeToPath(ctx, subProductCode)
|
||||||
if err == nil && path != "" {
|
if err == nil && path != "" {
|
||||||
// 获取组件名称(文件夹名或文件名)
|
// 获取组件名称(文件夹名或文件名)
|
||||||
componentName := filepath.Base(path)
|
componentName := filepath.Base(path)
|
||||||
@@ -88,20 +131,20 @@ func (g *ZipGenerator) GenerateZipFile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 遍历整个 src 目录
|
// 遍历整个 Pure_Component 目录
|
||||||
err = filepath.Walk(srcBasePath, func(path string, info os.FileInfo, err error) error {
|
err = filepath.Walk(srcBasePath, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算相对于 src 的路径
|
// 计算相对于 Pure_Component 的路径
|
||||||
relPath, err := filepath.Rel(srcBasePath, path)
|
relPath, err := filepath.Rel(srcBasePath, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为ZIP路径格式
|
// 转换为ZIP路径格式,保持在Pure_Component目录下
|
||||||
zipPath := filepath.ToSlash(filepath.Join("src", relPath))
|
zipPath := filepath.ToSlash(filepath.Join("Pure_Component", relPath))
|
||||||
|
|
||||||
// 检查是否在 ui 目录下
|
// 检查是否在 ui 目录下
|
||||||
uiRelPath, err := filepath.Rel(uiBasePath, path)
|
uiRelPath, err := filepath.Rel(uiBasePath, path)
|
||||||
@@ -120,26 +163,19 @@ func (g *ZipGenerator) GenerateZipFile(
|
|||||||
// 获取文件/文件夹名称
|
// 获取文件/文件夹名称
|
||||||
fileName := info.Name()
|
fileName := info.Name()
|
||||||
|
|
||||||
// 检查是否应该保留:
|
// 检查是否应该保留:匹配到的组件文件夹/文件
|
||||||
// 1. CBehaviorRiskScan.vue 文件(无论在哪里)
|
|
||||||
// 2. 匹配到的组件文件夹/文件
|
|
||||||
shouldInclude := false
|
shouldInclude := false
|
||||||
|
|
||||||
// 检查是否是 CBehaviorRiskScan.vue
|
// 检查是否是匹配的组件(检查组件名称)
|
||||||
if fileName == "CBehaviorRiskScan.vue" {
|
if matchedNames[fileName] {
|
||||||
shouldInclude = true
|
shouldInclude = true
|
||||||
} else {
|
} else {
|
||||||
// 检查是否是匹配的组件(检查组件名称)
|
// 检查是否在匹配的组件文件夹内
|
||||||
if matchedNames[fileName] {
|
// 获取相对于 ui 的路径的第一部分(组件文件夹名)
|
||||||
shouldInclude = true
|
parts := strings.Split(filepath.ToSlash(uiRelPath), "/")
|
||||||
} else {
|
if len(parts) > 0 && parts[0] != "" && parts[0] != "." {
|
||||||
// 检查是否在匹配的组件文件夹内
|
if matchedNames[parts[0]] {
|
||||||
// 获取相对于 ui 的路径的第一部分(组件文件夹名)
|
shouldInclude = true
|
||||||
parts := strings.Split(filepath.ToSlash(uiRelPath), "/")
|
|
||||||
if len(parts) > 0 && parts[0] != "" && parts[0] != "." {
|
|
||||||
if matchedNames[parts[0]] {
|
|
||||||
shouldInclude = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +200,7 @@ func (g *ZipGenerator) GenerateZipFile(
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Warn("添加src目录失败", zap.Error(err))
|
g.logger.Warn("添加Pure_Component目录失败", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
g.logger.Info("成功生成ZIP文件",
|
g.logger.Info("成功生成ZIP文件",
|
||||||
@@ -174,6 +210,15 @@ func (g *ZipGenerator) GenerateZipFile(
|
|||||||
zap.Int("sub_product_count", len(subProductCodes)),
|
zap.Int("sub_product_count", len(subProductCodes)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 缓存文件
|
||||||
|
if g.CacheEnabled {
|
||||||
|
if err := g.cacheFile(outputPath, cacheKey); err != nil {
|
||||||
|
g.logger.Warn("缓存ZIP文件失败", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
g.logger.Debug("ZIP文件已缓存", zap.String("cache_key", cacheKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return outputPath, nil
|
return outputPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,3 +308,197 @@ func (g *ZipGenerator) AddFolderToZipWithPrefix(zipWriter *zip.Writer, folderPat
|
|||||||
return g.AddFileToZip(zipWriter, path, zipPath)
|
return g.AddFileToZip(zipWriter, path, zipPath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateFilteredComponentZip 生成筛选后的组件ZIP文件
|
||||||
|
// productID: 产品ID
|
||||||
|
// subProductCodes: 子产品编号列表(用于筛选组件)
|
||||||
|
// outputPath: 输出ZIP文件路径(如果为空,则使用默认路径)
|
||||||
|
func (g *ZipGenerator) GenerateFilteredComponentZip(
|
||||||
|
ctx context.Context,
|
||||||
|
productID string,
|
||||||
|
subProductCodes []string,
|
||||||
|
outputPath string,
|
||||||
|
) (string, error) {
|
||||||
|
// 1. 确定基础路径
|
||||||
|
basePath := filepath.Join("resources", "Pure_Component")
|
||||||
|
uiBasePath := filepath.Join(basePath, "src", "ui")
|
||||||
|
|
||||||
|
// 2. 确定输出路径
|
||||||
|
if outputPath == "" {
|
||||||
|
// 使用默认路径:storage/component-reports/{productID}_filtered.zip
|
||||||
|
outputDir := "storage/component-reports"
|
||||||
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||||
|
return "", fmt.Errorf("创建输出目录失败: %w", err)
|
||||||
|
}
|
||||||
|
outputPath = filepath.Join(outputDir, fmt.Sprintf("%s_filtered.zip", productID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 创建ZIP文件
|
||||||
|
zipFile, err := os.Create(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("创建ZIP文件失败: %w", err)
|
||||||
|
}
|
||||||
|
defer zipFile.Close()
|
||||||
|
|
||||||
|
zipWriter := zip.NewWriter(zipFile)
|
||||||
|
defer zipWriter.Close()
|
||||||
|
|
||||||
|
// 4. 收集所有匹配的组件名称(文件夹名或文件名)
|
||||||
|
matchedNames := make(map[string]bool)
|
||||||
|
for _, productCode := range subProductCodes {
|
||||||
|
// 简化匹配逻辑,直接使用产品代码作为组件名
|
||||||
|
matchedNames[productCode] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 递归添加整个 Pure_Component 目录,但筛选 ui 目录下的内容
|
||||||
|
err = filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算相对于基础路径的相对路径
|
||||||
|
relPath, err := filepath.Rel(basePath, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为ZIP路径格式,保持在Pure_Component目录下
|
||||||
|
zipPath := filepath.ToSlash(filepath.Join("Pure_Component", relPath))
|
||||||
|
|
||||||
|
// 检查是否在 ui 目录下
|
||||||
|
uiRelPath, err := filepath.Rel(uiBasePath, path)
|
||||||
|
isInUIDir := err == nil && !strings.HasPrefix(uiRelPath, "..")
|
||||||
|
|
||||||
|
if isInUIDir {
|
||||||
|
// 如果是 ui 目录本身,直接添加
|
||||||
|
if uiRelPath == "." || uiRelPath == "" {
|
||||||
|
if info.IsDir() {
|
||||||
|
_, err = zipWriter.Create(zipPath + "/")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件/文件夹名称
|
||||||
|
fileName := info.Name()
|
||||||
|
|
||||||
|
// 检查是否应该保留:匹配到的组件文件夹/文件
|
||||||
|
shouldInclude := false
|
||||||
|
|
||||||
|
// 检查是否是匹配的组件(检查组件名称)
|
||||||
|
if matchedNames[fileName] {
|
||||||
|
shouldInclude = true
|
||||||
|
} else {
|
||||||
|
// 检查是否在匹配的组件文件夹内
|
||||||
|
// 获取相对于 ui 的路径的第一部分(组件文件夹名)
|
||||||
|
parts := strings.Split(filepath.ToSlash(uiRelPath), "/")
|
||||||
|
if len(parts) > 0 && parts[0] != "" && parts[0] != "." {
|
||||||
|
if matchedNames[parts[0]] {
|
||||||
|
shouldInclude = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !shouldInclude {
|
||||||
|
// 跳过不匹配的文件/文件夹
|
||||||
|
if info.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是目录,创建目录项
|
||||||
|
if info.IsDir() {
|
||||||
|
_, err = zipWriter.Create(zipPath + "/")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文件
|
||||||
|
return g.AddFileToZip(zipWriter, path, zipPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
g.logger.Warn("添加Pure_Component目录失败", zap.Error(err))
|
||||||
|
return "", fmt.Errorf("添加Pure_Component目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.logger.Info("成功生成筛选后的组件ZIP文件",
|
||||||
|
zap.String("product_id", productID),
|
||||||
|
zap.String("output_path", outputPath),
|
||||||
|
zap.Int("matched_components_count", len(matchedNames)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return outputPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateCacheKey 生成缓存键
|
||||||
|
func (g *ZipGenerator) generateCacheKey(productID string, subProductCodes []string) string {
|
||||||
|
// 使用产品ID和子产品编码列表生成MD5哈希
|
||||||
|
data := productID
|
||||||
|
for _, code := range subProductCodes {
|
||||||
|
data += "|" + code
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := md5.Sum([]byte(data))
|
||||||
|
return hex.EncodeToString(hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCachedFile 获取缓存文件
|
||||||
|
func (g *ZipGenerator) getCachedFile(cacheKey string) (string, error) {
|
||||||
|
// 确保缓存目录存在
|
||||||
|
if err := os.MkdirAll(g.CacheDir, 0755); err != nil {
|
||||||
|
return "", fmt.Errorf("创建缓存目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFilePath := filepath.Join(g.CacheDir, cacheKey+".zip")
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
fileInfo, err := os.Stat(cacheFilePath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", nil // 文件不存在,但不是错误
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件是否过期
|
||||||
|
if time.Since(fileInfo.ModTime()) > g.CacheTTL {
|
||||||
|
// 文件过期,删除
|
||||||
|
os.Remove(cacheFilePath)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheFilePath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheFile 缓存文件
|
||||||
|
func (g *ZipGenerator) cacheFile(filePath, cacheKey string) error {
|
||||||
|
// 确保缓存目录存在
|
||||||
|
if err := os.MkdirAll(g.CacheDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("创建缓存目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFilePath := filepath.Join(g.CacheDir, cacheKey+".zip")
|
||||||
|
|
||||||
|
// 复制文件到缓存目录
|
||||||
|
return g.copyFile(filePath, cacheFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyFile 复制文件
|
||||||
|
func (g *ZipGenerator) copyFile(src, dst string) error {
|
||||||
|
sourceFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sourceFile.Close()
|
||||||
|
|
||||||
|
destFile, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destFile, sourceFile)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,9 +35,17 @@ type CacheInfo struct {
|
|||||||
|
|
||||||
// NewPDFCacheManager 创建PDF缓存管理器
|
// NewPDFCacheManager 创建PDF缓存管理器
|
||||||
func NewPDFCacheManager(logger *zap.Logger, cacheDir string, ttl time.Duration, maxSize int64) (*PDFCacheManager, error) {
|
func NewPDFCacheManager(logger *zap.Logger, cacheDir string, ttl time.Duration, maxSize int64) (*PDFCacheManager, error) {
|
||||||
// 如果缓存目录为空,使用默认目录
|
// 如果缓存目录为空,使用项目根目录的storage/component-reports/cache目录
|
||||||
if cacheDir == "" {
|
if cacheDir == "" {
|
||||||
cacheDir = filepath.Join(os.TempDir(), "tyapi_pdf_cache")
|
// 获取当前工作目录并构建项目根目录路径
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
// 如果获取工作目录失败,回退到临时目录
|
||||||
|
cacheDir = filepath.Join(os.TempDir(), "tyapi_pdf_cache")
|
||||||
|
} else {
|
||||||
|
// 构建项目根目录下的storage/component-reports/cache路径
|
||||||
|
cacheDir = filepath.Join(wd, "storage", "component-reports", "cache")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保缓存目录存在
|
// 确保缓存目录存在
|
||||||
@@ -60,7 +68,6 @@ func NewPDFCacheManager(logger *zap.Logger, cacheDir string, ttl time.Duration,
|
|||||||
zap.Duration("ttl", ttl),
|
zap.Duration("ttl", ttl),
|
||||||
zap.Int64("max_size", maxSize),
|
zap.Int64("max_size", maxSize),
|
||||||
)
|
)
|
||||||
|
|
||||||
return manager, nil
|
return manager, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ func validateIDCardChecksum(idCard string) bool {
|
|||||||
|
|
||||||
return byte(lastChar) == checksum
|
return byte(lastChar) == checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePrice 价格验证
|
// validatePrice 价格验证
|
||||||
func validatePrice(fl validator.FieldLevel) bool {
|
func validatePrice(fl validator.FieldLevel) bool {
|
||||||
price := fl.Field().Float()
|
price := fl.Field().Float()
|
||||||
@@ -559,18 +560,18 @@ func validateReturnURL(fl validator.FieldLevel) bool {
|
|||||||
// validateEnterpriseName 企业名称验证器
|
// validateEnterpriseName 企业名称验证器
|
||||||
func validateEnterpriseName(fl validator.FieldLevel) bool {
|
func validateEnterpriseName(fl validator.FieldLevel) bool {
|
||||||
enterpriseName := fl.Field().String()
|
enterpriseName := fl.Field().String()
|
||||||
|
|
||||||
// 去除首尾空格
|
// 去除首尾空格
|
||||||
trimmedName := strings.TrimSpace(enterpriseName)
|
trimmedName := strings.TrimSpace(enterpriseName)
|
||||||
if trimmedName == "" {
|
if trimmedName == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 长度验证:2-100个字符
|
// 长度验证:2-100个字符
|
||||||
if len(trimmedName) < 2 || len(trimmedName) > 100 {
|
if len(trimmedName) < 2 || len(trimmedName) > 100 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否包含非法字符(允许中英文括号)
|
// 检查是否包含非法字符(允许中英文括号)
|
||||||
invalidChars := []string{
|
invalidChars := []string{
|
||||||
"`", "~", "!", "@", "#", "$", "%", "^", "&", "*",
|
"`", "~", "!", "@", "#", "$", "%", "^", "&", "*",
|
||||||
@@ -581,13 +582,13 @@ func validateEnterpriseName(fl validator.FieldLevel) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 必须包含至少一个中文字符或英文字母
|
// 必须包含至少一个中文字符或英文字母
|
||||||
hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName)
|
hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName)
|
||||||
if !hasValidChar {
|
if !hasValidChar {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证企业名称的基本格式(支持各种类型的企业)
|
// 验证企业名称的基本格式(支持各种类型的企业)
|
||||||
// 支持:有限公司、股份有限公司、工作室、个体工商户、合伙企业等
|
// 支持:有限公司、股份有限公司、工作室、个体工商户、合伙企业等
|
||||||
validSuffixes := []string{
|
validSuffixes := []string{
|
||||||
@@ -598,7 +599,7 @@ func validateEnterpriseName(fl validator.FieldLevel) bool {
|
|||||||
"Co.,Ltd", "Co., Ltd", "Ltd", "LLC", "Inc", "Corp",
|
"Co.,Ltd", "Co., Ltd", "Ltd", "LLC", "Inc", "Corp",
|
||||||
"Company", "Studio", "Workshop", "Enterprise",
|
"Company", "Studio", "Workshop", "Enterprise",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否以合法的企业类型结尾(不强制要求,因为有些企业名称可能没有标准后缀)
|
// 检查是否以合法的企业类型结尾(不强制要求,因为有些企业名称可能没有标准后缀)
|
||||||
// 但如果有后缀,必须是合法的
|
// 但如果有后缀,必须是合法的
|
||||||
hasValidSuffix := false
|
hasValidSuffix := false
|
||||||
@@ -613,7 +614,7 @@ func validateEnterpriseName(fl validator.FieldLevel) bool {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果名称中包含常见的企业类型关键词,则必须是合法的后缀
|
// 如果名称中包含常见的企业类型关键词,则必须是合法的后缀
|
||||||
enterpriseKeywords := []string{"公司", "工作室", "企业", "集团", "Co", "Ltd", "LLC", "Inc", "Corp", "Company", "Studio", "Workshop", "Enterprise"}
|
enterpriseKeywords := []string{"公司", "工作室", "企业", "集团", "Co", "Ltd", "LLC", "Inc", "Corp", "Company", "Studio", "Workshop", "Enterprise"}
|
||||||
containsKeyword := false
|
containsKeyword := false
|
||||||
@@ -623,12 +624,12 @@ func validateEnterpriseName(fl validator.FieldLevel) bool {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果包含企业关键词但没有合法后缀,则验证失败
|
// 如果包含企业关键词但没有合法后缀,则验证失败
|
||||||
if containsKeyword && !hasValidSuffix {
|
if containsKeyword && !hasValidSuffix {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,7 +658,7 @@ func ValidatePassword(password string) error {
|
|||||||
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
|
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
|
||||||
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
|
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
|
||||||
hasDigit := regexp.MustCompile(`\d`).MatchString(password)
|
hasDigit := regexp.MustCompile(`\d`).MatchString(password)
|
||||||
|
|
||||||
if !hasUpper {
|
if !hasUpper {
|
||||||
return fmt.Errorf("密码必须包含大写字母")
|
return fmt.Errorf("密码必须包含大写字母")
|
||||||
}
|
}
|
||||||
@@ -790,7 +791,7 @@ func ValidateRequired(value interface{}, fieldName string) error {
|
|||||||
if value == nil {
|
if value == nil {
|
||||||
return fmt.Errorf("%s不能为空", fieldName)
|
return fmt.Errorf("%s不能为空", fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
if strings.TrimSpace(v) == "" {
|
if strings.TrimSpace(v) == "" {
|
||||||
@@ -801,7 +802,7 @@ func ValidateRequired(value interface{}, fieldName string) error {
|
|||||||
return fmt.Errorf("%s不能为空", fieldName)
|
return fmt.Errorf("%s不能为空", fieldName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,7 +822,7 @@ func ValidateSliceNotEmpty(slice interface{}, fieldName string) error {
|
|||||||
if slice == nil {
|
if slice == nil {
|
||||||
return fmt.Errorf("%s不能为空", fieldName)
|
return fmt.Errorf("%s不能为空", fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := slice.(type) {
|
switch v := slice.(type) {
|
||||||
case []string:
|
case []string:
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
@@ -832,7 +833,7 @@ func ValidateSliceNotEmpty(slice interface{}, fieldName string) error {
|
|||||||
return fmt.Errorf("%s不能为空", fieldName)
|
return fmt.Errorf("%s不能为空", fieldName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,13 +842,13 @@ func ValidateEnterpriseName(enterpriseName string) error {
|
|||||||
if enterpriseName == "" {
|
if enterpriseName == "" {
|
||||||
return fmt.Errorf("企业名称不能为空")
|
return fmt.Errorf("企业名称不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 去除首尾空格
|
// 去除首尾空格
|
||||||
trimmedName := strings.TrimSpace(enterpriseName)
|
trimmedName := strings.TrimSpace(enterpriseName)
|
||||||
if trimmedName == "" {
|
if trimmedName == "" {
|
||||||
return fmt.Errorf("企业名称不能为空")
|
return fmt.Errorf("企业名称不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 长度验证:2-100个字符
|
// 长度验证:2-100个字符
|
||||||
if len(trimmedName) < 2 {
|
if len(trimmedName) < 2 {
|
||||||
return fmt.Errorf("企业名称长度不能少于2个字符")
|
return fmt.Errorf("企业名称长度不能少于2个字符")
|
||||||
@@ -855,7 +856,7 @@ func ValidateEnterpriseName(enterpriseName string) error {
|
|||||||
if len(trimmedName) > 100 {
|
if len(trimmedName) > 100 {
|
||||||
return fmt.Errorf("企业名称长度不能超过100个字符")
|
return fmt.Errorf("企业名称长度不能超过100个字符")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否包含非法字符(允许中英文括号)
|
// 检查是否包含非法字符(允许中英文括号)
|
||||||
invalidChars := []string{
|
invalidChars := []string{
|
||||||
"`", "~", "!", "@", "#", "$", "%", "^", "&", "*",
|
"`", "~", "!", "@", "#", "$", "%", "^", "&", "*",
|
||||||
@@ -866,13 +867,13 @@ func ValidateEnterpriseName(enterpriseName string) error {
|
|||||||
return fmt.Errorf("企业名称不能包含特殊字符: %s", char)
|
return fmt.Errorf("企业名称不能包含特殊字符: %s", char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 必须包含至少一个中文字符或英文字母
|
// 必须包含至少一个中文字符或英文字母
|
||||||
hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName)
|
hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName)
|
||||||
if !hasValidChar {
|
if !hasValidChar {
|
||||||
return fmt.Errorf("企业名称必须包含中文字符或英文字母")
|
return fmt.Errorf("企业名称必须包含中文字符或英文字母")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证企业名称的基本格式(支持各种类型的企业)
|
// 验证企业名称的基本格式(支持各种类型的企业)
|
||||||
// 支持:有限公司、股份有限公司、工作室、个体工商户、合伙企业等
|
// 支持:有限公司、股份有限公司、工作室、个体工商户、合伙企业等
|
||||||
validSuffixes := []string{
|
validSuffixes := []string{
|
||||||
@@ -883,7 +884,7 @@ func ValidateEnterpriseName(enterpriseName string) error {
|
|||||||
"Co.,Ltd", "Co., Ltd", "Ltd", "LLC", "Inc", "Corp",
|
"Co.,Ltd", "Co., Ltd", "Ltd", "LLC", "Inc", "Corp",
|
||||||
"Company", "Studio", "Workshop", "Enterprise",
|
"Company", "Studio", "Workshop", "Enterprise",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否以合法的企业类型结尾
|
// 检查是否以合法的企业类型结尾
|
||||||
hasValidSuffix := false
|
hasValidSuffix := false
|
||||||
for _, suffix := range validSuffixes {
|
for _, suffix := range validSuffixes {
|
||||||
@@ -897,7 +898,7 @@ func ValidateEnterpriseName(enterpriseName string) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果名称中包含常见的企业类型关键词,则必须是合法的后缀
|
// 如果名称中包含常见的企业类型关键词,则必须是合法的后缀
|
||||||
enterpriseKeywords := []string{"公司", "工作室", "企业", "集团", "Co", "Ltd", "LLC", "Inc", "Corp", "Company", "Studio", "Workshop", "Enterprise"}
|
enterpriseKeywords := []string{"公司", "工作室", "企业", "集团", "Co", "Ltd", "LLC", "Inc", "Corp", "Company", "Studio", "Workshop", "Enterprise"}
|
||||||
containsKeyword := false
|
containsKeyword := false
|
||||||
@@ -907,12 +908,12 @@ func ValidateEnterpriseName(enterpriseName string) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果包含企业关键词但没有合法后缀,则验证失败
|
// 如果包含企业关键词但没有合法后缀,则验证失败
|
||||||
if containsKeyword && !hasValidSuffix {
|
if containsKeyword && !hasValidSuffix {
|
||||||
return fmt.Errorf("企业名称格式不正确,请使用标准的企业类型后缀(如:有限公司、工作室等)")
|
return fmt.Errorf("企业名称格式不正确,请使用标准的企业类型后缀(如:有限公司、工作室等)")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -922,9 +923,9 @@ func ValidateEnterpriseName(enterpriseName string) error {
|
|||||||
func NewBusinessValidator() *BusinessValidator {
|
func NewBusinessValidator() *BusinessValidator {
|
||||||
// 确保全局校验器已初始化
|
// 确保全局校验器已初始化
|
||||||
InitGlobalValidator()
|
InitGlobalValidator()
|
||||||
|
|
||||||
return &BusinessValidator{
|
return &BusinessValidator{
|
||||||
validator: GetGlobalValidator(), // 使用全局校验器
|
validator: GetGlobalValidator(), // 使用全局校验器
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,29 +947,29 @@ func (bv *BusinessValidator) ValidateField(field interface{}, tag string) error
|
|||||||
// validateBase64Image Base64图片格式验证器(JPG、BMP、PNG)
|
// validateBase64Image Base64图片格式验证器(JPG、BMP、PNG)
|
||||||
func validateBase64Image(fl validator.FieldLevel) bool {
|
func validateBase64Image(fl validator.FieldLevel) bool {
|
||||||
base64Str := fl.Field().String()
|
base64Str := fl.Field().String()
|
||||||
|
|
||||||
// 如果为空,由 omitempty 处理
|
// 如果为空,由 omitempty 处理
|
||||||
if base64Str == "" {
|
if base64Str == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 去除首尾空格
|
// 去除首尾空格
|
||||||
base64Str = strings.TrimSpace(base64Str)
|
base64Str = strings.TrimSpace(base64Str)
|
||||||
if base64Str == "" {
|
if base64Str == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解码 base64 字符串
|
// 解码 base64 字符串
|
||||||
decoded, err := base64.StdEncoding.DecodeString(base64Str)
|
decoded, err := base64.StdEncoding.DecodeString(base64Str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查数据长度(至少要有文件头)
|
// 检查数据长度(至少要有文件头)
|
||||||
if len(decoded) < 4 {
|
if len(decoded) < 4 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查文件头,判断图片格式
|
// 检查文件头,判断图片格式
|
||||||
// JPG: FF D8 FF
|
// JPG: FF D8 FF
|
||||||
// PNG: 89 50 4E 47
|
// PNG: 89 50 4E 47
|
||||||
@@ -977,16 +978,16 @@ func validateBase64Image(fl validator.FieldLevel) bool {
|
|||||||
// JPG格式
|
// JPG格式
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(decoded) >= 4 && decoded[0] == 0x89 && decoded[1] == 0x50 && decoded[2] == 0x4E && decoded[3] == 0x47 {
|
if len(decoded) >= 4 && decoded[0] == 0x89 && decoded[1] == 0x50 && decoded[2] == 0x4E && decoded[3] == 0x47 {
|
||||||
// PNG格式
|
// PNG格式
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(decoded) >= 2 && decoded[0] == 0x42 && decoded[1] == 0x4D {
|
if len(decoded) >= 2 && decoded[0] == 0x42 && decoded[1] == 0x4D {
|
||||||
// BMP格式
|
// BMP格式
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user