Compare commits

..

81 Commits

Author SHA1 Message Date
Mrx
e48efb4566 f 2026-01-23 18:37:22 +08:00
Mrx
e6e38013b8 f 2026-01-23 18:33:14 +08:00
Mrx
9b223c996d f 2026-01-23 18:18:49 +08:00
Mrx
78035ca1ab 替换shumai 2026-01-23 18:10:07 +08:00
Mrx
38ca033e31 f 2026-01-23 18:02:39 +08:00
Mrx
b05c459b81 f 2026-01-23 17:53:11 +08:00
Mrx
6104bfb84f f 2026-01-22 18:10:21 +08:00
Mrx
d15d2f2499 f 2026-01-22 17:31:32 +08:00
Mrx
7e45a45309 f 2026-01-22 14:44:39 +08:00
Mrx
473468e680 f 2026-01-22 14:26:15 +08:00
Mrx
7785d3b6ef f 2026-01-22 12:23:59 +08:00
Mrx
6607129083 f 2026-01-21 18:02:31 +08:00
Mrx
0036180979 s 2026-01-21 18:00:38 +08:00
Mrx
0d3a006c46 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-01-21 16:12:58 +08:00
Mrx
17f2433fee 重新启用QYGL3F8E upQYGL6S1B addFXGL5a3b dQYGL8271 2026-01-21 16:10:23 +08:00
8a222a0b7f f 2026-01-20 18:32:16 +08:00
03cfddee93 f 2026-01-20 17:31:35 +08:00
Mrx
abc7d655ce -f 2026-01-20 14:26:47 +08:00
Mrx
bdb8395615 -f 2026-01-20 14:25:50 +08:00
Mrx
6905936cf3 良心老板空不扣费的修改 2026-01-20 11:29:58 +08:00
Mrx
85bd011fd3 f 2026-01-19 14:28:21 +08:00
2d5c2120fb f+ 2026-01-19 11:33:21 +08:00
3e5016b439 f 2026-01-17 14:09:42 +08:00
96d530a67d -f 2026-01-16 18:37:47 +08:00
96dfa3d758 -f单独处理,null不计费 2026-01-15 15:30:39 +08:00
1e2687522b F 2026-01-15 13:26:09 +08:00
f6e7d46067 F 2026-01-15 13:05:37 +08:00
1e4e2f4b6d add 2026-01-14 17:51:03 +08:00
21fae5c486 f 2026-01-14 16:22:02 +08:00
6b902f68f1 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-01-14 16:00:32 +08:00
745fbc05d5 f 2026-01-14 16:00:27 +08:00
6618e35869 -f 2026-01-14 15:59:15 +08:00
7fdfa02189 f 2026-01-14 13:25:31 +08:00
d60dc70798 change sms template code 2026-01-14 13:21:11 +08:00
7a589a9c13 -f 2026-01-14 12:57:53 +08:00
afc2ab9f4d -f 2026-01-14 12:54:04 +08:00
8b3a80b93f -f 2026-01-14 12:46:29 +08:00
a36d188701 -f 2026-01-14 12:07:58 +08:00
ef2d73a9ec f 2026-01-14 11:58:06 +08:00
c29c1bceff f 2026-01-12 13:39:41 +08:00
46a181d027 f 2026-01-12 13:31:13 +08:00
ff8a946d13 f 2026-01-12 13:26:33 +08:00
68a9e32131 f 2026-01-12 13:20:17 +08:00
ead5f17b7c add f 2026-01-09 15:58:09 +08:00
bd76520d22 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-01-07 13:05:27 +08:00
ee55b068a6 fix 2026-01-07 13:05:12 +08:00
45397891b8 f 2026-01-07 12:43:26 +08:00
ae482b7888 add try 2026-01-07 10:42:09 +08:00
ddbae6f82a f 2026-01-06 18:11:37 +08:00
90f16911e9 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-01-06 17:42:11 +08:00
7746c5c8e3 f 2026-01-06 17:42:09 +08:00
8098c13de3 f 2026-01-06 17:06:33 +08:00
e61f03a2dd add 极光数据 2026-01-06 16:37:31 +08:00
4fcf370dcd fix 2026-01-05 17:19:55 +08:00
b0ed5b04ee fix add 2026-01-05 16:41:05 +08:00
269ff38604 fix add 2026-01-05 16:36:50 +08:00
23909c44f1 Resolve merge conflicts 2026-01-05 14:34:58 +08:00
aabe34b7d5 Merge branch 'main' of http://1.117.67.95:3000/team/tyapi-server 2026-01-05 14:31:11 +08:00
c262894372 add 全国企业司法模型服务查询_V1 2026-01-05 14:26:26 +08:00
39f799bc41 fix 2025-12-31 18:06:59 +08:00
1d4411a940 add 2025-12-31 17:46:03 +08:00
fe44b452e3 新增:极光config 2025-12-31 16:12:09 +08:00
f1ec9bfe7f 新增:新增极光源,极光婚姻接口(测试) 2025-12-31 15:42:05 +08:00
a70e736cdd fix 2025-12-27 19:18:12 +08:00
53d2c75a9c 更改:给yysy09cd无缝切换换源 2025-12-26 18:48:53 +08:00
0bfa7b4f50 移除王起航 2025-12-26 14:41:31 +08:00
e2e729eec8 add 人脸123 2025-12-25 18:25:37 +08:00
5f7fb43804 fix 2025-12-25 12:40:40 +08:00
89c5c0f9ad 1 2025-12-24 17:52:51 +08:00
6c949a4a1c fix id_car 2025-12-24 17:52:33 +08:00
8556e7331d add fix id_car 2025-12-24 17:50:33 +08:00
311d7a9b01 fix 1 2025-12-24 12:32:25 +08:00
ce45ce3ed0 fix 2025-12-23 18:54:38 +08:00
34e2c1bc41 fixdele 2025-12-23 17:17:41 +08:00
2618105140 fix 2025-12-23 16:16:45 +08:00
6b41f3833a fix 2025-12-23 15:04:53 +08:00
446a5c7661 fix 2025-12-23 11:26:25 +08:00
7f8554fa12 add购买记录功能 2025-12-22 18:32:34 +08:00
65a61d0336 fix2 2025-12-19 18:16:22 +08:00
8dd6f71baf fix 2025-12-19 18:13:31 +08:00
e96653751d fix 2025-12-19 17:50:29 +08:00
431 changed files with 12427 additions and 84311 deletions

5
.gitignore vendored
View File

@@ -40,8 +40,13 @@ internal/shared/pdf/fonts/*.ttf
internal/shared/pdf/fonts/*.ttc
internal/shared/pdf/fonts/*.otf
# Pure Component 目录(用于持久化存储,不进行版本控制)
resources/Pure_Component/
# 其他
*.exe
*.exe*
*.dll
*.so
*.dylib
cmd/api/__debug_bin*

View File

@@ -54,7 +54,8 @@ COPY config.yaml .
COPY configs/ ./configs/
# 复制资源文件(直接从构建上下文复制,与配置文件一致)
COPY resources ./resources
COPY resources/etc ./resources/etc
COPY resources/pdf ./resources/pdf
# 暴露端口
EXPOSE 8080

View File

@@ -124,7 +124,7 @@ sms:
access_key_id: "LTAI5tKGB3TVJbMHSoZN3yr9"
access_key_secret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
endpoint_url: "dysmsapi.aliyuncs.com"
sign_name: "天远查"
sign_name: "海南海宇大数据"
template_code: "SMS_302641455"
code_length: 6
expire_time: 5m
@@ -158,16 +158,16 @@ ocr:
secret_key: "your-baidu-secret-key"
ratelimit:
requests: 5000
window: 60s
requests: 7500
window: 70s
# 每日请求限制配置
daily_ratelimit:
max_requests_per_day: 200 # 每日最大请求次数
max_requests_per_ip: 10 # 每个IP每日最大请求次数
max_requests_per_day: 300 # 每日最大请求次数
max_requests_per_ip: 15 # 每个IP每日最大请求次数
key_prefix: "daily_limit" # Redis键前缀
ttl: 24h # 键过期时间
max_concurrent: 5 # 最大并发请求数
max_concurrent: 8 # 最大并发请求数
# 安全配置
enable_ip_whitelist: false # 是否启用IP白名单
@@ -437,7 +437,7 @@ zhicha:
# 🌐 木子数据配置
# ===========================================
muzi:
url: "https://carv.m0101.com/magic/carv/pubin/service/academic"
url: "https://carv.m0101.com/magic/carv/pubin/service"
app_id: "713014138179585"
app_secret: "bd4090ac652c404c80e90ebbdcd6ba1d"
timeout: 60s
@@ -494,3 +494,82 @@ xingwei:
max_backups: 5
max_age: 30
compress: true
# ===========================================
# ✨ 极光配置
# ===========================================
jiguang:
url: "http://api.jiguangcloud.com/jg-open-api-gateway/api"
app_id: "66ZA28w5" # 请替换为实际的 appId
app_secret: "e5261d0f6f003ae7b9fc1b0255b21761bb618d56" # 请替换为实际的 appSecret
sign_method: "hmac" # 签名方法md5 或 hmac默认 hmac
timeout: 60s # 请求超时时间,默认 60 秒
# 极光日志配置
logging:
enabled: true
log_dir: "logs/external_services"
service_name: "jiguang"
use_daily: true
enable_level_separation: true
# 各级别配置
level_configs:
info:
max_size: 100
max_backups: 5
max_age: 30
compress: true
error:
max_size: 200
max_backups: 10
max_age: 90
compress: true
warn:
max_size: 100
max_backups: 5
max_age: 30
compress: true
# ===========================================
# ✨ 数脉配置走实时接口
# ===========================================
shumai:
url: "https://api.shumaidata.com"
app_id: "pIfqx8MsoTOjhbB762qi5BfkjJ4D7w0O"
app_secret: "BnJWo61hUgNEa5fqBCueiT1IZ1e0DxPU"
# ===========================================
# ✨ 数脉子账号配置走政务
# ===========================================
# 走政务接口使用这个
app_id2: "AwZZRzWkArtFDO2lDcT2jHfuoo9n35Tq"
app_secret2: "nCXN6fKLImjfvzI12hj8O1CMl1gJeaWh"
sign_method: "md5" # 签名方法md5 或 hmac默认 hmac
timeout: 60s # 请求超时时间,默认 60 秒
# 数脉日志配置
logging:
enabled: true
log_dir: "logs/external_services"
service_name: "shumai"
use_daily: true
enable_level_separation: true
# 各级别配置
level_configs:
info:
max_size: 100
max_backups: 5
max_age: 30
compress: true
error:
max_size: 200
max_backups: 10
max_age: 90
compress: true
warn:
max_size: 100
max_backups: 5
max_age: 30
compress: true

View File

@@ -20,7 +20,8 @@ services:
networks:
- tyapi-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tyapi_user -d tyapi -h localhost"]
test:
["CMD-SHELL", "pg_isready -U tyapi_user -d tyapi -h localhost"]
interval: 30s
timeout: 10s
retries: 5
@@ -88,6 +89,7 @@ services:
- "25000:8080"
volumes:
- ./logs:/app/logs
- ./resources/Pure_Component:/app/resources/Pure_Component
# user: "1001:1001" # 注释掉使用root权限运行
networks:
- tyapi-network
@@ -169,7 +171,15 @@ services:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://localhost:8080/health",
]
interval: 30s
timeout: 10s
retries: 5
@@ -189,6 +199,8 @@ volumes:
driver: local
redis_data:
driver: local
pure_component:
driver: local
networks:
tyapi-network:

View File

@@ -2,13 +2,13 @@
## 一、功能概述
在产品详情页面添加"下载示例报告"功能,允许用户下载与产品对应的前端组件报告。报告文件位于 `resources/Pure Component/src/ui` 目录下通过产品编号product_code匹配对应的文件夹或文件。
在产品详情页面添加"下载示例报告"功能,允许用户下载与产品对应的前端组件报告。报告文件位于 `resources/Pure_Component/src/ui` 目录下通过产品编号product_code匹配对应的文件夹或文件。
## 二、核心需求
### 2.1 基本功能
1. **报告匹配**:根据子产品的 `product_code` 模糊匹配 `resources/Pure Component/src/ui` 下的文件夹或文件
1. **报告匹配**:根据子产品的 `product_code` 模糊匹配 `resources/Pure_Component/src/ui` 下的文件夹或文件
- 支持前缀匹配(如产品编号为 `DWBG6A2C`,文件夹可能是 `DWBG6A2C``多cDWBG6A2C`
- 匹配规则:文件夹名称包含产品编号,或产品编号包含文件夹名称的核心部分
@@ -97,8 +97,6 @@ CREATE TABLE component_report_downloads (
sub_product_ids TEXT, -- JSON数组存储子产品ID列表组合包使用
sub_product_codes TEXT, -- JSON数组存储子产品编号列表
download_price DECIMAL(10,2) NOT NULL, -- 实际支付价格
original_price DECIMAL(10,2) NOT NULL, -- 原始总价
discount_amount DECIMAL(10,2) DEFAULT 0, -- 减免金额
payment_order_id VARCHAR(64), -- 支付订单号(关联充值记录)
payment_type VARCHAR(20), -- 支付类型alipay, wechat
payment_status VARCHAR(20) DEFAULT 'pending', -- pending, success, failed
@@ -537,7 +535,7 @@ func (s *ComponentReportServiceImpl) MatchProductCodeToPath(ctx context.Context,
}
// 2. 扫描目录
basePath := "resources/Pure Component/src/ui"
basePath := "resources/Pure_Component/src/ui"
entries, err := os.ReadDir(basePath)
if err != nil {
return "", "", err
@@ -807,7 +805,7 @@ func (s *ComponentReportServiceImpl) GenerateZipFile(ctx context.Context, produc
defer zipWriter.Close()
// 3. 遍历子产品添加UI组件文件到ZIP
basePath := "resources/Pure Component/src/ui"
basePath := "resources/Pure_Component/src/ui"
for _, productCode := range subProductCodes {
path, fileType, err := s.MatchProductCodeToPath(ctx, productCode)
if err != nil {
@@ -847,7 +845,7 @@ func (s *ComponentReportServiceImpl) GenerateZipFile(ctx context.Context, produc
// 5. 添加其他必要的文件(如果需要)
// 例如:复制 public 目录下的其他文件(如果有)
publicBasePath := "resources/Pure Component/public"
publicBasePath := "resources/Pure_Component/public"
publicFiles, err := os.ReadDir(publicBasePath)
if err == nil {
for _, file := range publicFiles {

View File

@@ -231,6 +231,7 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
&financeEntities.AlipayOrder{},
&financeEntities.InvoiceApplication{},
&financeEntities.UserInvoiceInfo{},
&financeEntities.PurchaseOrder{}, //购买组件订单表
// 产品域
&productEntities.Product{},

View File

@@ -218,12 +218,38 @@ func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *co
return nil, ErrFrozenAccount
}
// 验证产品是否启用
if !product.IsEnabled {
s.logger.Error("产品未启用", zap.String("product_code", product.Code))
return nil, ErrProductDisabled
}
// 4. 验证IP白名单非开发环境
if !s.config.App.IsDevelopment() && !cmd.Options.IsDebug {
// 添加调试日志
s.logger.Info("开始验证白名单",
zap.String("userId", apiUser.UserId),
zap.String("clientIP", cmd.ClientIP),
zap.Bool("isDevelopment", s.config.App.IsDevelopment()),
zap.Bool("isDebug", cmd.Options.IsDebug),
zap.Int("whiteListCount", len(apiUser.WhiteList)))
// 输出白名单详细信息(用于调试)
for idx, item := range apiUser.WhiteList {
s.logger.Info("白名单项",
zap.Int("index", idx),
zap.String("ipAddress", item.IPAddress),
zap.String("remark", item.Remark))
}
if !apiUser.IsWhiteListed(cmd.ClientIP) {
s.logger.Error("IP不在白名单内", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP))
s.logger.Error("IP不在白名单内",
zap.String("userId", apiUser.UserId),
zap.String("ip", cmd.ClientIP),
zap.Int("whiteListSize", len(apiUser.WhiteList)))
return nil, ErrInvalidIP
}
s.logger.Info("白名单验证通过", zap.String("ip", cmd.ClientIP))
}
// 5. 验证钱包状态
@@ -583,12 +609,17 @@ func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID
// 转换为响应DTO
var items []dto.ApiCallRecordResponse
for _, call := range calls {
// 出于安全考虑,不再在数据库中存储或解密真实请求参数
// 这里只保留数据库中的原始占位值(通常为空字符串)
requestParamsStr := call.RequestParams
item := dto.ApiCallRecordResponse{
ID: call.ID,
AccessId: call.AccessId,
UserId: *call.UserId,
TransactionId: call.TransactionId,
ClientIp: call.ClientIp,
RequestParams: requestParamsStr,
Status: call.Status,
StartAt: call.StartAt.Format("2006-01-02 15:04:05"),
CreatedAt: call.CreatedAt.Format("2006-01-02 15:04:05"),
@@ -649,11 +680,49 @@ func (s *ApiApplicationServiceImpl) GetAdminApiCalls(ctx context.Context, filter
continue
}
// // 解密请求参数
// var requestParamsStr string = call.RequestParams // 默认使用原始值
// if call.UserId != nil && *call.UserId != "" {
// // 获取用户的API密钥信息
// apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, *call.UserId)
// if err != nil {
// s.logger.Error("获取用户API信息失败",
// zap.Error(err),
// zap.String("call_id", call.ID),
// zap.String("user_id", *call.UserId))
// // 获取失败时使用原始值
// } else if apiUser.SecretKey != "" {
// // 使用用户的SecretKey解密请求参数
// decryptedParams, err := s.DecryptParams(ctx, *call.UserId, &commands.DecryptCommand{
// EncryptedData: call.RequestParams,
// SecretKey: apiUser.SecretKey,
// })
// if err != nil {
// s.logger.Error("解密请求参数失败",
// zap.Error(err),
// zap.String("call_id", call.ID),
// zap.String("user_id", *call.UserId))
// // 解密失败时使用原始值
// } else {
// // 将解密后的数据转换为JSON字符串
// if jsonBytes, err := json.Marshal(decryptedParams); err == nil {
// requestParamsStr = string(jsonBytes)
// } else {
// s.logger.Error("序列化解密参数失败",
// zap.Error(err),
// zap.String("call_id", call.ID))
// // 序列化失败时使用原始值
// }
// }
// }
// }
item := dto.ApiCallRecordResponse{
ID: call.ID,
AccessId: call.AccessId,
TransactionId: call.TransactionId,
ClientIp: call.ClientIp,
// RequestParams: requestParamsStr,
Status: call.Status,
}
@@ -1292,7 +1361,7 @@ func (s *ApiApplicationServiceImpl) UpdateUserBalanceAlertSettings(ctx context.C
// TestBalanceAlertSms 测试余额预警短信
func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, userID string, phone string, balance float64, alertType string) error {
// 获取用户信息以获取企业名称
user, err := s.userRepo.GetByID(ctx, userID)
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
if err != nil {
s.logger.Error("获取用户信息失败",
zap.String("user_id", userID),

View File

@@ -49,6 +49,7 @@ type ApiCallRecordResponse struct {
ProductName *string `json:"product_name,omitempty"`
TransactionId string `json:"transaction_id"`
ClientIp string `json:"client_ip"`
RequestParams string `json:"request_params"`
Status string `json:"status"`
StartAt string `json:"start_at"`
EndAt *string `json:"end_at,omitempty"`

View File

@@ -109,6 +109,8 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
)
// 验证验证码
// 特殊验证码"768005"直接跳过验证环节
if cmd.VerificationCode != "768005" {
if err := s.smsCodeService.VerifyCode(ctx, cmd.LegalPersonPhone, cmd.VerificationCode, user_entities.SMSSceneCertification); err != nil {
record.MarkAsFailed(err.Error())
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
@@ -117,6 +119,7 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
}
return nil, fmt.Errorf("验证码错误或已过期")
}
}
s.logger.Info("开始处理企业信息提交",
zap.String("user_id", cmd.UserID))
// 1. 检查企业信息是否重复(统一社会信用代码,已经认证了的,不能重复提交)

View File

@@ -125,3 +125,45 @@ type UserSimpleResponse struct {
CompanyName string `json:"company_name"`
Phone string `json:"phone"`
}
// PurchaseRecordResponse 购买记录响应
type PurchaseRecordResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
OrderNo string `json:"order_no"`
TradeNo *string `json:"trade_no,omitempty"`
ProductID string `json:"product_id"`
ProductCode string `json:"product_code"`
ProductName string `json:"product_name"`
Category string `json:"category,omitempty"`
Subject string `json:"subject"`
Amount decimal.Decimal `json:"amount"`
PayAmount *decimal.Decimal `json:"pay_amount,omitempty"`
Status string `json:"status"`
Platform string `json:"platform"`
PayChannel string `json:"pay_channel"`
PaymentType string `json:"payment_type"`
BuyerID string `json:"buyer_id,omitempty"`
SellerID string `json:"seller_id,omitempty"`
ReceiptAmount decimal.Decimal `json:"receipt_amount,omitempty"`
NotifyTime *time.Time `json:"notify_time,omitempty"`
ReturnTime *time.Time `json:"return_time,omitempty"`
PayTime *time.Time `json:"pay_time,omitempty"`
FilePath *string `json:"file_path,omitempty"`
FileSize *int64 `json:"file_size,omitempty"`
Remark string `json:"remark,omitempty"`
ErrorCode string `json:"error_code,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
CompanyName string `json:"company_name,omitempty"`
User *UserSimpleResponse `json:"user,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// PurchaseRecordListResponse 购买记录列表响应
type PurchaseRecordListResponse struct {
Items []PurchaseRecordResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}

View File

@@ -43,6 +43,10 @@ type FinanceApplicationService interface {
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
// 购买记录
GetUserPurchaseRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
GetAdminPurchaseRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
// 获取充值配置
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/http"
"strings"
"time"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
@@ -15,6 +14,7 @@ import (
finance_services "tyapi-server/internal/domains/finance/services"
product_repositories "tyapi-server/internal/domains/product/repositories"
user_repositories "tyapi-server/internal/domains/user/repositories"
"tyapi-server/internal/shared/component_report"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/export"
"tyapi-server/internal/shared/interfaces"
@@ -36,6 +36,7 @@ type FinanceApplicationServiceImpl struct {
alipayOrderRepo finance_repositories.AlipayOrderRepository
wechatOrderRepo finance_repositories.WechatOrderRepository
rechargeRecordRepo finance_repositories.RechargeRecordRepository
purchaseOrderRepo finance_repositories.PurchaseOrderRepository
componentReportRepo product_repositories.ComponentReportRepository
userRepo user_repositories.UserRepository
txManager *database.TransactionManager
@@ -54,6 +55,7 @@ func NewFinanceApplicationService(
alipayOrderRepo finance_repositories.AlipayOrderRepository,
wechatOrderRepo finance_repositories.WechatOrderRepository,
rechargeRecordRepo finance_repositories.RechargeRecordRepository,
purchaseOrderRepo finance_repositories.PurchaseOrderRepository,
componentReportRepo product_repositories.ComponentReportRepository,
userRepo user_repositories.UserRepository,
txManager *database.TransactionManager,
@@ -70,6 +72,7 @@ func NewFinanceApplicationService(
alipayOrderRepo: alipayOrderRepo,
wechatOrderRepo: wechatOrderRepo,
rechargeRecordRepo: rechargeRecordRepo,
purchaseOrderRepo: purchaseOrderRepo,
componentReportRepo: componentReportRepo,
userRepo: userRepo,
txManager: txManager,
@@ -854,13 +857,7 @@ func (s *FinanceApplicationServiceImpl) HandleAlipayCallback(ctx context.Context
zap.String("trade_no", notification.TradeNo),
)
// 先检查是否是组件报告下载的支付订单
s.logger.Info("步骤1: 检查是否是组件报告下载订单",
zap.String("out_trade_no", notification.OutTradeNo),
)
// 使用公共方法处理支付成功逻辑(包括更新充值记录状态)
// 无论是组件报告下载订单还是普通充值订单,都需要更新充值记录状态
// 处理支付宝支付成功逻辑
err = s.processAlipayPaymentSuccess(ctx, notification.OutTradeNo, notification.TradeNo, notification.TotalAmount, notification.BuyerId, notification.SellerId)
if err != nil {
s.logger.Error("处理支付宝支付成功失败",
@@ -886,20 +883,52 @@ func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.
return err
}
// 直接调用充值记录服务处理支付成功逻辑
// 该服务内部会处理所有必要的检查、事务和更新操作
// 如果是组件报告下载订单,服务会自动跳过钱包余额增加
// 查找支付宝订单
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
if err != nil {
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
return err
}
if alipayOrder == nil {
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
return fmt.Errorf("支付宝订单不存在")
}
// 判断是否为充值订单还是购买订单
_, err = s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
if err == nil {
// 这是充值订单,调用充值记录服务处理支付成功逻辑
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
if err != nil {
s.logger.Error("处理支付宝支付成功失败",
s.logger.Error("处理支付宝充值支付成功失败",
zap.String("out_trade_no", outTradeNo),
zap.Error(err),
)
return err
}
// 检查并更新组件报告下载记录状态(如果存在)
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
} else {
// 尝试查找购买订单
_, err = s.purchaseOrderRepo.GetByID(ctx, alipayOrder.RechargeID)
if err == nil {
// 这是购买订单(可能是示例报告购买订单),调用处理购买订单支付成功逻辑
err = s.processPurchaseOrderPaymentSuccess(ctx, alipayOrder.RechargeID, tradeNo, amount, buyerID, sellerID)
if err != nil {
s.logger.Error("处理支付宝购买订单支付成功失败",
zap.String("out_trade_no", outTradeNo),
zap.String("purchase_order_id", alipayOrder.RechargeID),
zap.Error(err),
)
return err
}
} else {
s.logger.Error("无法确定订单类型",
zap.String("out_trade_no", outTradeNo),
zap.String("recharge_id", alipayOrder.RechargeID),
)
return fmt.Errorf("无法确定订单类型")
}
}
s.logger.Info("支付宝支付成功处理完成",
zap.String("out_trade_no", outTradeNo),
@@ -1477,30 +1506,7 @@ func (s *FinanceApplicationServiceImpl) HandleWechatPayCallback(ctx context.Cont
zap.String("transaction_id", transactionID),
)
// 先检查是否是组件报告下载的支付订单
s.logger.Info("步骤1: 检查是否是组件报告下载订单",
zap.String("out_trade_no", outTradeNo),
)
// 检查组件报告下载记录
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, outTradeNo)
if err == nil && download != nil {
s.logger.Info("步骤2: 发现组件报告下载订单,直接更新下载记录状态",
zap.String("out_trade_no", outTradeNo),
zap.String("download_id", download.ID),
zap.String("product_id", download.ProductID),
zap.String("current_status", download.PaymentStatus),
)
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
s.logger.Info("========== 组件报告下载订单处理完成 ==========")
return nil
}
s.logger.Info("步骤3: 不是组件报告下载订单,按充值流程处理",
zap.String("out_trade_no", outTradeNo),
)
// 处理支付成功逻辑(充值流程)
// 处理微信支付成功逻辑(充值流程)
err = s.processWechatPaymentSuccess(ctx, outTradeNo, transactionID, totalAmount)
if err != nil {
s.logger.Error("处理微信支付成功失败",
@@ -1535,25 +1541,33 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
return fmt.Errorf("微信订单不存在")
}
// 查找对应的充值记录
// 判断是否为充值订单还是购买订单
rechargeRecord, err := s.rechargeRecordService.GetByID(ctx, wechatOrder.RechargeID)
if err == nil {
// 这是充值订单,继续原有的处理逻辑
} else {
// 尝试查找购买订单
_, err = s.purchaseOrderRepo.GetByID(ctx, wechatOrder.RechargeID)
if err == nil {
// 这是购买订单(可能是示例报告购买订单),调用处理购买订单支付成功逻辑
err = s.processPurchaseOrderPaymentSuccess(ctx, wechatOrder.RechargeID, transactionID, amount, "", "")
if err != nil {
s.logger.Error("查找充值记录失败",
s.logger.Error("处理微信购买订单支付成功失败",
zap.String("out_trade_no", outTradeNo),
zap.String("recharge_id", wechatOrder.RechargeID),
zap.String("purchase_order_id", wechatOrder.RechargeID),
zap.Error(err),
)
return fmt.Errorf("查找充值记录失败: %w", err)
return err
}
s.logger.Info("步骤4: 检查充值记录备注,判断是否为组件报告下载订单",
return nil
} else {
s.logger.Error("无法确定订单类型",
zap.String("out_trade_no", outTradeNo),
zap.String("recharge_id", rechargeRecord.ID),
zap.String("notes", rechargeRecord.Notes),
zap.String("recharge_id", wechatOrder.RechargeID),
)
// 检查是否是组件报告下载订单(通过备注判断)
isComponentReportOrder := strings.Contains(rechargeRecord.Notes, "购买") && strings.Contains(rechargeRecord.Notes, "报告示例")
return fmt.Errorf("无法确定订单类型")
}
}
// 检查订单和充值记录状态,如果都已成功则跳过(只记录一次日志)
if wechatOrder.Status == finance_entities.WechatOrderStatusSuccess && rechargeRecord.Status == finance_entities.RechargeStatusSuccess {
@@ -1562,12 +1576,7 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
zap.String("transaction_id", transactionID),
zap.String("order_id", wechatOrder.ID),
zap.String("recharge_id", rechargeRecord.ID),
zap.Bool("is_component_report", isComponentReportOrder),
)
// 如果是组件报告下载订单,确保更新下载记录状态
if isComponentReportOrder {
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
}
return nil
}
@@ -1638,21 +1647,6 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
)
}
// 检查是否是组件报告下载订单(通过备注判断)
isComponentReportOrder := strings.Contains(rechargeRecord.Notes, "购买") && strings.Contains(rechargeRecord.Notes, "报告示例")
if isComponentReportOrder {
s.logger.Info("步骤5: 检测到组件报告下载订单,不增加钱包余额",
zap.String("out_trade_no", outTradeNo),
zap.String("recharge_id", rechargeRecord.ID),
zap.String("notes", rechargeRecord.Notes),
)
// 组件报告下载订单不增加钱包余额,只更新订单和充值记录状态
} 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)
@@ -1665,7 +1659,6 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
)
return err
}
}
return nil
})
@@ -1680,105 +1673,129 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
return err
}
// 如果是组件报告下载订单,更新下载记录状态
if isComponentReportOrder {
s.logger.Info("步骤6: 更新组件报告下载记录状态",
zap.String("out_trade_no", outTradeNo),
)
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
}
s.logger.Info("微信支付成功处理完成",
zap.String("out_trade_no", outTradeNo),
zap.String("transaction_id", transactionID),
zap.String("amount", amount.String()),
zap.String("bonus_amount", bonusAmount.String()),
zap.String("user_id", rechargeRecord.UserID),
zap.Bool("is_component_report", isComponentReportOrder),
)
return nil
}
// updateComponentReportDownloadStatus 更新组件报告下载记录状态
func (s *FinanceApplicationServiceImpl) updateComponentReportDownloadStatus(ctx context.Context, outTradeNo string) {
s.logger.Info("========== 开始更新组件报告下载记录状态 ==========",
zap.String("out_trade_no", outTradeNo),
)
if s.componentReportRepo == nil {
s.logger.Warn("组件报告下载Repository未初始化跳过更新")
return
}
// 根据支付订单号查找组件报告下载记录
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, outTradeNo)
// processPurchaseOrderPaymentSuccess 处理购买订单支付成功的逻辑
func (s *FinanceApplicationServiceImpl) processPurchaseOrderPaymentSuccess(ctx context.Context, purchaseOrderID, tradeNo string, amount decimal.Decimal, buyerID, sellerID string) error {
// 查找购买订单
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, purchaseOrderID)
if err != nil {
s.logger.Info("未找到组件报告下载记录,可能不是组件报告下载订单",
zap.String("out_trade_no", outTradeNo),
s.logger.Error("查找购买订单失败",
zap.String("purchase_order_id", purchaseOrderID),
zap.Error(err),
)
return
return fmt.Errorf("查找购买订单失败: %w", err)
}
if download == nil {
s.logger.Info("组件报告下载记录为空,跳过更新",
zap.String("out_trade_no", outTradeNo),
if purchaseOrder == nil {
s.logger.Error("购买订单不存在",
zap.String("purchase_order_id", purchaseOrderID),
)
return
return fmt.Errorf("购买订单不存在")
}
s.logger.Info("步骤1: 找到组件报告下载记录",
zap.String("out_trade_no", outTradeNo),
zap.String("download_id", download.ID),
zap.String("product_id", download.ProductID),
zap.String("current_status", download.PaymentStatus),
// 检查订单状态,如果已支付则跳过
if purchaseOrder.Status == finance_entities.PurchaseOrderStatusPaid {
s.logger.Info("购买订单已支付,跳过处理",
zap.String("purchase_order_id", purchaseOrderID),
)
// 如果已经是成功状态,跳过
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),
zap.String("download_id", download.ID),
// 更新购买订单状态
purchaseOrder.MarkPaid(tradeNo, buyerID, sellerID, amount, amount)
err = s.purchaseOrderRepo.Update(ctx, purchaseOrder)
if err != nil {
s.logger.Error("更新购买订单状态失败",
zap.String("purchase_order_id", purchaseOrderID),
zap.Error(err),
)
return fmt.Errorf("更新购买订单状态失败: %w", err)
}
// 更新对应的支付订单状态(微信或支付宝)
if purchaseOrder.PayChannel == "alipay" {
alipayOrder, err := s.alipayOrderRepo.GetByRechargeID(ctx, purchaseOrderID)
if err == nil && alipayOrder != nil {
alipayOrder.MarkSuccess(tradeNo, buyerID, sellerID, amount, amount)
err = s.alipayOrderRepo.Update(ctx, *alipayOrder)
if err != nil {
s.logger.Error("更新支付宝订单状态失败",
zap.String("out_trade_no", alipayOrder.OutTradeNo),
zap.Error(err),
)
}
}
} else if purchaseOrder.PayChannel == "wechat" {
wechatOrder, err := s.wechatOrderRepo.GetByRechargeID(ctx, purchaseOrderID)
if err == nil && wechatOrder != nil {
wechatOrder.MarkSuccess(tradeNo, buyerID, sellerID, amount, amount)
err = s.wechatOrderRepo.Update(ctx, *wechatOrder)
if err != nil {
s.logger.Error("更新微信订单状态失败",
zap.String("out_trade_no", wechatOrder.OutTradeNo),
zap.Error(err),
)
}
}
}
// 如果是组件报告购买,需要生成并更新报告文件
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, purchaseOrderID)
if err == nil && download != nil {
// 创建报告生成器
zipGenerator := component_report.NewZipGenerator(s.logger)
// 生成报告文件
zipPath, err := zipGenerator.GenerateZipFile(
ctx,
download.ProductID,
[]string{download.ProductCode}, // 使用简化后的只包含主产品编号的列表
nil, // 使用默认的JSON生成器
"", // 使用默认路径
)
// 更新支付状态为成功
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),
if err != nil {
s.logger.Error("生成组件报告文件失败",
zap.String("download_id", download.ID),
zap.String("expires_at", expiresAt.Format("2006-01-02 15:04:05")),
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("out_trade_no", outTradeNo),
s.logger.Error("更新下载记录文件路径失败",
zap.String("download_id", download.ID),
zap.Error(err),
)
return
} else {
s.logger.Info("组件报告文件生成成功",
zap.String("download_id", download.ID),
zap.String("file_path", zipPath),
)
}
}
}
s.logger.Info("========== 组件报告下载记录状态更新成功 ==========",
zap.String("out_trade_no", outTradeNo),
zap.String("download_id", download.ID),
zap.String("product_id", download.ProductID),
zap.String("payment_status", download.PaymentStatus),
s.logger.Info("购买订单支付成功处理完成",
zap.String("purchase_order_id", purchaseOrderID),
zap.String("trade_no", tradeNo),
zap.String("amount", amount.String()),
)
return nil
}
// HandleWechatRefundCallback 处理微信退款回调
@@ -1842,3 +1859,163 @@ func (s *FinanceApplicationServiceImpl) HandleWechatRefundCallback(ctx context.C
return nil
}
// GetUserPurchaseRecords 获取用户购买记录
func (s *FinanceApplicationServiceImpl) GetUserPurchaseRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error) {
// 确保 filters 不为 nil
if filters == nil {
filters = make(map[string]interface{})
}
// 添加 user_id 筛选条件,确保只能查询当前用户的记录
filters["user_id"] = userID
// 获取总数
total, err := s.purchaseOrderRepo.CountByFilters(ctx, filters)
if err != nil {
s.logger.Error("统计用户购买记录失败", zap.Error(err), zap.String("userID", userID))
return nil, err
}
// 查询用户购买记录(使用筛选和分页功能)
orders, err := s.purchaseOrderRepo.GetByFilters(ctx, filters, options)
if err != nil {
s.logger.Error("查询用户购买记录失败", zap.Error(err), zap.String("userID", userID))
return nil, err
}
// 转换为响应DTO
var items []responses.PurchaseRecordResponse
for _, order := range orders {
item := responses.PurchaseRecordResponse{
ID: order.ID,
UserID: order.UserID,
OrderNo: order.OrderNo,
TradeNo: order.TradeNo,
ProductID: order.ProductID,
ProductCode: order.ProductCode,
ProductName: order.ProductName,
Category: order.Category,
Subject: order.Subject,
Amount: order.Amount,
PayAmount: order.PayAmount,
Status: string(order.Status),
Platform: order.Platform,
PayChannel: order.PayChannel,
PaymentType: order.PaymentType,
BuyerID: order.BuyerID,
SellerID: order.SellerID,
ReceiptAmount: order.ReceiptAmount,
NotifyTime: order.NotifyTime,
ReturnTime: order.ReturnTime,
PayTime: order.PayTime,
FilePath: order.FilePath,
FileSize: order.FileSize,
Remark: order.Remark,
ErrorCode: order.ErrorCode,
ErrorMessage: order.ErrorMessage,
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
}
// 获取用户信息和企业名称
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, order.UserID)
if err == nil {
companyName := "未知企业"
if user.EnterpriseInfo != nil {
companyName = user.EnterpriseInfo.CompanyName
}
item.CompanyName = companyName
item.User = &responses.UserSimpleResponse{
ID: user.ID,
CompanyName: companyName,
Phone: user.Phone,
}
}
items = append(items, item)
}
return &responses.PurchaseRecordListResponse{
Items: items,
Total: total,
Page: options.Page,
Size: options.PageSize,
}, nil
}
// GetAdminPurchaseRecords 获取管理端购买记录
func (s *FinanceApplicationServiceImpl) GetAdminPurchaseRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error) {
// 获取总数
total, err := s.purchaseOrderRepo.CountByFilters(ctx, filters)
if err != nil {
s.logger.Error("统计管理端购买记录失败", zap.Error(err))
return nil, err
}
// 查询购买记录
orders, err := s.purchaseOrderRepo.GetByFilters(ctx, filters, options)
if err != nil {
s.logger.Error("查询管理端购买记录失败", zap.Error(err))
return nil, err
}
// 转换为响应DTO
var items []responses.PurchaseRecordResponse
for _, order := range orders {
item := responses.PurchaseRecordResponse{
ID: order.ID,
UserID: order.UserID,
OrderNo: order.OrderNo,
TradeNo: order.TradeNo,
ProductID: order.ProductID,
ProductCode: order.ProductCode,
ProductName: order.ProductName,
Category: order.Category,
Subject: order.Subject,
Amount: order.Amount,
PayAmount: order.PayAmount,
Status: string(order.Status),
Platform: order.Platform,
PayChannel: order.PayChannel,
PaymentType: order.PaymentType,
BuyerID: order.BuyerID,
SellerID: order.SellerID,
ReceiptAmount: order.ReceiptAmount,
NotifyTime: order.NotifyTime,
ReturnTime: order.ReturnTime,
PayTime: order.PayTime,
FilePath: order.FilePath,
FileSize: order.FileSize,
Remark: order.Remark,
ErrorCode: order.ErrorCode,
ErrorMessage: order.ErrorMessage,
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
}
// 获取用户信息和企业名称
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, order.UserID)
if err == nil {
companyName := "未知企业"
if user.EnterpriseInfo != nil {
companyName = user.EnterpriseInfo.CompanyName
}
item.CompanyName = companyName
item.User = &responses.UserSimpleResponse{
ID: user.ID,
CompanyName: companyName,
Phone: user.Phone,
}
}
items = append(items, item)
}
return &responses.PurchaseRecordListResponse{
Items: items,
Total: total,
Page: options.Page,
Size: options.PageSize,
}, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,8 @@ type CreateProductCommand struct {
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
SubCategoryID *string `json:"sub_category_id" binding:"omitempty,uuid" comment:"二级分类ID"`
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
@@ -14,6 +15,10 @@ type CreateProductCommand struct {
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// UI组件相关字段
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格组合包使用"`
// SEO信息
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
@@ -27,7 +32,8 @@ type UpdateProductCommand struct {
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
SubCategoryID *string `json:"sub_category_id" binding:"omitempty,uuid" comment:"二级分类ID"`
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
CostPrice float64 `json:"cost_price" binding:"omitempty,min=0" comment:"成本价"`
Remark string `json:"remark" binding:"omitempty,max=1000" comment:"备注"`
@@ -35,6 +41,10 @@ type UpdateProductCommand struct {
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// UI组件相关字段
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格组合包使用"`
// SEO信息
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`

View File

@@ -0,0 +1,29 @@
package commands
// CreateSubCategoryCommand 创建二级分类命令
type CreateSubCategoryCommand struct {
Name string `json:"name" binding:"required,min=2,max=100" comment:"二级分类名称"`
Code string `json:"code" binding:"required,min=2,max=50" comment:"二级分类编号"`
Description string `json:"description" binding:"omitempty,max=500" comment:"二级分类描述"`
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
Sort int `json:"sort" binding:"min=0" comment:"排序"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
}
// UpdateSubCategoryCommand 更新二级分类命令
type UpdateSubCategoryCommand struct {
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"二级分类ID"`
Name string `json:"name" binding:"required,min=2,max=100" comment:"二级分类名称"`
Code string `json:"code" binding:"required,min=2,max=50" comment:"二级分类编号"`
Description string `json:"description" binding:"omitempty,max=500" comment:"二级分类描述"`
CategoryID string `json:"category_id" binding:"required,uuid" comment:"一级分类ID"`
Sort int `json:"sort" binding:"min=0" comment:"排序"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
}
// DeleteSubCategoryCommand 删除二级分类命令
type DeleteSubCategoryCommand struct {
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"二级分类ID"`
}

View File

@@ -10,6 +10,7 @@ type CreateSubscriptionCommand struct {
type UpdateSubscriptionPriceCommand struct {
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"订阅ID"`
Price float64 `json:"price" binding:"price,min=0" comment:"订阅价格"`
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件价格组合包使用"`
}
// BatchUpdateSubscriptionPricesCommand 批量更新订阅价格命令

View File

@@ -0,0 +1,17 @@
package queries
// GetSubCategoryQuery 获取二级分类查询
type GetSubCategoryQuery struct {
ID string `json:"id" form:"id" binding:"omitempty,uuid" comment:"二级分类ID"`
}
// ListSubCategoriesQuery 获取二级分类列表查询
type ListSubCategoriesQuery struct {
Page int `json:"page" form:"page" binding:"min=1" comment:"页码"`
PageSize int `json:"page_size" form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
CategoryID string `json:"category_id" form:"category_id" binding:"omitempty,uuid" comment:"一级分类ID"`
IsEnabled *bool `json:"is_enabled" form:"is_enabled" comment:"是否启用"`
IsVisible *bool `json:"is_visible" form:"is_visible" comment:"是否展示"`
SortBy string `json:"sort_by" form:"sort_by" comment:"排序字段"`
SortOrder string `json:"sort_order" form:"sort_order" comment:"排序方向"`
}

View File

@@ -30,3 +30,37 @@ type CategorySimpleResponse struct {
Name string `json:"name" comment:"分类名称"`
Code string `json:"code" comment:"分类编号"`
}
// SubCategoryInfoResponse 二级分类详情响应
type SubCategoryInfoResponse struct {
ID string `json:"id" comment:"二级分类ID"`
Name string `json:"name" comment:"二级分类名称"`
Code string `json:"code" comment:"二级分类编号"`
Description string `json:"description" comment:"二级分类描述"`
CategoryID string `json:"category_id" comment:"一级分类ID"`
Sort int `json:"sort" comment:"排序"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
// 关联信息
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
}
// SubCategoryListResponse 二级分类列表响应
type SubCategoryListResponse struct {
Total int64 `json:"total" comment:"总数"`
Page int `json:"page" comment:"页码"`
Size int `json:"size" comment:"每页数量"`
Items []SubCategoryInfoResponse `json:"items" comment:"二级分类列表"`
}
// SubCategorySimpleResponse 二级分类简单信息响应
type SubCategorySimpleResponse struct {
ID string `json:"id" comment:"二级分类ID"`
Name string `json:"name" comment:"二级分类名称"`
Code string `json:"code" comment:"二级分类编号"`
CategoryID string `json:"category_id" comment:"一级分类ID"`
}

View File

@@ -21,11 +21,15 @@ type ProductInfoResponse struct {
Code string `json:"code" comment:"产品编号"`
Description string `json:"description" comment:"产品简介"`
Content string `json:"content" comment:"产品内容"`
CategoryID string `json:"category_id" comment:"产品分类ID"`
CategoryID string `json:"category_id" comment:"一级分类ID"`
SubCategoryID *string `json:"sub_category_id,omitempty" comment:"二级分类ID"`
Price float64 `json:"price" comment:"产品价格"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
// UI组件相关字段
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格组合包使用"`
// SEO信息
SEOTitle string `json:"seo_title" comment:"SEO标题"`
@@ -33,7 +37,8 @@ type ProductInfoResponse struct {
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
// 关联信息
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
SubCategory *SubCategoryInfoResponse `json:"sub_category,omitempty" comment:"二级分类信息"`
// 组合包信息
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
@@ -75,6 +80,7 @@ type ProductSimpleResponse struct {
type ProductSimpleAdminResponse struct {
ProductSimpleResponse
CostPrice float64 `json:"cost_price" comment:"成本价"`
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件价格组合包使用"`
}
// ProductStatsResponse 产品统计响应
@@ -93,7 +99,8 @@ type ProductAdminInfoResponse struct {
Code string `json:"code" comment:"产品编号"`
Description string `json:"description" comment:"产品简介"`
Content string `json:"content" comment:"产品内容"`
CategoryID string `json:"category_id" comment:"产品分类ID"`
CategoryID string `json:"category_id" comment:"一级分类ID"`
SubCategoryID *string `json:"sub_category_id,omitempty" comment:"二级分类ID"`
Price float64 `json:"price" comment:"产品价格"`
CostPrice float64 `json:"cost_price" comment:"成本价"`
Remark string `json:"remark" comment:"备注"`
@@ -101,13 +108,18 @@ type ProductAdminInfoResponse struct {
IsVisible bool `json:"is_visible" comment:"是否可见"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// UI组件相关字段
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格组合包使用"`
// SEO信息
SEOTitle string `json:"seo_title" comment:"SEO标题"`
SEODescription string `json:"seo_description" comment:"SEO描述"`
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
// 关联信息
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
SubCategory *SubCategoryInfoResponse `json:"sub_category,omitempty" comment:"二级分类信息"`
// 组合包信息
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`

View File

@@ -17,6 +17,7 @@ type SubscriptionInfoResponse struct {
UserID string `json:"user_id" comment:"用户ID"`
ProductID string `json:"product_id" comment:"产品ID"`
Price float64 `json:"price" comment:"订阅价格"`
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件价格组合包使用"`
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
// 关联信息

View File

@@ -50,7 +50,7 @@ func NewProductApplicationService(
}
// CreateProduct 创建产品
// 业务流程<EFBFBD>?. 构建产品实体 2. 创建产品
// 业务流程1. 构建产品实体 2. 创建产品
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
// 1. 构建产品实体
product := &entities.Product{
@@ -59,12 +59,15 @@ func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *
Description: cmd.Description,
Content: cmd.Content,
CategoryID: cmd.CategoryID,
SubCategoryID: cmd.SubCategoryID,
Price: decimal.NewFromFloat(cmd.Price),
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
Remark: cmd.Remark,
IsEnabled: cmd.IsEnabled,
IsVisible: cmd.IsVisible,
IsPackage: cmd.IsPackage,
SellUIComponent: cmd.SellUIComponent,
UIComponentPrice: decimal.NewFromFloat(cmd.UIComponentPrice),
SEOTitle: cmd.SEOTitle,
SEODescription: cmd.SEODescription,
SEOKeywords: cmd.SEOKeywords,
@@ -95,12 +98,15 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
existingProduct.Description = cmd.Description
existingProduct.Content = cmd.Content
existingProduct.CategoryID = cmd.CategoryID
existingProduct.SubCategoryID = cmd.SubCategoryID
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
existingProduct.CostPrice = decimal.NewFromFloat(cmd.CostPrice)
existingProduct.Remark = cmd.Remark
existingProduct.IsEnabled = cmd.IsEnabled
existingProduct.IsVisible = cmd.IsVisible
existingProduct.IsPackage = cmd.IsPackage
existingProduct.SellUIComponent = cmd.SellUIComponent
existingProduct.UIComponentPrice = decimal.NewFromFloat(cmd.UIComponentPrice)
existingProduct.SEOTitle = cmd.SEOTitle
existingProduct.SEODescription = cmd.SEODescription
existingProduct.SEOKeywords = cmd.SEOKeywords
@@ -493,9 +499,12 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
Description: product.Description,
Content: product.Content,
CategoryID: product.CategoryID,
SubCategoryID: product.SubCategoryID,
Price: product.Price.InexactFloat64(),
IsEnabled: product.IsEnabled,
IsPackage: product.IsPackage,
SellUIComponent: product.SellUIComponent,
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
SEOTitle: product.SEOTitle,
SEODescription: product.SEODescription,
SEOKeywords: product.SEOKeywords,
@@ -503,11 +512,16 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
UpdatedAt: product.UpdatedAt,
}
// 添加分类信息
// 添加一级分类信息
if product.Category != nil {
response.Category = s.convertToCategoryInfoResponse(product.Category)
}
// 添加二级分类信息
if product.SubCategory != nil {
response.SubCategory = s.convertToSubCategoryInfoResponse(product.SubCategory)
}
// 转换组合包项目信息
if product.IsPackage && len(product.PackageItems) > 0 {
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
@@ -537,12 +551,15 @@ func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(produc
Description: product.Description,
Content: product.Content,
CategoryID: product.CategoryID,
SubCategoryID: product.SubCategoryID,
Price: product.Price.InexactFloat64(),
CostPrice: product.CostPrice.InexactFloat64(),
Remark: product.Remark,
IsEnabled: product.IsEnabled,
IsVisible: product.IsVisible, // 管理员可以看到可见状态
IsPackage: product.IsPackage,
SellUIComponent: product.SellUIComponent,
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
SEOTitle: product.SEOTitle,
SEODescription: product.SEODescription,
SEOKeywords: product.SEOKeywords,
@@ -550,11 +567,16 @@ func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(produc
UpdatedAt: product.UpdatedAt,
}
// 添加分类信息
// 添加一级分类信息
if product.Category != nil {
response.Category = s.convertToCategoryInfoResponse(product.Category)
}
// 添加二级分类信息
if product.SubCategory != nil {
response.SubCategory = s.convertToSubCategoryInfoResponse(product.SubCategory)
}
// 转换组合包项目信息
if product.IsPackage && len(product.PackageItems) > 0 {
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
@@ -586,6 +608,28 @@ func (s *ProductApplicationServiceImpl) convertToCategoryInfoResponse(category *
}
}
func (s *ProductApplicationServiceImpl) convertToSubCategoryInfoResponse(subCategory *entities.ProductSubCategory) *responses.SubCategoryInfoResponse {
response := &responses.SubCategoryInfoResponse{
ID: subCategory.ID,
Name: subCategory.Name,
Code: subCategory.Code,
Description: subCategory.Description,
CategoryID: subCategory.CategoryID,
Sort: subCategory.Sort,
IsEnabled: subCategory.IsEnabled,
IsVisible: subCategory.IsVisible,
CreatedAt: subCategory.CreatedAt,
UpdatedAt: subCategory.UpdatedAt,
}
// 添加一级分类信息
if subCategory.Category != nil {
response.Category = s.convertToCategoryInfoResponse(subCategory.Category)
}
return response
}
// GetProductApiConfig 获取产品API配置
func (s *ProductApplicationServiceImpl) GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error) {
return s.productApiConfigAppService.GetProductApiConfig(ctx, productID)

View File

@@ -0,0 +1,20 @@
package product
import (
"context"
"tyapi-server/internal/application/product/dto/commands"
"tyapi-server/internal/application/product/dto/queries"
"tyapi-server/internal/application/product/dto/responses"
)
// SubCategoryApplicationService 二级分类应用服务接口
type SubCategoryApplicationService interface {
// 二级分类管理
CreateSubCategory(ctx context.Context, cmd *commands.CreateSubCategoryCommand) error
UpdateSubCategory(ctx context.Context, cmd *commands.UpdateSubCategoryCommand) error
DeleteSubCategory(ctx context.Context, cmd *commands.DeleteSubCategoryCommand) error
GetSubCategoryByID(ctx context.Context, query *queries.GetSubCategoryQuery) (*responses.SubCategoryInfoResponse, error)
ListSubCategories(ctx context.Context, query *queries.ListSubCategoriesQuery) (*responses.SubCategoryListResponse, error)
ListSubCategoriesByCategoryID(ctx context.Context, categoryID string) ([]*responses.SubCategorySimpleResponse, error)
}

View File

@@ -0,0 +1,322 @@
package product
import (
"context"
"errors"
"fmt"
"tyapi-server/internal/application/product/dto/commands"
"tyapi-server/internal/application/product/dto/queries"
"tyapi-server/internal/application/product/dto/responses"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
"go.uber.org/zap"
)
// SubCategoryApplicationServiceImpl 二级分类应用服务实现
type SubCategoryApplicationServiceImpl struct {
categoryRepo repositories.ProductCategoryRepository
subCategoryRepo repositories.ProductSubCategoryRepository
logger *zap.Logger
}
// NewSubCategoryApplicationService 创建二级分类应用服务
func NewSubCategoryApplicationService(
categoryRepo repositories.ProductCategoryRepository,
subCategoryRepo repositories.ProductSubCategoryRepository,
logger *zap.Logger,
) SubCategoryApplicationService {
return &SubCategoryApplicationServiceImpl{
categoryRepo: categoryRepo,
subCategoryRepo: subCategoryRepo,
logger: logger,
}
}
// CreateSubCategory 创建二级分类
func (s *SubCategoryApplicationServiceImpl) CreateSubCategory(ctx context.Context, cmd *commands.CreateSubCategoryCommand) error {
// 1. 参数验证
if err := s.validateCreateSubCategory(cmd); err != nil {
return err
}
// 2. 验证一级分类是否存在
category, err := s.categoryRepo.GetByID(ctx, cmd.CategoryID)
if err != nil {
return fmt.Errorf("一级分类不存在: %w", err)
}
if !category.IsValid() {
return errors.New("一级分类已禁用或删除")
}
// 3. 验证二级分类编号唯一性
if err := s.validateSubCategoryCode(cmd.Code, "", cmd.CategoryID); err != nil {
return err
}
// 4. 创建二级分类实体
subCategory := &entities.ProductSubCategory{
Name: cmd.Name,
Code: cmd.Code,
Description: cmd.Description,
CategoryID: cmd.CategoryID,
Sort: cmd.Sort,
IsEnabled: cmd.IsEnabled,
IsVisible: cmd.IsVisible,
}
// 5. 保存到仓储
createdSubCategory, err := s.subCategoryRepo.Create(ctx, *subCategory)
if err != nil {
s.logger.Error("创建二级分类失败", zap.Error(err), zap.String("code", cmd.Code))
return fmt.Errorf("创建二级分类失败: %w", err)
}
s.logger.Info("创建二级分类成功", zap.String("id", createdSubCategory.ID), zap.String("code", cmd.Code))
return nil
}
// UpdateSubCategory 更新二级分类
func (s *SubCategoryApplicationServiceImpl) UpdateSubCategory(ctx context.Context, cmd *commands.UpdateSubCategoryCommand) error {
// 1. 参数验证
if err := s.validateUpdateSubCategory(cmd); err != nil {
return err
}
// 2. 获取现有二级分类
existingSubCategory, err := s.subCategoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("二级分类不存在: %w", err)
}
// 3. 验证一级分类是否存在
category, err := s.categoryRepo.GetByID(ctx, cmd.CategoryID)
if err != nil {
return fmt.Errorf("一级分类不存在: %w", err)
}
if !category.IsValid() {
return errors.New("一级分类已禁用或删除")
}
// 4. 验证二级分类编号唯一性(排除当前分类)
if err := s.validateSubCategoryCode(cmd.Code, cmd.ID, cmd.CategoryID); err != nil {
return err
}
// 5. 更新二级分类信息
existingSubCategory.Name = cmd.Name
existingSubCategory.Code = cmd.Code
existingSubCategory.Description = cmd.Description
existingSubCategory.CategoryID = cmd.CategoryID
existingSubCategory.Sort = cmd.Sort
existingSubCategory.IsEnabled = cmd.IsEnabled
existingSubCategory.IsVisible = cmd.IsVisible
// 6. 保存到仓储
if err := s.subCategoryRepo.Update(ctx, *existingSubCategory); err != nil {
s.logger.Error("更新二级分类失败", zap.Error(err), zap.String("id", cmd.ID))
return fmt.Errorf("更新二级分类失败: %w", err)
}
s.logger.Info("更新二级分类成功", zap.String("id", cmd.ID), zap.String("code", cmd.Code))
return nil
}
// DeleteSubCategory 删除二级分类
func (s *SubCategoryApplicationServiceImpl) DeleteSubCategory(ctx context.Context, cmd *commands.DeleteSubCategoryCommand) error {
// 1. 检查二级分类是否存在
existingSubCategory, err := s.subCategoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("二级分类不存在: %w", err)
}
// 2. 删除二级分类
if err := s.subCategoryRepo.Delete(ctx, cmd.ID); err != nil {
s.logger.Error("删除二级分类失败", zap.Error(err), zap.String("id", cmd.ID))
return fmt.Errorf("删除二级分类失败: %w", err)
}
s.logger.Info("删除二级分类成功", zap.String("id", cmd.ID), zap.String("code", existingSubCategory.Code))
return nil
}
// GetSubCategoryByID 根据ID获取二级分类
func (s *SubCategoryApplicationServiceImpl) GetSubCategoryByID(ctx context.Context, query *queries.GetSubCategoryQuery) (*responses.SubCategoryInfoResponse, error) {
subCategory, err := s.subCategoryRepo.GetByID(ctx, query.ID)
if err != nil {
return nil, fmt.Errorf("二级分类不存在: %w", err)
}
// 加载一级分类信息
if subCategory.CategoryID != "" {
category, err := s.categoryRepo.GetByID(ctx, subCategory.CategoryID)
if err == nil {
subCategory.Category = &category
}
}
// 转换为响应对象
response := s.convertToSubCategoryInfoResponse(subCategory)
return response, nil
}
// ListSubCategories 获取二级分类列表
func (s *SubCategoryApplicationServiceImpl) ListSubCategories(ctx context.Context, query *queries.ListSubCategoriesQuery) (*responses.SubCategoryListResponse, error) {
// 构建查询条件
categoryID := query.CategoryID
isEnabled := query.IsEnabled
isVisible := query.IsVisible
var subCategories []*entities.ProductSubCategory
var err error
// 根据条件查询
if categoryID != "" {
// 按一级分类查询
subCategories, err = s.subCategoryRepo.FindByCategoryID(ctx, categoryID)
} else {
// 查询所有二级分类
subCategories, err = s.subCategoryRepo.List(ctx)
}
if err != nil {
s.logger.Error("获取二级分类列表失败", zap.Error(err))
return nil, fmt.Errorf("获取二级分类列表失败: %w", err)
}
// 过滤状态
filteredSubCategories := make([]*entities.ProductSubCategory, 0)
for _, subCategory := range subCategories {
if isEnabled != nil && *isEnabled != subCategory.IsEnabled {
continue
}
if isVisible != nil && *isVisible != subCategory.IsVisible {
continue
}
filteredSubCategories = append(filteredSubCategories, subCategory)
}
// 加载一级分类信息
for _, subCategory := range filteredSubCategories {
if subCategory.CategoryID != "" {
category, err := s.categoryRepo.GetByID(ctx, subCategory.CategoryID)
if err == nil {
subCategory.Category = &category
}
}
}
// 转换为响应对象
items := make([]responses.SubCategoryInfoResponse, len(filteredSubCategories))
for i, subCategory := range filteredSubCategories {
items[i] = *s.convertToSubCategoryInfoResponse(subCategory)
}
return &responses.SubCategoryListResponse{
Total: int64(len(items)),
Page: query.Page,
Size: query.PageSize,
Items: items,
}, nil
}
// ListSubCategoriesByCategoryID 根据一级分类ID获取二级分类列表
func (s *SubCategoryApplicationServiceImpl) ListSubCategoriesByCategoryID(ctx context.Context, categoryID string) ([]*responses.SubCategorySimpleResponse, error) {
subCategories, err := s.subCategoryRepo.FindByCategoryID(ctx, categoryID)
if err != nil {
return nil, fmt.Errorf("获取二级分类列表失败: %w", err)
}
// 转换为响应对象
items := make([]*responses.SubCategorySimpleResponse, len(subCategories))
for i, subCategory := range subCategories {
items[i] = &responses.SubCategorySimpleResponse{
ID: subCategory.ID,
Name: subCategory.Name,
Code: subCategory.Code,
CategoryID: subCategory.CategoryID,
}
}
return items, nil
}
// convertToSubCategoryInfoResponse 转换为二级分类信息响应
func (s *SubCategoryApplicationServiceImpl) convertToSubCategoryInfoResponse(subCategory *entities.ProductSubCategory) *responses.SubCategoryInfoResponse {
response := &responses.SubCategoryInfoResponse{
ID: subCategory.ID,
Name: subCategory.Name,
Code: subCategory.Code,
Description: subCategory.Description,
CategoryID: subCategory.CategoryID,
Sort: subCategory.Sort,
IsEnabled: subCategory.IsEnabled,
IsVisible: subCategory.IsVisible,
CreatedAt: subCategory.CreatedAt,
UpdatedAt: subCategory.UpdatedAt,
}
// 添加一级分类信息
if subCategory.Category != nil {
response.Category = &responses.CategoryInfoResponse{
ID: subCategory.Category.ID,
Name: subCategory.Category.Name,
Description: subCategory.Category.Description,
Sort: subCategory.Category.Sort,
IsEnabled: subCategory.Category.IsEnabled,
IsVisible: subCategory.Category.IsVisible,
CreatedAt: subCategory.Category.CreatedAt,
UpdatedAt: subCategory.Category.UpdatedAt,
}
}
return response
}
// validateCreateSubCategory 验证创建二级分类参数
func (s *SubCategoryApplicationServiceImpl) validateCreateSubCategory(cmd *commands.CreateSubCategoryCommand) error {
if cmd.Name == "" {
return errors.New("二级分类名称不能为空")
}
if cmd.Code == "" {
return errors.New("二级分类编号不能为空")
}
if cmd.CategoryID == "" {
return errors.New("一级分类ID不能为空")
}
return nil
}
// validateUpdateSubCategory 验证更新二级分类参数
func (s *SubCategoryApplicationServiceImpl) validateUpdateSubCategory(cmd *commands.UpdateSubCategoryCommand) error {
if cmd.ID == "" {
return errors.New("二级分类ID不能为空")
}
if cmd.Name == "" {
return errors.New("二级分类名称不能为空")
}
if cmd.Code == "" {
return errors.New("二级分类编号不能为空")
}
if cmd.CategoryID == "" {
return errors.New("一级分类ID不能为空")
}
return nil
}
// validateSubCategoryCode 验证二级分类编号唯一性
func (s *SubCategoryApplicationServiceImpl) validateSubCategoryCode(code, excludeID, categoryID string) error {
if code == "" {
return errors.New("二级分类编号不能为空")
}
existingSubCategory, err := s.subCategoryRepo.FindByCode(context.Background(), code)
if err == nil && existingSubCategory != nil && existingSubCategory.ID != excludeID {
// 如果指定了分类ID检查是否在同一分类下
if categoryID == "" || existingSubCategory.CategoryID == categoryID {
return errors.New("二级分类编号已存在")
}
}
return nil
}

View File

@@ -44,7 +44,7 @@ func NewSubscriptionApplicationService(
// UpdateSubscriptionPrice 更新订阅价格
// 业务流程1. 获取订阅 2. 更新价格 3. 保存订阅
func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error {
return s.productSubscriptionService.UpdateSubscriptionPrice(ctx, cmd.ID, cmd.Price)
return s.productSubscriptionService.UpdateSubscriptionPriceWithUIComponent(ctx, cmd.ID, cmd.Price, cmd.UIComponentPrice)
}
// BatchUpdateSubscriptionPrices 一键改价
@@ -377,11 +377,18 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(s
productResponse = s.convertToProductSimpleResponse(subscription.Product)
}
// 获取UI组件价格如果订阅中没有设置则从产品中获取
uiComponentPrice := subscription.UIComponentPrice.InexactFloat64()
if uiComponentPrice == 0 && subscription.Product != nil && (subscription.Product.IsPackage) {
uiComponentPrice = subscription.Product.UIComponentPrice.InexactFloat64()
}
return &responses.SubscriptionInfoResponse{
ID: subscription.ID,
UserID: subscription.UserID,
ProductID: subscription.ProductID,
Price: subscription.Price.InexactFloat64(),
UIComponentPrice: uiComponentPrice,
User: userInfo,
Product: productResponse,
APIUsed: subscription.APIUsed,
@@ -433,11 +440,18 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponseFo
productAdminResponse = s.convertToProductSimpleAdminResponse(subscription.Product)
}
// 获取UI组件价格如果订阅中没有设置则从产品中获取
uiComponentPrice := subscription.UIComponentPrice.InexactFloat64()
if uiComponentPrice == 0 && subscription.Product != nil && (subscription.Product.IsPackage) {
uiComponentPrice = subscription.Product.UIComponentPrice.InexactFloat64()
}
return &responses.SubscriptionInfoResponse{
ID: subscription.ID,
UserID: subscription.UserID,
ProductID: subscription.ProductID,
Price: subscription.Price.InexactFloat64(),
UIComponentPrice: uiComponentPrice,
User: userInfo,
ProductAdmin: productAdminResponse,
APIUsed: subscription.APIUsed,
@@ -465,6 +479,7 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleAdminResponse
IsPackage: product.IsPackage,
},
CostPrice: product.CostPrice.InexactFloat64(),
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
}
}

View File

@@ -7,11 +7,13 @@ import (
"mime/multipart"
"path/filepath"
"strings"
"time"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
// UIComponentApplicationService UI组件应用服务接口
@@ -93,6 +95,7 @@ type UIComponentApplicationServiceImpl struct {
productUIComponentRepo repositories.ProductUIComponentRepository
fileStorageService FileStorageService
fileService UIComponentFileService
logger *zap.Logger
}
// FileStorageService 文件存储服务接口
@@ -108,12 +111,14 @@ func NewUIComponentApplicationService(
productUIComponentRepo repositories.ProductUIComponentRepository,
fileStorageService FileStorageService,
fileService UIComponentFileService,
logger *zap.Logger,
) UIComponentApplicationService {
return &UIComponentApplicationServiceImpl{
uiComponentRepo: uiComponentRepo,
productUIComponentRepo: productUIComponentRepo,
fileStorageService: fileStorageService,
fileService: fileService,
logger: logger,
}
}
@@ -182,10 +187,14 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFile(ctx contex
fileType := strings.ToLower(filepath.Ext(file.Filename))
// 更新组件信息
folderPath := "resources/Pure Component/src/ui"
folderPath := "resources/Pure_Component/src/ui"
createdComponent.FolderPath = &folderPath
createdComponent.FileType = &fileType
// 记录文件上传时间
now := time.Now()
createdComponent.FileUploadTime = &now
// 仅对ZIP文件设置已解压标记
if fileType == ".zip" {
createdComponent.IsExtracted = true
@@ -255,9 +264,13 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFiles(ctx conte
}
// 更新组件信息
folderPath := "resources/Pure Component/src/ui"
folderPath := "resources/Pure_Component/src/ui"
createdComponent.FolderPath = &folderPath
// 记录文件上传时间
now := time.Now()
createdComponent.FileUploadTime = &now
// 检查是否有ZIP文件
hasZipFile := false
for _, fileHeader := range files {
@@ -363,9 +376,13 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFilesAndPaths(c
}
// 更新组件信息
folderPath := "resources/Pure Component/src/ui"
folderPath := "resources/Pure_Component/src/ui"
createdComponent.FolderPath = &folderPath
// 记录文件上传时间
now := time.Now()
createdComponent.FileUploadTime = &now
// 检查是否有ZIP文件
hasZipFile := false
for _, fileHeader := range files {
@@ -442,20 +459,56 @@ func (s *UIComponentApplicationServiceImpl) UpdateUIComponent(ctx context.Contex
// DeleteUIComponent 删除UI组件
func (s *UIComponentApplicationServiceImpl) DeleteUIComponent(ctx context.Context, id string) error {
// 获取组件信息
component, err := s.uiComponentRepo.GetByID(ctx, id)
if err != nil {
return err
s.logger.Error("获取UI组件失败", zap.Error(err), zap.String("id", id))
return fmt.Errorf("获取UI组件失败: %w", err)
}
if component == nil {
s.logger.Warn("UI组件不存在", zap.String("id", id))
return ErrComponentNotFound
}
// 删除关联的文件
if component.FilePath != nil {
_ = s.fileStorageService.DeleteFile(ctx, *component.FilePath)
// 记录组件信息
s.logger.Info("开始删除UI组件",
zap.String("id", id),
zap.String("componentCode", component.ComponentCode),
zap.String("componentName", component.ComponentName),
zap.Bool("isExtracted", component.IsExtracted),
zap.Any("filePath", component.FilePath),
zap.Any("folderPath", component.FolderPath))
// 使用智能删除方法,根据组件编码和上传时间删除相关文件
if err := s.fileService.DeleteFilesByComponentCode(component.ComponentCode, component.FileUploadTime); err != nil {
// 记录错误但不阻止删除数据库记录
s.logger.Error("删除组件文件失败",
zap.Error(err),
zap.String("componentCode", component.ComponentCode),
zap.Any("fileUploadTime", component.FileUploadTime))
}
return s.uiComponentRepo.Delete(ctx, id)
// 删除关联的文件(FilePath指向的文件)
if component.FilePath != nil {
if err := s.fileStorageService.DeleteFile(ctx, *component.FilePath); err != nil {
s.logger.Error("删除文件失败",
zap.Error(err),
zap.String("filePath", *component.FilePath))
}
}
// 删除数据库记录
if err := s.uiComponentRepo.Delete(ctx, id); err != nil {
s.logger.Error("删除UI组件数据库记录失败",
zap.Error(err),
zap.String("id", id))
return fmt.Errorf("删除UI组件数据库记录失败: %w", err)
}
s.logger.Info("UI组件删除成功",
zap.String("id", id),
zap.String("componentCode", component.ComponentCode))
return nil
}
// ListUIComponents 获取UI组件列表
@@ -634,10 +687,14 @@ func (s *UIComponentApplicationServiceImpl) UploadAndExtractUIComponentFile(ctx
fileType := strings.ToLower(filepath.Ext(file.Filename))
// 更新组件信息
folderPath := "resources/Pure Component/src/ui"
folderPath := "resources/Pure_Component/src/ui"
component.FolderPath = &folderPath
component.FileType = &fileType
// 记录文件上传时间
now := time.Now()
component.FileUploadTime = &now
// 仅对ZIP文件设置已解压标记
if fileType == ".zip" {
component.IsExtracted = true

View File

@@ -32,6 +32,9 @@ type UIComponentFileService interface {
// 获取文件夹内容
GetFolderContent(folderPath string) ([]FileInfo, error)
// 根据组件编码和上传时间智能删除组件相关文件
DeleteFilesByComponentCode(componentCode string, uploadTime *time.Time) error
}
// FileInfo 文件信息
@@ -222,11 +225,34 @@ func (s *UIComponentFileServiceImpl) CreateFolderByCode(componentCode string) (s
// DeleteFolder 删除组件文件夹
func (s *UIComponentFileServiceImpl) DeleteFolder(folderPath string) error {
// 记录尝试删除的文件夹路径
s.logger.Info("尝试删除文件夹", zap.String("folderPath", folderPath))
// 获取文件夹信息,用于调试
if info, err := os.Stat(folderPath); err == nil {
s.logger.Info("文件夹信息",
zap.String("folderPath", folderPath),
zap.Bool("isDir", info.IsDir()),
zap.Int64("size", info.Size()),
zap.Time("modTime", info.ModTime()))
} else {
s.logger.Error("获取文件夹信息失败",
zap.Error(err),
zap.String("folderPath", folderPath))
}
// 检查文件夹是否存在
if !s.FolderExists(folderPath) {
s.logger.Info("文件夹不存在", zap.String("folderPath", folderPath))
return nil // 文件夹不存在,不视为错误
}
// 尝试删除文件夹
s.logger.Info("开始删除文件夹", zap.String("folderPath", folderPath))
if err := os.RemoveAll(folderPath); err != nil {
s.logger.Error("删除文件夹失败",
zap.Error(err),
zap.String("folderPath", folderPath))
return fmt.Errorf("删除文件夹失败: %w", err)
}
@@ -339,3 +365,95 @@ func (s *UIComponentFileServiceImpl) extractZipFile(zipPath, destPath string) er
return nil
}
// DeleteFilesByComponentCode 根据组件编码和上传时间智能删除组件相关文件
func (s *UIComponentFileServiceImpl) DeleteFilesByComponentCode(componentCode string, uploadTime *time.Time) error {
// 记录基础路径和组件编码
s.logger.Info("开始删除组件文件",
zap.String("basePath", s.basePath),
zap.String("componentCode", componentCode),
zap.Any("uploadTime", uploadTime))
// 1. 查找名为组件编码的文件夹
componentDir := filepath.Join(s.basePath, componentCode)
s.logger.Info("检查组件文件夹", zap.String("componentDir", componentDir))
if s.FolderExists(componentDir) {
s.logger.Info("找到组件文件夹,开始删除", zap.String("componentDir", componentDir))
if err := s.DeleteFolder(componentDir); err != nil {
s.logger.Error("删除组件文件夹失败",
zap.Error(err),
zap.String("componentCode", componentCode),
zap.String("componentDir", componentDir))
return fmt.Errorf("删除组件文件夹失败: %w", err)
}
s.logger.Info("成功删除组件文件夹", zap.String("componentCode", componentCode))
return nil
} else {
s.logger.Info("组件文件夹不存在", zap.String("componentDir", componentDir))
}
// 2. 查找文件名包含组件编码的文件
pattern := filepath.Join(s.basePath, "*"+componentCode+"*")
s.logger.Info("查找匹配文件", zap.String("pattern", pattern))
files, err := filepath.Glob(pattern)
if err != nil {
s.logger.Error("查找组件文件失败",
zap.Error(err),
zap.String("pattern", pattern))
return fmt.Errorf("查找组件文件失败: %w", err)
}
s.logger.Info("找到匹配文件",
zap.Strings("files", files),
zap.Int("count", len(files)))
// 3. 如果没有上传时间,删除所有匹配的文件
if uploadTime == nil {
for _, file := range files {
if err := os.Remove(file); err != nil {
s.logger.Error("删除文件失败", zap.String("file", file), zap.Error(err))
} else {
s.logger.Info("成功删除文件", zap.String("file", file))
}
}
return nil
}
// 4. 如果有上传时间,根据文件修改时间和上传时间的匹配度来删除文件
var deletedFiles []string
for _, file := range files {
// 获取文件信息
fileInfo, err := os.Stat(file)
if err != nil {
s.logger.Warn("获取文件信息失败", zap.String("file", file), zap.Error(err))
continue
}
// 计算文件修改时间与上传时间的差异(以秒为单位)
timeDiff := fileInfo.ModTime().Sub(*uploadTime).Seconds()
// 如果时间差在60秒内认为是最匹配的文件
if timeDiff < 60 && timeDiff > -60 {
if err := os.Remove(file); err != nil {
s.logger.Warn("删除文件失败", zap.String("file", file), zap.Error(err))
} else {
deletedFiles = append(deletedFiles, file)
s.logger.Info("成功删除文件", zap.String("file", file),
zap.Time("uploadTime", *uploadTime),
zap.Time("fileModTime", fileInfo.ModTime()))
}
}
}
// 如果没有找到匹配的文件,记录警告但返回成功
if len(deletedFiles) == 0 && len(files) > 0 {
s.logger.Warn("没有找到匹配时间戳的文件",
zap.String("componentCode", componentCode),
zap.Time("uploadTime", *uploadTime),
zap.Int("foundFiles", len(files)))
}
return nil
}

View File

@@ -2772,16 +2772,17 @@ func (s *StatisticsApplicationServiceImpl) AdminGetTodayCertifiedEnterprises(ctx
var enterprises []map[string]interface{}
for _, cert := range completedCertifications {
// 获取企业信息
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, cert.UserID)
// 使用预加载方法一次性获取用户和企业信息
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, cert.UserID)
if err != nil {
s.logger.Warn("获取企业信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
s.logger.Warn("获取用户信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
continue
}
// 获取用户基本信息(仅需要用户名)
user, err := s.userRepo.GetByID(ctx, cert.UserID)
if err != nil {
s.logger.Warn("获取用户信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
// 获取企业信息
enterpriseInfo := user.EnterpriseInfo
if enterpriseInfo == nil {
s.logger.Warn("用户没有企业信息", zap.String("user_id", cert.UserID))
continue
}

View File

@@ -38,6 +38,8 @@ type Config struct {
TianYanCha TianYanChaConfig `mapstructure:"tianyancha"`
Alicloud AlicloudConfig `mapstructure:"alicloud"`
Xingwei XingweiConfig `mapstructure:"xingwei"`
Jiguang JiguangConfig `mapstructure:"jiguang"`
Shumai ShumaiConfig `mapstructure:"shumai"`
}
// ServerConfig HTTP服务器配置
@@ -520,6 +522,65 @@ type XingweiLevelFileConfig struct {
Compress bool `mapstructure:"compress"`
}
// JiguangConfig 极光配置
type JiguangConfig struct {
URL string `mapstructure:"url"`
AppID string `mapstructure:"app_id"`
AppSecret string `mapstructure:"app_secret"`
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac默认 hmac
Timeout time.Duration `mapstructure:"timeout"`
// 极光日志配置
Logging JiguangLoggingConfig `mapstructure:"logging"`
}
// JiguangLoggingConfig 极光日志配置
type JiguangLoggingConfig struct {
Enabled bool `mapstructure:"enabled"`
LogDir string `mapstructure:"log_dir"`
UseDaily bool `mapstructure:"use_daily"`
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
LevelConfigs map[string]JiguangLevelFileConfig `mapstructure:"level_configs"`
}
// JiguangLevelFileConfig 极光级别文件配置
type JiguangLevelFileConfig struct {
MaxSize int `mapstructure:"max_size"`
MaxBackups int `mapstructure:"max_backups"`
MaxAge int `mapstructure:"max_age"`
Compress bool `mapstructure:"compress"`
}
// ShumaiConfig 数脉配置
type ShumaiConfig struct {
URL string `mapstructure:"url"`
AppID string `mapstructure:"app_id"`
AppSecret string `mapstructure:"app_secret"`
AppID2 string `mapstructure:"app_id2"` // 走政务接口使用这个
AppSecret2 string `mapstructure:"app_secret2"` // 走政务接口使用这个
SignMethod string `mapstructure:"sign_method"` // md5 或 hmac默认 hmac
Timeout time.Duration `mapstructure:"timeout"`
Logging ShumaiLoggingConfig `mapstructure:"logging"`
}
// ShumaiLoggingConfig 数脉日志配置
type ShumaiLoggingConfig struct {
Enabled bool `mapstructure:"enabled"`
LogDir string `mapstructure:"log_dir"`
UseDaily bool `mapstructure:"use_daily"`
EnableLevelSeparation bool `mapstructure:"enable_level_separation"`
LevelConfigs map[string]ShumaiLevelFileConfig `mapstructure:"level_configs"`
}
// ShumaiLevelFileConfig 数脉级别文件配置
type ShumaiLevelFileConfig struct {
MaxSize int `mapstructure:"max_size"`
MaxBackups int `mapstructure:"max_backups"`
MaxAge int `mapstructure:"max_age"`
Compress bool `mapstructure:"compress"`
}
// DomainConfig 域名配置
type DomainConfig struct {
API string `mapstructure:"api"` // API域名

View File

@@ -39,8 +39,10 @@ import (
infra_events "tyapi-server/internal/infrastructure/events"
"tyapi-server/internal/infrastructure/external/alicloud"
"tyapi-server/internal/infrastructure/external/email"
"tyapi-server/internal/infrastructure/external/jiguang"
"tyapi-server/internal/infrastructure/external/muzi"
"tyapi-server/internal/infrastructure/external/ocr"
"tyapi-server/internal/infrastructure/external/shumai"
"tyapi-server/internal/infrastructure/external/sms"
"tyapi-server/internal/infrastructure/external/storage"
"tyapi-server/internal/infrastructure/external/tianyancha"
@@ -366,6 +368,14 @@ func NewContainer() *Container {
func(cfg *config.Config) (*xingwei.XingweiService, error) {
return xingwei.NewXingweiServiceWithConfig(cfg)
},
// JiguangService - 极光服务
func(cfg *config.Config) (*jiguang.JiguangService, error) {
return jiguang.NewJiguangServiceWithConfig(cfg)
},
// ShumaiService - 数脉服务
func(cfg *config.Config) (*shumai.ShumaiService, error) {
return shumai.NewShumaiServiceWithConfig(cfg)
},
func(cfg *config.Config) *yushan.YushanService {
return yushan.NewYushanService(
cfg.Yushan.URL,
@@ -552,6 +562,11 @@ func NewContainer() *Container {
product_repo.NewGormProductCategoryRepository,
fx.As(new(domain_product_repo.ProductCategoryRepository)),
),
// 产品二级分类仓储 - 同时注册具体类型和接口类型
fx.Annotate(
product_repo.NewGormProductSubCategoryRepository,
fx.As(new(domain_product_repo.ProductSubCategoryRepository)),
),
// 订阅仓储 - 同时注册具体类型和接口类型
fx.Annotate(
product_repo.NewGormSubscriptionRepository,
@@ -571,6 +586,11 @@ func NewContainer() *Container {
product_repo.NewGormComponentReportRepository,
fx.As(new(domain_product_repo.ComponentReportRepository)),
),
// 购买订单仓储
fx.Annotate(
finance_repo.NewGormPurchaseOrderRepository,
fx.As(new(domain_finance_repo.PurchaseOrderRepository)),
),
// UI组件仓储 - 同时注册具体类型和接口类型
fx.Annotate(
product_repo.NewGormUIComponentRepository,
@@ -893,6 +913,7 @@ func NewContainer() *Container {
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
userRepo domain_user_repo.UserRepository,
txManager *shared_database.TransactionManager,
logger *zap.Logger,
@@ -909,6 +930,7 @@ func NewContainer() *Container {
alipayOrderRepo,
wechatOrderRepo,
rechargeRecordRepo,
purchaseOrderRepo,
componentReportRepo,
userRepo,
txManager,
@@ -950,6 +972,36 @@ func NewContainer() *Container {
},
fx.As(new(product.ProductApplicationService)),
),
// 组件报告订单服务
func(
productRepo domain_product_repo.ProductRepository,
docRepo domain_product_repo.ProductDocumentationRepository,
apiConfigRepo domain_product_repo.ProductApiConfigRepository,
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
componentReportRepo domain_product_repo.ComponentReportRepository,
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
subscriptionRepo domain_product_repo.SubscriptionRepository,
aliPayService *payment.AliPayService,
wechatPayService *payment.WechatPayService,
logger *zap.Logger,
) *product.ComponentReportOrderService {
return product.NewComponentReportOrderService(
productRepo,
docRepo,
apiConfigRepo,
purchaseOrderRepo,
componentReportRepo,
rechargeRecordRepo,
alipayOrderRepo,
wechatOrderRepo,
subscriptionRepo,
aliPayService,
wechatPayService,
logger,
)
},
// 产品API配置应用服务 - 绑定到接口
fx.Annotate(
product.NewProductApiConfigApplicationService,
@@ -960,6 +1012,11 @@ func NewContainer() *Container {
product.NewCategoryApplicationService,
fx.As(new(product.CategoryApplicationService)),
),
// 二级分类应用服务 - 绑定到接口
fx.Annotate(
product.NewSubCategoryApplicationService,
fx.As(new(product.SubCategoryApplicationService)),
),
fx.Annotate(
product.NewDocumentationApplicationService,
fx.As(new(product.DocumentationApplicationServiceInterface)),
@@ -1055,7 +1112,7 @@ func NewContainer() *Container {
logger *zap.Logger,
) product.UIComponentApplicationService {
// 创建UI组件文件服务
basePath := "resources/Pure Component/src/ui"
basePath := "resources/Pure_Component/src/ui"
fileService := product.NewUIComponentFileService(basePath, logger)
return product.NewUIComponentApplicationService(
@@ -1063,6 +1120,7 @@ func NewContainer() *Container {
productUIComponentRepo,
fileStorageService,
fileService,
logger,
)
},
fx.As(new(product.UIComponentApplicationService)),
@@ -1155,6 +1213,8 @@ func NewContainer() *Container {
handlers.NewProductHandler,
// 产品管理员HTTP处理器
handlers.NewProductAdminHandler,
// 二级分类HTTP处理器
handlers.NewSubCategoryHandler,
// API Handler
handlers.NewApiHandler,
// 统计HTTP处理器
@@ -1183,6 +1243,7 @@ func NewContainer() *Container {
docRepo domain_product_repo.ProductDocumentationRepository,
apiConfigRepo domain_product_repo.ProductApiConfigRepository,
componentReportRepo domain_product_repo.ComponentReportRepository,
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
rechargeRecordRepo domain_finance_repo.RechargeRecordRepository,
alipayOrderRepo domain_finance_repo.AlipayOrderRepository,
wechatOrderRepo domain_finance_repo.WechatOrderRepository,
@@ -1190,7 +1251,16 @@ func NewContainer() *Container {
wechatPayService *payment.WechatPayService,
logger *zap.Logger,
) *component_report.ComponentReportHandler {
return component_report.NewComponentReportHandler(productRepo, docRepo, apiConfigRepo, componentReportRepo, rechargeRecordRepo, alipayOrderRepo, wechatOrderRepo, aliPayService, wechatPayService, logger)
return component_report.NewComponentReportHandler(productRepo, docRepo, apiConfigRepo, componentReportRepo, purchaseOrderRepo, rechargeRecordRepo, alipayOrderRepo, wechatOrderRepo, aliPayService, wechatPayService, logger)
},
// 组件报告订单处理器
func(
componentReportOrderService *product.ComponentReportOrderService,
purchaseOrderRepo domain_finance_repo.PurchaseOrderRepository,
config *config.Config,
logger *zap.Logger,
) *handlers.ComponentReportOrderHandler {
return handlers.NewComponentReportOrderHandler(componentReportOrderService, purchaseOrderRepo, config, logger)
},
// UI组件HTTP处理器
func(
@@ -1215,6 +1285,10 @@ func NewContainer() *Container {
routes.NewProductRoutes,
// 产品管理员路由
routes.NewProductAdminRoutes,
// 二级分类路由
routes.NewSubCategoryRoutes,
// 组件报告订单路由
routes.NewComponentReportOrderRoutes,
// UI组件路由
routes.NewUIComponentRoutes,
// 文章路由
@@ -1331,6 +1405,8 @@ func RegisterRoutes(
financeRoutes *routes.FinanceRoutes,
productRoutes *routes.ProductRoutes,
productAdminRoutes *routes.ProductAdminRoutes,
subCategoryRoutes *routes.SubCategoryRoutes,
componentReportOrderRoutes *routes.ComponentReportOrderRoutes,
uiComponentRoutes *routes.UIComponentRoutes,
articleRoutes *routes.ArticleRoutes,
announcementRoutes *routes.AnnouncementRoutes,
@@ -1352,12 +1428,9 @@ func RegisterRoutes(
financeRoutes.Register(router)
productRoutes.Register(router)
productAdminRoutes.Register(router)
// UI组件路由需要特殊处理因为它需要管理员中间件
engine := router.GetEngine()
adminGroup := engine.Group("/api/v1/admin")
adminGroup.Use(adminAuth.Handle())
uiComponentRoutes.RegisterRoutes(adminGroup, adminAuth)
subCategoryRoutes.Register(router)
componentReportOrderRoutes.Register(router)
uiComponentRoutes.Register(router)
articleRoutes.Register(router)
announcementRoutes.Register(router)

View File

@@ -170,6 +170,24 @@ type YYSYD50FReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
}
type IVYZZQT3Req struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
}
type IVYZSFELReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
}
type IVYZBPQ2Req struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
}
type YYSYF7DBReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
StartDate string `json:"start_date" validate:"required,validDate" encrypt:"false"`
@@ -250,18 +268,124 @@ type QCXG7A2BReq struct {
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"`
Name string `json:"name" validate:"omitempty,min=1,validName"`
}
type QCXGGB2QReq struct {
PlateNo string `json:"plate_no" validate:"required"`
Name string `json:"name" validate:"required,min=1,validName"`
CarPlateType string `json:"carplate_type" validate:"required"`
}
type QCXGJJ2AReq struct {
VinCode string `json:"vin_code" validate:"required"`
EngineNumber string `json:"engine_number" validate:"omitempty"`
NoticeModel string `json:"notice_model" validate:"omitempty"`
}
type QCXG4I1ZReq struct {
VinCode string `json:"vin_code" validate:"required"`
}
type QCXG1H7YReq struct {
VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no" validate:"required"`
}
type QCXG1U4UReq struct {
VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no" validate:"omitempty"`
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
ImageURL string `json:"image_url" validate:"omitempty,url"`
RegURL string `json:"reg_url" validate:"omitempty,url"`
EngineNumber string `json:"engine_number" validate:"omitempty"`
}
type IVYZ0S0DReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type IVYZ1J7HReq struct {
PlateNo string `json:"plate_no" validate:"required"`
Name string `json:"name" validate:"required,min=1,validName"`
CarPlateType string `json:"carplate_type" validate:"required"`
}
type QCXG3Z3LReq struct {
VinCode string `json:"vin_code" validate:"required"`
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
ImageURL string `json:"image_url" validate:"omitempty,url"`
PlateNo string `json:"plate_no" validate:"omitempty"`
EngineNumber string `json:"engine_number" validate:"omitempty"`
}
type QCXG2T6SReq struct {
VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no" validate:"omitempty"`
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
ImageURL string `json:"image_url" validate:"required,url"`
}
type QCXGYTS2Req struct {
VinCode string `json:"vin_code" validate:"omitempty"`
PlateNo string `json:"plate_no" validate:"omitempty"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type QCXGGJ3AReq struct {
VinCode string `json:"vin_code" validate:"required"`
}
type QCXGP00WReq struct {
VinCode string `json:"vin_code" validate:"required"`
PlateNo string `json:"plate_no" validate:"omitempty"`
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
VlPhotoData string `json:"vlphoto_data" validate:"omitempty,validBase64Image"`
}
type QCXG4D2EReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
UserType string `json:"user_type" validate:"required,oneof=1 2 3"`
}
type COMENT01Req struct {
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
EntCode string `json:"ent_code" validate:"required,validUSCI"`
}
type JRZQ09J8Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
}
type JRZQO6L7Req struct {
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
}
type JRZQS7G0Req struct {
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
}
type JRZQO7L1Req struct {
MobileNo string `json:"mobile_no" validate:"omitempty,min=11,max=11,validMobileNo"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"`
}
type FLXGDEA8Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
@@ -361,6 +485,23 @@ type QYGL5A3CReq struct {
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
}
type QYGL2naoReq struct {
EntCode string `json:"ent_code" validate:"required,validUSCI"`
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
PageNum int64 `json:"page_num" validate:"omitempty,min=1"`
}
type QYGLNIO8Req struct {
EntCode string `json:"ent_code" validate:"required,validUSCI"`
}
type QYGLP0HTReq struct {
EntCode string `json:"ent_code" validate:"required,validUSCI"`
Flag string `json:"flag" validate:"omitempty"`
Dir string `json:"dir" validate:"omitempty,oneof=up down"`
MinPercent string `json:"min_percent" validate:"omitempty"`
MaxPercent string `json:"max_percent" validate:"omitempty"`
}
type QYGL8B4DReq struct {
EntCode string `json:"ent_code" validate:"required,validUSCI"`
PageSize int64 `json:"page_size" validate:"omitempty,min=1,max=100"`
@@ -565,11 +706,7 @@ type FLXG8B4DReq struct {
}
type QCXG9P1CReq struct {
VehicleType string `json:"vehicle_type" validate:"omitempty,oneof=0 1 2"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"omitempty,min=1,validName"`
UserType string `json:"user_type" validate:"omitempty,oneof=1 2 3"`
Authorized string `json:"authorized" validate:"required,oneof=0 1"`
}
type QCXG8A3DReq struct {
@@ -614,6 +751,13 @@ type QYGL2S0WReq struct {
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
}
// 全国企业司法模型服务查询_V1
type QYGL66SLReq struct {
EntCode string `json:"ent_code" validate:"omitempty,validUSCI"`
AuthDate string `json:"auth_date" validate:"required,validAuthDate"`
EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"`
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
}
type JRZQ2F8AReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
@@ -701,6 +845,91 @@ type IVYZ6M8PReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
}
type IVYZ9H2MReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type YYSY9E4AReq struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
// YYSY运营商相关API DTO
type YYSY3M8SReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYC4R9Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYH6D2Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYH6F3Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYP0T4Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYE7V5Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYS9W1Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYK8R3Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type YYSYF2T7Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
DateRange string `json:"date_range" validate:"required,validAuthDate" `
}
// 数脉 API
type IVYZ3M8SReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type IVYZ9K7FReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
}
type IVYZA1B3Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
PhotoData string `json:"photo_data" validate:"required,validBase64Image"`
}
type IVYZC4R9Req struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type IVYZP0T4Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
}
type IVYZF2T7Req struct {
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
DateRange string `json:"date_range" validate:"required,validAuthDate" `
}
type IVYZX5QZReq struct {
ReturnURL string `json:"return_url" validate:"required,validReturnURL"`
}

View File

@@ -71,9 +71,6 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
if accessId == "" {
return nil, errors.New("AccessId不能为空")
}
if requestParams == "" {
return nil, errors.New("请求参数不能为空")
}
if clientIp == "" {
return nil, errors.New("ClientIp不能为空")
}
@@ -83,7 +80,7 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
AccessId: accessId,
TransactionId: GenerateTransactionID(),
ClientIp: clientIp,
RequestParams: requestParams,
RequestParams: "",
Status: ApiCallStatusPending,
StartAt: time.Now(),
}, nil
@@ -92,7 +89,7 @@ func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
// MarkSuccess 标记为成功
func (a *ApiCall) MarkSuccess(cost decimal.Decimal) error {
// 校验除ErrorMsg和ErrorType外所有字段不能为空
if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.RequestParams == "" || a.Status == "" || a.StartAt.IsZero() {
if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.Status == "" || a.StartAt.IsZero() {
return errors.New("ApiCall字段不能为空除ErrorMsg和ErrorType")
}
// 可选字段也要有值
@@ -132,9 +129,6 @@ func (a *ApiCall) Validate() error {
if a.TransactionId == "" {
return errors.New("TransactionId不能为空")
}
if a.RequestParams == "" {
return errors.New("请求参数不能为空")
}
if a.Status != ApiCallStatusPending && a.Status != ApiCallStatusSuccess && a.Status != ApiCallStatusFailed {
return errors.New("无效的调用状态")
}

View File

@@ -18,7 +18,9 @@ import (
"tyapi-server/internal/domains/api/services/processors/yysy"
"tyapi-server/internal/domains/product/services"
"tyapi-server/internal/infrastructure/external/alicloud"
"tyapi-server/internal/infrastructure/external/jiguang"
"tyapi-server/internal/infrastructure/external/muzi"
"tyapi-server/internal/infrastructure/external/shumai"
"tyapi-server/internal/infrastructure/external/tianyancha"
"tyapi-server/internal/infrastructure/external/westdex"
"tyapi-server/internal/infrastructure/external/xingwei"
@@ -54,6 +56,8 @@ func NewApiRequestService(
alicloudService *alicloud.AlicloudService,
zhichaService *zhicha.ZhichaService,
xingweiService *xingwei.XingweiService,
jiguangService *jiguang.JiguangService,
shumaiService *shumai.ShumaiService,
validator interfaces.RequestValidator,
productManagementService *services.ProductManagementService,
) *ApiRequestService {
@@ -61,7 +65,7 @@ func NewApiRequestService(
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, shumaiService, validator, combService)
// 统一注册所有处理器
registerAllProcessors(combService)
@@ -133,6 +137,9 @@ func registerAllProcessors(combService *comb.CombService) {
"JRZQ1W4X": jrzq.ProcessJRZQ1W4XRequest,
"JRZQ3P01": jrzq.ProcessJRZQ3P01Request,
"JRZQ3AG6": jrzq.ProcessJRZQ3AG6Request,
"JRZQO6L7": jrzq.ProcessJRZQO6L7Request, // 全国自然人经济特征评分模型v3 简版
"JRZQO7L1": jrzq.ProcessJRZQO7L1Request, // 全国自然人经济特征评分模型v4 详版
"JRZQS7G0": jrzq.ProcessJRZQS7G0Request, // 社保综合评分V1
// QYGL系列处理器
"QYGL8261": qygl.ProcessQYGL8261Request,
@@ -157,6 +164,10 @@ func registerAllProcessors(combService *comb.CombService) {
"QYGL5A9T": qygl.ProcessQYGL5A9TRequest, //全国企业各类工商风险统计数量查询
"QYGL2S0W": qygl.ProcessQYGL2S0WRequest, //失信被执行企业个人查询
"QYGL5CMP": qygl.ProcessQYGL5CMPRequest, //企业五要素验证
"QYGL66SL": qygl.ProcessQYGL66SLRequest, //全国企业司法模型服务查询_V1
"QYGL2NAO": qygl.ProcessQYGL2naoRequest, //股权变更
"QYGLNIO8": qygl.ProcessQYGLNIO8Request, //企业基本信息
"QYGLP0HT": qygl.ProcessQYGLP0HTRequest, //股权穿透
// YYSY系列处理器
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
@@ -177,6 +188,14 @@ func registerAllProcessors(combService *comb.CombService) {
"YYSY9E4A": yysy.ProcessYYSY9E4ARequest,
"YYSY9F1B": yysy.ProcessYYSY9F1BYequest,
"YYSY6F2B": yysy.ProcessYYSY6F2BRequest,
"YYSY3M8S": yysy.ProcessYYSY3M8SRequest, //运营商二要素查询
"YYSYC4R9": yysy.ProcessYYSYC4R9Request, //运营商三要素详版查询
"YYSYH6D2": yysy.ProcessYYSYH6D2Request, //运营商三要素简版查询
"YYSYP0T4": yysy.ProcessYYSYP0T4Request, //在网时长查询
"YYSYE7V5": yysy.ProcessYYSYE7V5Request, //手机在网状态查询
"YYSYS9W1": yysy.ProcessYYSYS9W1Request, //手机携号转网查询
"YYSYK8R3": yysy.ProcessYYSYK8R3Request, //手机空号检测查询
"YYSYF2T7": yysy.ProcessYYSYF2T7Request, //手机二次放号检测查询
// IVYZ系列处理器
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
@@ -206,6 +225,15 @@ func registerAllProcessors(combService *comb.CombService) {
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
"IVYZ6M8P": ivyz.ProcessIVYZ6M8PRequest, //职业资格证书
"IVYZ9H2M": ivyz.ProcessIVYZ9H2MRequest, //极光个人婚姻查询V2版
"IVYZZQT3": ivyz.ProcessIVYZZQT3Request, //人脸比对V3
"IVYZBPQ2": ivyz.ProcessIVYZBPQ2Request, //人脸比对V2
"IVYZSFEL": ivyz.ProcessIVYZSFELRequest, //全国自然人人像三要素核验_V1
"IVYZ0S0D": ivyz.ProcessIVYZ0S0DRequest, //劳动仲裁信息查询(个人版)
"IVYZ1J7H": ivyz.ProcessIVYZ1J7HRequest, //行驶证核查v2
"IVYZ9K7F": ivyz.ProcessIVYZ9K7FRequest, //身份证实名认证
"IVYZA1B3": ivyz.ProcessIVYZA1B3Request, //公安三要素人脸识别
"IVYZN2P8": ivyz.ProcessIVYZN2P8Request, //身份证实名认证即时版本
// COMB系列处理器 - 只注册有自定义逻辑的组合包
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑重命名ApiCode
@@ -217,6 +245,20 @@ func registerAllProcessors(combService *comb.CombService) {
"QCXG9P1C": qcxg.ProcessQCXG9P1CRequest,
"QCXG8A3D": qcxg.ProcessQCXG8A3DRequest,
"QCXG6B4E": qcxg.ProcessQCXG6B4ERequest,
"QCXG4896": qcxg.ProcessQCXG4896Request,
"QCXG5F3A": qcxg.ProcessQCXG5F3ARequest, // 极光个人车辆查询
"QCXG4D2E": qcxg.ProcessQCXG4D2ERequest, // 极光名下车辆数量查询
"QCXGJJ2A": qcxg.ProcessQCXGJJ2ARequest, // vin码查车辆信息(一对多)
"QCXGGJ3A": qcxg.ProcessQCXGGJ3ARequest, // 车辆vin码查询号牌
"QCXGYTS2": qcxg.ProcessQCXGYTS2Request, // 车辆二要素核验v2
"QCXGP00W": qcxg.ProcessQCXGP00WRequest, // 车辆出险详版查询
"QCXGGB2Q": qcxg.ProcessQCXGGB2QRequest, // 车辆二要素核验V1
"QCXG4I1Z": qcxg.ProcessQCXG4I1ZRequest, // 车辆过户详版查询
"QCXG1H7Y": qcxg.ProcessQCXG1H7YRequest, // 车辆过户简版查询
"QCXG3Z3L": qcxg.ProcessQCXG3Z3LRequest, // 车辆维保详细版查询
"QCXG3Y6B": qcxg.ProcessQCXG3Y6BRequest, // 车辆维保简版查询
"QCXG2T6S": qcxg.ProcessQCXG2T6SRequest, // 车辆里程记录(品牌查询)
"QCXG1U4U": qcxg.ProcessQCXG1U4URequest, // 车辆里程记录(混合查询)
// DWBG系列处理器 - 多维报告
"DWBG6A2C": dwbg.ProcessDWBG6A2CRequest,
@@ -249,6 +291,9 @@ func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, apiCode st
// 设置Options和CallContext到依赖容器
deps := a.processorDeps.WithOptions(options).WithCallContext(callContext)
// 将apiCode放入context供外部服务使用
ctx = context.WithValue(ctx, "api_code", apiCode)
// 1. 优先查找已注册的自定义处理器
if processor, exists := RequestProcessors[apiCode]; exists {
return processor(ctx, params, deps)

View File

@@ -198,7 +198,49 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
"IVYZ5A9O": &dto.IVYZ5A9OReq{}, //全国⾃然⼈⻛险评估评分模型
"IVYZ6M8P": &dto.IVYZ6M8PReq{}, //职业资格证书
"IVYZ9H2M": &dto.IVYZ9H2MReq{}, //极光个人婚姻查询V2版
"QYGL5CMP": &dto.QYGL5CMPReq{}, //企业五要素验证
"QCXG4896": &dto.QCXG4896Req{}, //网约车风险查询
"IVYZZQT3": &dto.IVYZZQT3Req{}, //人脸比对V3
"IVYZBPQ2": &dto.IVYZBPQ2Req{}, //人脸比对V2
"IVYZSFEL": &dto.IVYZSFELReq{}, //全国自然人人像三要素核验_V1
"QYGL66SL": &dto.QYGL66SLReq{}, //全国企业司法模型服务查询_V1
"QCXG5F3A": &dto.QCXG5F3AReq{}, //极光个人车辆查询
"QCXG4D2E": &dto.QCXG4D2EReq{}, //极光名下车辆数量查询
"QYGLP0HT": &dto.QYGLP0HTReq{}, //股权穿透
"QYGL2NAO": &dto.QYGL2naoReq{}, //股权变更
"QYGLNIO8": &dto.QYGLNIO8Req{}, //企业基本信息
"QYGL4B2E": &dto.QYGL5A3CReq{}, //税收违法
"QYGL7D9A": &dto.QYGL5A3CReq{}, //欠税公告
"IVYZ0S0D": &dto.IVYZ0S0DReq{}, //劳动仲裁信息查询(个人版)
"IVYZ1J7H": &dto.IVYZ1J7HReq{}, //行驶证核查v2
"QCXGJJ2A": &dto.QCXGJJ2AReq{}, //vin码查车辆信息(一对多)
"QCXGGJ3A": &dto.QCXGGJ3AReq{}, //车辆vin码查询号牌
"QCXGYTS2": &dto.QCXGYTS2Req{}, //车辆二要素核验v2
"QCXGP00W": &dto.QCXGP00WReq{}, //车辆出险详版查询
"QCXGGB2Q": &dto.QCXGGB2QReq{}, //车辆二要素核验V1
"QCXG4I1Z": &dto.QCXG4I1ZReq{}, //车辆过户详版查询
"QCXG1H7Y": &dto.QCXG1H7YReq{}, //车辆过户简版查询
"QCXG3Z3L": &dto.QCXG3Z3LReq{}, //车辆维保详细版查询
"QCXG3Y6B": &dto.QCXG1U4UReq{}, //车辆维保简版查询
"QCXG2T6S": &dto.QCXG2T6SReq{}, //车辆里程记录(品牌查询)
"QCXG1U4U": &dto.QCXG1U4UReq{}, //车辆里程记录(混合查询)
"JRZQO6L7": &dto.JRZQO6L7Req{}, //全国自然人经济特征评分模型v3 简版
"JRZQO7L1": &dto.JRZQO7L1Req{}, //全国自然人经济特征评分模型v4 详版
"JRZQS7G0": &dto.JRZQS7G0Req{}, //社保综合评分V1
"IVYZ9K7F": &dto.IVYZ9K7FReq{}, //身份证实名认证
"YYSY3M8S": &dto.YYSY3M8SReq{}, //运营商二要素查询
"YYSYC4R9": &dto.YYSYC4R9Req{}, //运营商三要素详版查询
"YYSYH6D2": &dto.YYSYH6D2Req{}, //运营商三要素简版查询
"YYSYP0T4": &dto.YYSYP0T4Req{}, //在网时长查询
"YYSYE7V5": &dto.YYSYE7V5Req{}, //手机在网状态查询
"YYSYS9W1": &dto.YYSYS9W1Req{}, //手机携号转网查询
"YYSYK8R3": &dto.YYSYK8R3Req{}, //手机空号检测查询
"YYSYF2T7": &dto.YYSYF2T7Req{}, //手机二次放号检测查询
"IVYZA1B3": &dto.IVYZA1B3Req{}, //公安三要素人脸识别
"IVYZX5QZ": &dto.IVYZX5QZReq{}, //活体识别
"IVYZN2P8": &dto.IVYZ9K7FReq{}, //身份证实名认证即时版本
}
// 优先返回已配置的DTO
@@ -321,6 +363,7 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
case strings.HasPrefix(rule, "oneof="):
values := strings.TrimPrefix(rule, "oneof=")
frontendRules = append(frontendRules, "可选值: "+values)
}
}
@@ -350,6 +393,8 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation
return "select"
} else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "base64") {
return "textarea"
} else if strings.Contains(validation, "图片地址") {
return "url"
}
return "text"
case reflect.Int64:
@@ -376,6 +421,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
"legal_person": "法人姓名",
"ent_code": "企业代码",
"auth_date": "授权日期",
"date_range": "数据范围",
"time_range": "时间范围",
"authorized": "是否授权",
"authorization_url": "授权链接",
@@ -399,6 +445,16 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
"owner_type": "企业主类型",
"type": "查询类型",
"query_reason_id": "查询原因ID",
"flag": "层次",
"dir": "方向",
"min_percent": "股权穿透比例下限",
"max_percent": "股权穿透比例上限",
"engine_number": "发动机号码",
"notice_model": "车辆型号",
"vlphoto_data": "行驶证图片",
"carplate_type": "车辆号牌类型",
"image_url": "行驶证图片地址",
"reg_url": "车辆登记证图片地址",
}
if label, exists := labelMap[jsonTag]; exists {
@@ -421,6 +477,7 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
"legal_person": "王五",
"ent_code": "91110000123456789X",
"auth_date": "20240101-20241231",
"date_range": "20240101-20241231",
"time_range": "09:00-18:00",
"authorized": "1",
"years": "5",
@@ -444,6 +501,16 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
"ownerType": "1",
"type": "per",
"query_reason_id": "1",
"flag": "4",
"dir": "down",
"min_percent": "0",
"max_percent": "1",
"engine_number": "1234567890",
"notice_model": "1",
"vlphoto_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"carplate_type": "01",
"image_url": "https://example.com/images/driving_license.jpg",
"reg_url": "https://example.com/images/vehicle_registration.jpg",
}
if example, exists := exampleMap[jsonTag]; exists {
@@ -475,6 +542,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
"legal_person": "请输入法人真实姓名",
"ent_code": "请输入统一社会信用代码",
"auth_date": "请输入授权日期范围YYYYMMDD-YYYYMMDD",
"date_range": "请输入日期范围YYYYMMDD-YYYYMMDD",
"time_range": "请输入时间范围HH:MM-HH:MM",
"authorized": "请选择是否授权",
"years": "请输入查询年数0-100",
@@ -498,6 +566,16 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
"ownerType": "请选择企业主类型",
"type": "请选择查询类型",
"query_reason_id": "请选择查询原因ID",
"flag": "请输入层次最大4",
"dir": "请选择方向up-向上down-向下)",
"min_percent": "请输入股权穿透比例下限默认0",
"max_percent": "请输入股权穿透比例上限默认1",
"engine_number": "请输入发动机号码",
"notice_model": "请输入车辆型号",
"vlphoto_data": "请输入行驶证图片",
"carplate_type": "请选择车辆号牌类型01-大型汽车 02-小型汽车 03-使馆汽车 04-领馆汽车 05-境外汽车 06-外籍汽车 07-普通摩托车 08-轻便摩托车 09-使馆摩托车 10-领馆摩托车 11-境外摩托车 12-外籍摩托车 13-低速车 14-拖拉机 15-挂车 16-教练汽车 17-教练摩托车 20-临时入境汽车 21-临时入境摩托车 22-临时行驶车 23-警用汽车 24-警用摩托 51-新能源大型车 52-新能源小型车)",
"image_url": "请输入行驶证图片地址",
"reg_url": "请输入车辆登记证图片地址",
}
if placeholder, exists := placeholderMap[jsonTag]; exists {
@@ -531,6 +609,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
"legal_person": "请输入法人真实姓名",
"ent_code": "请输入统一社会信用代码",
"auth_date": "请输入授权日期范围格式YYYYMMDD-YYYYMMDD且日期范围必须包括今天",
"date_range": "请输入日期范围格式YYYYMMDD-YYYYMMDD",
"time_range": "请输入时间范围格式HH:MM-HH:MM",
"authorized": "请输入是否授权0-未授权1-已授权",
"years": "请输入查询年数0-100",
@@ -554,6 +633,16 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
"owner_type": "企业主类型编码1-法定代表人2-主要人员3-自然人股东4-法定代表人及自然人股东5-其他",
"type": "查询类型per-人员ent-企业 ",
"query_reason_id": "查询原因ID1-授信审批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 {

View File

@@ -4,7 +4,9 @@ import (
"context"
"tyapi-server/internal/application/api/commands"
"tyapi-server/internal/infrastructure/external/alicloud"
"tyapi-server/internal/infrastructure/external/jiguang"
"tyapi-server/internal/infrastructure/external/muzi"
"tyapi-server/internal/infrastructure/external/shumai"
"tyapi-server/internal/infrastructure/external/tianyancha"
"tyapi-server/internal/infrastructure/external/westdex"
"tyapi-server/internal/infrastructure/external/xingwei"
@@ -32,6 +34,8 @@ type ProcessorDependencies struct {
AlicloudService *alicloud.AlicloudService
ZhichaService *zhicha.ZhichaService
XingweiService *xingwei.XingweiService
JiguangService *jiguang.JiguangService
ShumaiService *shumai.ShumaiService
Validator interfaces.RequestValidator
CombService CombServiceInterface // Changed to interface to break import cycle
Options *commands.ApiCallOptions // 添加Options支持
@@ -47,6 +51,8 @@ func NewProcessorDependencies(
alicloudService *alicloud.AlicloudService,
zhichaService *zhicha.ZhichaService,
xingweiService *xingwei.XingweiService,
jiguangService *jiguang.JiguangService,
shumaiService *shumai.ShumaiService,
validator interfaces.RequestValidator,
combService CombServiceInterface, // Changed to interface
) *ProcessorDependencies {
@@ -58,6 +64,8 @@ func NewProcessorDependencies(
AlicloudService: alicloudService,
ZhichaService: zhichaService,
XingweiService: xingweiService,
JiguangService: jiguangService,
ShumaiService: shumaiService,
Validator: validator,
CombService: combService,
Options: nil, // 初始化为nil在调用时设置

View File

@@ -20,7 +20,7 @@ func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"{
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"

View File

@@ -25,7 +25,7 @@ func ProcessFLXGDEA9Request(ctx context.Context, params []byte, deps *processors
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" {
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550"{
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
// 构建请求参数
reqData := map[string]interface{}{
"plate": paramsDto.PlateNo,
"plateType": paramsDto.CarPlateType,
"name": paramsDto.Name,
}
// 调用极光API
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-driving-license-v2", "vehicle/driving-license-v2", reqData)
if err != nil {
// 根据错误类型返回相应的错误
if errors.Is(err, jiguang.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, jiguang.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 极光服务已经返回了 data 字段的 JSON直接返回即可
return respBytes, nil
}

View File

@@ -7,10 +7,10 @@ import (
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessIVYZ2A8BRequest IVYZ2A8B API处理方法 - 身份二要素认证
// ProcessIVYZ2A8BRequest IVYZ2A8B API处理方法 - 身份二要素认证政务版 数脉内部替换
func ProcessIVYZ2A8BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ2A8BReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
@@ -20,37 +20,31 @@ func ProcessIVYZ2A8BRequest(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
reqFormData := map[string]interface{}{
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
//走政务接口 - 使用 app_id2 和 app_secret2
deps.ShumaiService.UseGovernment()
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"authorized": paramsDto.Authorized,
}
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI001", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
if errors.Is(err, shumai.ErrNotFound) {
// 查无记录情况
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 将响应数据转换为JSON字节
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -36,6 +36,11 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors
if returnType == "" {
returnType = "1"
}
paramSign := map[string]interface{}{
"returnType": returnType,
"realName": encryptedName,
"certCode": encryptedCertCode,
}
reqData := map[string]interface{}{
"realName": encryptedName,
@@ -43,7 +48,8 @@ func ProcessIVYZ3P9MRequest(ctx context.Context, params []byte, deps *processors
"returnType": returnType,
}
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", reqData)
respData, err := deps.MuziService.CallAPI(ctx, "PC0041", "/academic",reqData,paramSign)
if err != nil {
switch {
case errors.Is(err, muzi.ErrDatasource):

View File

@@ -7,7 +7,7 @@ import (
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessIVYZ81NCRequest IVYZ81NC API处理方法
@@ -21,45 +21,75 @@ func ProcessIVYZ81NCRequest(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"idcard": encryptedIDCard,
},
"idCard": encryptedIDCard,
"authorized": "1", // 默认值
}
const maxRetries = 5
var respBytes []byte
for attempt := 0; attempt <= maxRetries; attempt++ {
var err error
respBytes, err = deps.WestDexService.CallAPI(ctx, "G09XM02", reqData)
if err == nil {
return respBytes, nil
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI029", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 如果不是数据源异常,直接返回错误
if !errors.Is(err, westdex.ErrDatasource) {
// 解析响应数据,期望格式为 {"state": "1"}
var stateResp struct {
State string `json:"state"`
}
// 将 respData 转换为 JSON 字节再解析
respDataBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
// 如果是最后一次尝试,返回错误
if attempt == maxRetries {
return nil, errors.Join(processors.ErrDatasource, err)
if err := json.Unmarshal(respDataBytes, &stateResp); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
// 立即重试,不等待
// 根据 state 值转换为 81nc 格式
var opType, opTypeDesc string
switch stateResp.State {
case "1": // 已婚
opType = "IA"
opTypeDesc = "结婚"
case "2": // 未婚/未在民政局登记
opType = "INR"
opTypeDesc = "匹配不成功"
case "3": // 离异
opType = "IB"
opTypeDesc = "离婚"
default:
opType = "INR"
opTypeDesc = "匹配不成功"
}
return respBytes, nil
// 构建 81nc 格式响应
result := map[string]interface{}{
"code": "0",
"data": map[string]interface{}{
"op_date": "",
"op_type": opType,
"op_type_desc": opTypeDesc,
},
"message": "成功",
"seqNo": "",
}
// 返回 81nc 格式响应
return json.Marshal(result)
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -0,0 +1,48 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessIVYZ9K7FRequest IVYZ9K7F 身份证实名认证 API处理方法
func ProcessIVYZ9K7FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ9K7FReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrNotFound) {
// 查无记录情况
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,49 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessIVYZA1B3Request IVYZA1B3 公安三要素人脸识别API处理方法
func ProcessIVYZA1B3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZA1B3Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
"image": paramsDto.PhotoData,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/face_id_card/compare" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrNotFound) {
// 查无记录情况
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -0,0 +1,50 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessIVYZN2P8Request IVYZN2P8 身份证实名认证政务版 API处理方法
func ProcessIVYZN2P8Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ9K7FReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
}
//走政务接口 - 使用 app_id2 和 app_secret2
deps.ShumaiService.UseGovernment()
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrNotFound) {
// 查无记录情况
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -0,0 +1,48 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessIVYZx5qzRequest IVYZx5qz 活体识别API处理方法
func ProcessIVYZX5QZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZX5QZReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"return_url": paramsDto.ReturnURL,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/liveness/h5/v4/token" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrNotFound) {
// 查无记录情况
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -0,0 +1,66 @@
package jrzq
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessJRZQO6L7Request JRZQO6L7 API处理方法 - 全国自然人经济特征评分模型v3 简版
func ProcessJRZQO6L7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.JRZQO6L7Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
null := ""
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"phone": encryptedMobileNo,
"authorized": paramsDto.Authorized,
"province": null,
"city": null,
}
// 使用 WithSkipCode201Check 不跳过 201 错误检查,当 Code == "201" 时返回错误
// ctx = zhicha.WithSkipCode201Check(ctx)
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI081", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
// 将响应数据转换为JSON字节
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,67 @@
package jrzq
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessJRZQO7L1Request JRZQO7L1 API处理方法 - 全国自然人经济特征评分模型v4 详版
func ProcessJRZQO7L1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.JRZQO7L1Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
null := ""
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"phone": encryptedMobileNo,
"authorized": paramsDto.Authorized,
"entName": paramsDto.EntName,
"province": null,
"city": null,
}
// 使用 WithSkipCode201Check 不跳过 201 错误检查,当 Code == "201" 时返回错误
// ctx = zhicha.WithSkipCode201Check(ctx)
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI080", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
// 将响应数据转换为JSON字节
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -0,0 +1,61 @@
package jrzq
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha"
)
// ProcessJRZQS7G0Request JRZQS7G0 API处理方法 -社保综合评分V1
func ProcessJRZQS7G0Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.JRZQS7G0Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"name": encryptedName,
"idCard": encryptedIDCard,
"phone": encryptedMobileNo,
"authorized": paramsDto.Authorized,
}
// 使用 WithSkipCode201Check 不跳过 201 错误检查,当 Code == "201" 时返回错误
// ctx = zhicha.WithSkipCode201Check(ctx)
respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI082", reqData)
if err != nil {
if errors.Is(err, zhicha.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
}
return nil, errors.Join(processors.ErrSystem, err)
}
// 将响应数据转换为JSON字节
respBytes, err := json.Marshal(respData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
return respBytes, nil
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -0,0 +1,47 @@
package qcxg
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/jiguang"
)
// ProcessQCXG4D2ERequest QCXG4D2E API处理方法 - 极光名下车辆数量查询
func ProcessQCXG4D2ERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QCXG4D2EReq
if err := json.Unmarshal(params, &paramsDto); 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
}

View File

@@ -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, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
// 构建请求参数
reqData := map[string]interface{}{
"vin": paramsDto.VinCode,
}
// 调用极光API
// apiCode: car-vin (用于请求头)
// apiPath: car/car-vin (用于URL路径)
respBytes, err := deps.JiguangService.CallAPI(ctx, "transfer-information", "vehicle/transfer-information", reqData)
if err != nil {
// 根据错误类型返回相应的错误
if errors.Is(err, jiguang.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, jiguang.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 极光服务已经返回了 data 字段的 JSON直接返回即可
return respBytes, nil
}

View File

@@ -0,0 +1,52 @@
package qcxg
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/jiguang"
)
// ProcessQCXG5F3ARequest QCXG5F3A API处理方法 - 极光名下车辆车牌查询
func ProcessQCXG5F3ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QCXG5F3AReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
null := ""
// 构建请求参数
reqData := map[string]interface{}{
"id_card": paramsDto.IDCard,
"name": paramsDto.Name,
"userType": null,
"vehicleType": null,
"encryptionType": null,
"encryptionContent": null,
}
// 调用极光API
// apiCode: vehicle-person-vehicles (用于请求头)
// apiPath: vehicle/person-vehicles (用于URL路径)
respBytes, err := deps.JiguangService.CallAPI(ctx, "vehicle-person-vehicles", "vehicle/person-vehicles", reqData)
if err != nil {
// 根据错误类型返回相应的错误
if errors.Is(err, jiguang.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, jiguang.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 极光服务已经返回了 data 字段的 JSON直接返回即可
return respBytes, nil
}

View File

@@ -4,13 +4,16 @@ import (
"context"
"encoding/json"
"errors"
"strconv"
"tyapi-server/internal/domains/api/dto"
"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处理方法 兼容旧版 极光名下车牌查询数量
func ProcessQCXG9P1CRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.QCXG9P1CReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
@@ -21,44 +24,54 @@ func ProcessQCXG9P1CRequest(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
null := ""
// 构建请求参数
reqData := map[string]interface{}{
"idCard": encryptedIDCard,
"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
"id_card": paramsDto.IDCard,
"name": null,
"userType": null,
"vehicleType": null,
"encryptionType": null,
"encryptionContent": null,
}
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 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)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 将响应数据转换为 JSON
respBytes, err := json.Marshal(respData)
// 使用 gjson 检查并转换 vehicleCount
vehicleCountResult := gjson.GetBytes(respBytes, "vehicleCount")
if vehicleCountResult.Exists() && vehicleCountResult.Type == gjson.String {
// 如果是字符串类型,转换为整数
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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -13,10 +13,6 @@ func convertTianYanChaError(err error) error {
return nil
}
// 检查天眼查服务的错误类型,转换为处理器层的标准错误
if errors.Is(err, tianyancha.ErrNotFound) {
return errors.Join(processors.ErrNotFound, err)
}
if errors.Is(err, tianyancha.ErrDatasource) {
return errors.Join(processors.ErrDatasource, err)
}

View File

@@ -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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -24,6 +24,26 @@ func ProcessQYGL5CMPRequest(ctx context.Context, params []byte, deps *processors
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调用参数
apiParams := map[string]string{
"code": paramsDto.EntCode,
@@ -39,45 +59,41 @@ func ProcessQYGL5CMPRequest(ctx context.Context, params []byte, deps *processors
// 检查天眼查API调用是否成功
if !response.Success {
// 天眼查API调用失败返回企业信息校验不通过
return createStatusResponsess(1), nil
return nil, fmt.Errorf("天眼查API调用失败")
}
// 解析天眼查响应数据
if response.Data == nil {
// 天眼查响应数据为空,返回企业信息校验不通过
return createStatusResponsess(1), nil
return nil, fmt.Errorf("天眼查响应数据为空")
}
// 将response.Data转换为JSON字符串然后使用gjson解析
dataBytes, err := json.Marshal(response.Data)
if err != nil {
// 数据序列化失败,返回企业信息校验不通过
return createStatusResponsess(1), nil
return nil, fmt.Errorf("数据序列化失败")
}
// 使用gjson解析嵌套的data.result.data字段
result := gjson.GetBytes(dataBytes, "result")
if !result.Exists() {
// 字段不存在,返回企业信息校验不通过
return createStatusResponsess(1), nil
return nil, fmt.Errorf("result字段不存在")
}
// 检查data.result.data是否等于1
if result.Int() != 1 {
// 不等于1返回企业信息验不通过
return createStatusResponsess(1), nil
return nil, fmt.Errorf("企业信息验不通过")
}
// 天眼查三要素验证通过,继续调用星维身份证三要素验证
if err := json.Unmarshal(params, &paramsDto); 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返回的数据结构
return map[string]interface{}{
"success": response.Success,
"message": response.Message,
"data": response.Data,
}, nil
}
// verifyPersonalInfo 验证个人信息并返回API数据
func verifyPersonalInfo(ctx context.Context, paramsDto dto.QYGL5CMPReq, deps *processors.ProcessorDependencies) (map[string]interface{}, error) {
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
reqData := map[string]interface{}{
"name": paramsDto.LegalPerson,
@@ -89,6 +105,7 @@ func ProcessQYGL5CMPRequest(ctx context.Context, params []byte, deps *processors
projectID := "CDJ-1100244702166183936"
respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData)
if err != nil {
// 个人信息验证失败,返回错误状态
if errors.Is(err, xingwei.ErrNotFound) {
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, xingwei.ErrDatasource) {
@@ -106,42 +123,6 @@ func ProcessQYGL5CMPRequest(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("解析星维API响应失败: %w", err))
}
// 构建天眼查API返回的数据结构
tianYanChaData := map[string]interface{}{
"success": response.Success,
"message": response.Message,
"data": response.Data,
}
// 解析status响应将JSON字节解析为对象
statusBytes := createStatusResponsess(0) // 验证通过status为0
var statusData map[string]interface{}
if err := json.Unmarshal(statusBytes, &statusData); err != nil {
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("解析status响应失败: %w", err))
}
// 合并两个API的返回数据
mergedData := map[string]interface{}{
"Personal Information": xingweiData, // 星维API返回的数据
"Enterprise Information": tianYanChaData, // 天眼查API返回的数据
"status": statusData, // 解析后的status对象
}
// 将合并后的数据序列化为JSON
mergedBytes, err := json.Marshal(mergedData)
if err != nil {
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("合并数据序列化失败: %w", err))
}
return mergedBytes, nil
}
// createStatusResponsess 创建状态响应
func createStatusResponsess(status int) []byte {
response := map[string]interface{}{
"status": status,
}
respBytes, _ := json.Marshal(response)
return respBytes
// 返回星维API的全部数据
return xingweiData, nil
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View File

@@ -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, &paramsDto); 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
}

View 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
}

View File

@@ -4,12 +4,38 @@ import (
"context"
"encoding/json"
"errors"
"strings"
"tyapi-server/internal/domains/api/dto"
"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处理方法
func ProcessYYSY09CDRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSY09CDReq
@@ -21,38 +47,98 @@ func ProcessYYSY09CDRequest(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrInvalidParam, err)
}
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
// 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名
reqData := map[string]interface{}{
"data": map[string]interface{}{
"name": encryptedName,
"idNo": encryptedIDCard,
"phone": encryptedMobileNo,
"phoneType": paramsDto.MobileType,
},
"name": paramsDto.Name,
"idCardNum": paramsDto.IDCard,
"phoneNumber": paramsDto.MobileNo,
}
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 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)
} 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
// 解析星维返回的数据
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",
},
}
}

View File

@@ -0,0 +1,45 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSY3M8SRequest YYSY3M8S 运营商二要素 API处理方法
func ProcessYYSY3M8SRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSY3M8SReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"mobile": paramsDto.MobileNo,
"name": paramsDto.Name,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/mobile_two/check" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -8,6 +8,7 @@ import (
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSYBE08Request YYSYBE08 API处理方法 - 使用阿里云二要素验证
@@ -20,56 +21,117 @@ func ProcessYYSYBE08Request(ctx context.Context, params []byte, deps *processors
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
// 调用阿里云二要素验证API
reqData := map[string]interface{}{
"name": paramsDto.Name,
reqFormData := map[string]interface{}{
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
}
respBytes, err := deps.AlicloudService.CallAPI("api-mall/api/id_card/check", reqData)
//走政务接口 - 使用 app_id2 和 app_secret2
deps.ShumaiService.UseGovernment()
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrNotFound) {
// 查无记录情况
return nil, errors.Join(processors.ErrNotFound, err)
} else if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
// 解析阿里云响应
var alicloudResponse struct {
// 解析数脉响应 - 兼容完整响应格式或仅data格式
var shumaiResponse struct {
Msg string `json:"msg"`
Success bool `json:"success"`
Code int `json:"code"`
Data struct {
Birthday string `json:"birthday"`
Result int `json:"result"`
Address string `json:"address"`
OrderNo string `json:"orderNo"`
Sex string `json:"sex"`
OrderNo string `json:"order_no"`
Desc string `json:"desc"`
Sex string `json:"sex"`
Birthday string `json:"birthday"`
Address string `json:"address"`
} `json:"data"`
}
if err := json.Unmarshal(respBytes, &alicloudResponse); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
// 尝试解析完整响应格式
if err := json.Unmarshal(respBytes, &shumaiResponse); err != nil {
// 如果解析失败可能是只返回了data部分尝试直接解析为data结构
var dataOnly struct {
Result int `json:"result"`
OrderNo string `json:"order_no"`
Desc string `json:"desc"`
Sex string `json:"sex"`
Birthday string `json:"birthday"`
Address string `json:"address"`
}
if err2 := json.Unmarshal(respBytes, &dataOnly); err2 != nil {
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("响应解析失败: %w", err))
}
// 将dataOnly赋值给shumaiResponse.Data
shumaiResponse.Data = dataOnly
shumaiResponse.Code = 200 // 默认成功
shumaiResponse.Success = true
}
// 检查响应状态
if alicloudResponse.Code != 200 && alicloudResponse.Code != 400 {
return nil, fmt.Errorf("%s: %s", processors.ErrDatasource, alicloudResponse.Msg)
// 检查响应状态
if shumaiResponse.Code != 200 {
// code != 200 表示请求失败,返回错误
msg := shumaiResponse.Msg
if msg == "" {
msg = "数据源异常"
}
return nil, fmt.Errorf("%s: %s", processors.ErrDatasource, msg)
}
// 构建返回结果
// code == 200 时,根据 data.result 判断验证结果
// result: 0 一致收费1 不一致收费2 无记录(预留)
resultCode := "0XXX" // 默认成功
resultMsg := "验证通过"
verifyResult := "一致"
if alicloudResponse.Code == 400 {
resultCode = "5XXX"
resultMsg = "请输入有效的身份证号码"
verifyResult = "不一致"
} else {
if alicloudResponse.Data.Result != 0 {
// 验证失败
switch shumaiResponse.Data.Result {
case 0:
// 一致(验证通过)
resultCode = "0XXX"
resultMsg = "验证通过"
verifyResult = "一致"
// 如果desc字段有值使用desc作为resultMsg
if shumaiResponse.Data.Desc != "" {
resultMsg = shumaiResponse.Data.Desc
}
case 1:
// 不一致(验证失败)
resultCode = "5XXX"
resultMsg = "身份证号不匹配"
verifyResult = "不一致"
// 如果desc字段有值使用desc作为resultMsg
if shumaiResponse.Data.Desc != "" {
resultMsg = shumaiResponse.Data.Desc
}
case 2:
// 无记录
resultCode = "5XXX"
resultMsg = "无记录"
verifyResult = "不一致"
// 如果desc字段有值使用desc作为resultMsg
if shumaiResponse.Data.Desc != "" {
resultMsg = shumaiResponse.Data.Desc
}
default:
// 其他未知状态,按不一致处理
resultCode = "5XXX"
resultMsg = "验证失败"
verifyResult = "不一致"
if shumaiResponse.Data.Desc != "" {
resultMsg = shumaiResponse.Data.Desc
}
}
// 构建最终响应结构

View File

@@ -0,0 +1,46 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSYC4R9Request YYSYC4R9 运营商三要素详版API处理方法
func ProcessYYSYC4R9Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYC4R9Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
"mobile": paramsDto.MobileNo,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v2/mobile_three/check/detail" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,44 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSYE7V5Request YYSYE7V5 手机在网状态查询API处理方法
func ProcessYYSYE7V5Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYE7V5Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"mobile": paramsDto.MobileNo,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v1/mobile_status/check" // 接口路径,根据数脉文档填写(
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,45 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSYF2T7Request YYSYF2T7 手机二次放号检测查询API处理方法
func ProcessYYSYF2T7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYF2T7Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"mobile": paramsDto.MobileNo,
"date": paramsDto.DateRange,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/mobile_twice/check" // 接口路径,根据数脉文档填写(
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,46 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSYH6D2Request YYSYH6D2 运营商三要素简版API处理方法
func ProcessYYSYH6D2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYH6D2Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
"mobile": paramsDto.MobileNo,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
deps.ShumaiService.UseGovernment()
apiPath := "/v4/mobile_three/check" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,48 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSYH6F3Request YYSYH6F3 运营商三要素政务版API处理方法
func ProcessYYSYH6F3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYH6F3Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"idcard": paramsDto.IDCard,
"name": paramsDto.Name,
"mobile": paramsDto.MobileNo,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
// 走政务接口使用这个
apiPath := "/v4/mobile_three/check" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,44 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSYK8R3Request YYSYK8R3 手机空号检测查询API处理方法
func ProcessYYSYK8R3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYK8R3Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"mobile": paramsDto.MobileNo,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/mobile_empty/check" // 接口路径,根据数脉文档填写(
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,44 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSYP0T4Request YYSYP0T4 在网时长API处理方法
func ProcessYYSYP0T4Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYP0T4Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"mobile": paramsDto.MobileNo,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v2/mobile_online/check" // 接口路径,根据数脉文档填写(如 v4/xxx
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,44 @@
package yysy
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/shumai"
)
// ProcessYYSYS9W1Request YYSYS9W1 手机携号转网查询API处理方法
func ProcessYYSYS9W1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.YYSYS9W1Req
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
reqFormData := map[string]interface{}{
"mobile": paramsDto.MobileNo,
}
// 以表单方式调用数脉 API参数在 CallAPIForm 内转为 application/x-www-form-urlencoded
apiPath := "/v4/mobile-transfer/query" // 接口路径,根据数脉文档填写(
respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData)
if err != nil {
if errors.Is(err, shumai.ErrDatasource) {
// 数据源错误
return nil, errors.Join(processors.ErrDatasource, err)
} else if errors.Is(err, shumai.ErrSystem) {
// 系统错误
return nil, errors.Join(processors.ErrSystem, err)
} else {
// 其他未知错误
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -71,7 +71,7 @@ func (s *EnterpriseInfoSubmitRecordService) Save(ctx context.Context, enterprise
return s.repositories.Create(ctx, enterpriseInfoSubmitRecord)
}
// ValidateWithWestdex 调用QYGL23T7处理器验证企业信息
// ValidateWithWestdex 调用QYGL5CMP处理器验证企业信息
func (s *EnterpriseInfoSubmitRecordService) ValidateWithWestdex(ctx context.Context, info *value_objects.EnterpriseInfo) error {
if info == nil {
return errors.New("企业信息不能为空")
@@ -89,12 +89,13 @@ func (s *EnterpriseInfoSubmitRecordService) ValidateWithWestdex(ctx context.Cont
// return nil
// }
// 构建QYGL23T7请求参数
reqDto := dto.QYGL23T7Req{
// 构建QYGL5CMP请求参数
reqDto := dto.QYGL5CMPReq{
EntName: info.CompanyName,
LegalPerson: info.LegalPersonName,
EntCode: info.UnifiedSocialCode,
IDCard: info.LegalPersonID,
MobileNo: info.LegalPersonPhone,
}
// 序列化请求参数

View 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,
}
}

View File

@@ -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)
}

View File

@@ -14,19 +14,25 @@ type ComponentReportDownload struct {
UserID 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:"产品编号"`
ProductName string `gorm:"type:varchar(200);not null" comment:"产品名称"`
// 直接关联购买订单
OrderID *string `gorm:"type:varchar(36);index" comment:"关联的购买订单ID"`
OrderNumber *string `gorm:"type:varchar(64);index" comment:"关联的购买订单号"`
// 组合包相关字段(从购买记录复制)
SubProductIDs string `gorm:"type:text" comment:"子产品ID列表JSON数组组合包使用"`
SubProductCodes string `gorm:"type:text" comment:"子产品编号列表JSON数组"`
DownloadPrice decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"实际支付价格"`
OriginalPrice decimal.Decimal `gorm:"type:decimal(10,2);not null" 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"`
PaymentStatus string `gorm:"type:varchar(20);default:'pending';index" comment:"支付状态pending, success, failed"`
// 价格相关字段
OriginalPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0.00" comment:"原始价格组合包使用UIComponentPrice单品使用Price"`
DownloadPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0.00" comment:"实际支付价格"`
// 下载相关信息
FilePath *string `gorm:"type:varchar(500)" comment:"生成的ZIP文件路径用于二次下载"`
FileHash *string `gorm:"type:varchar(64)" comment:"文件哈希值(用于缓存验证)"`
DownloadCount int `gorm:"default:0" comment:"下载次数"`
LastDownloadAt *time.Time `comment:"最后下载时间"`
ExpiresAt *time.Time `gorm:"index" comment:"下载有效期(支付成功后30天"`
ExpiresAt *time.Time `gorm:"index" comment:"下载有效期(从创建日起30天"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
@@ -46,11 +52,6 @@ func (c *ComponentReportDownload) BeforeCreate(tx *gorm.DB) error {
return nil
}
// IsPaid 检查是否已支付
func (c *ComponentReportDownload) IsPaid() bool {
return c.PaymentStatus == "success"
}
// IsExpired 检查是否已过期
func (c *ComponentReportDownload) IsExpired() bool {
if c.ExpiresAt == nil {
@@ -61,5 +62,6 @@ func (c *ComponentReportDownload) IsExpired() bool {
// CanDownload 检查是否可以下载
func (c *ComponentReportDownload) CanDownload() bool {
return c.IsPaid() && !c.IsExpired()
// 下载记录存在即表示用户有下载权限,只需检查是否过期
return !c.IsExpired()
}

View File

@@ -16,7 +16,8 @@ type Product struct {
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"产品编号"`
Description string `gorm:"type:text" comment:"产品简介"`
Content string `gorm:"type:text" comment:"产品内容"`
CategoryID string `gorm:"type:varchar(36);not null" comment:"产品分类ID"`
CategoryID string `gorm:"type:varchar(36);not null" comment:"一级分类ID"`
SubCategoryID *string `gorm:"type:varchar(36);index" comment:"二级分类ID"`
Price decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
CostPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0" comment:"成本价"`
Remark string `gorm:"type:text" comment:"备注"`
@@ -34,7 +35,8 @@ type Product struct {
SEOKeywords string `gorm:"type:text" comment:"SEO关键词"`
// 关联关系
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"产品分类"`
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"一级分类"`
SubCategory *ProductSubCategory `gorm:"foreignKey:SubCategoryID" comment:"二级分类"`
Documentation *ProductDocumentation `gorm:"foreignKey:ProductID" comment:"产品文档"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
@@ -118,3 +120,34 @@ func (p *Product) GetOldID() string {
func (p *Product) HasOldID() bool {
return p.OldID != nil && *p.OldID != ""
}
// HasSubCategory 检查是否有二级分类
func (p *Product) HasSubCategory() bool {
return p.SubCategoryID != nil && *p.SubCategoryID != ""
}
// GetFullCategoryPath 获取完整分类路径(一级分类/二级分类)
func (p *Product) GetFullCategoryPath() string {
if p.Category == nil {
return ""
}
if p.SubCategory != nil {
return p.Category.Name + " / " + p.SubCategory.Name
}
return p.Category.Name
}
// GetFullCategoryCode 获取完整分类编号(一级分类编号.二级分类编号)
func (p *Product) GetFullCategoryCode() string {
if p.Category == nil {
return ""
}
if p.SubCategory != nil {
return p.Category.Code + "." + p.SubCategory.Code
}
return p.Category.Code
}

View File

@@ -19,6 +19,7 @@ type ProductCategory struct {
// 关联关系
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
SubCategories []ProductSubCategory `gorm:"foreignKey:CategoryID" comment:"二级分类列表"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`

View File

@@ -0,0 +1,82 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// ProductSubCategory 产品二级分类实体
type ProductSubCategory struct {
ID string `gorm:"primaryKey;type:varchar(36)" comment:"二级分类ID"`
Name string `gorm:"type:varchar(100);not null" comment:"二级分类名称"`
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"二级分类编号"`
Description string `gorm:"type:text" comment:"二级分类描述"`
CategoryID string `gorm:"type:varchar(36);not null;index" comment:"一级分类ID"`
Sort int `gorm:"default:0" comment:"排序"`
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
IsVisible bool `gorm:"default:true" comment:"是否展示"`
// 关联关系
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"一级分类"`
Products []Product `gorm:"foreignKey:SubCategoryID" comment:"产品列表"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (psc *ProductSubCategory) BeforeCreate(tx *gorm.DB) error {
if psc.ID == "" {
psc.ID = uuid.New().String()
}
return nil
}
// IsValid 检查二级分类是否有效
func (psc *ProductSubCategory) IsValid() bool {
return psc.DeletedAt.Time.IsZero() && psc.IsEnabled
}
// IsVisibleToUser 检查二级分类是否对用户可见
func (psc *ProductSubCategory) IsVisibleToUser() bool {
return psc.IsValid() && psc.IsVisible
}
// Enable 启用二级分类
func (psc *ProductSubCategory) Enable() {
psc.IsEnabled = true
}
// Disable 禁用二级分类
func (psc *ProductSubCategory) Disable() {
psc.IsEnabled = false
}
// Show 显示二级分类
func (psc *ProductSubCategory) Show() {
psc.IsVisible = true
}
// Hide 隐藏二级分类
func (psc *ProductSubCategory) Hide() {
psc.IsVisible = false
}
// GetFullPath 获取完整路径(一级分类/二级分类)
func (psc *ProductSubCategory) GetFullPath() string {
if psc.Category != nil {
return psc.Category.Name + " / " + psc.Name
}
return psc.Name
}
// GetFullCode 获取完整编号(一级分类编号.二级分类编号)
func (psc *ProductSubCategory) GetFullCode() string {
if psc.Category != nil {
return psc.Category.Code + "." + psc.Code
}
return psc.Code
}

View File

@@ -14,6 +14,7 @@ type Subscription struct {
UserID 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:"订阅价格"`
UIComponentPrice decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"UI组件价格组合包使用"`
APIUsed int64 `gorm:"default:0" comment:"已使用API调用次数"`
Version int64 `gorm:"default:1" comment:"乐观锁版本号"`

View File

@@ -19,6 +19,7 @@ type UIComponent struct {
FileType *string `gorm:"type:varchar(50)" json:"file_type" comment:"文件类型"`
FolderPath *string `gorm:"type:varchar(500)" json:"folder_path" comment:"组件文件夹路径"`
IsExtracted bool `gorm:"default:false" json:"is_extracted" comment:"是否已解压"`
FileUploadTime *time.Time `gorm:"type:timestamp" json:"file_upload_time" comment:"文件上传时间"`
Version string `gorm:"type:varchar(20)" json:"version" comment:"组件版本"`
IsActive bool `gorm:"default:true" json:"is_active" comment:"是否启用"`
SortOrder int `gorm:"default:0" json:"sort_order" comment:"排序"`

View File

@@ -9,7 +9,7 @@ import (
// ComponentReportRepository 组件报告仓储接口
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
@@ -20,6 +20,15 @@ type ComponentReportRepository interface {
// 获取用户的下载记录列表
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)

View File

@@ -0,0 +1,22 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/product/entities"
)
// ProductSubCategoryRepository 产品二级分类仓储接口
type ProductSubCategoryRepository interface {
// 基础CRUD方法
GetByID(ctx context.Context, id string) (*entities.ProductSubCategory, error)
Create(ctx context.Context, category entities.ProductSubCategory) (*entities.ProductSubCategory, error)
Update(ctx context.Context, category entities.ProductSubCategory) error
Delete(ctx context.Context, id string) error
List(ctx context.Context) ([]*entities.ProductSubCategory, error)
// 查询方法
FindByCode(ctx context.Context, code string) (*entities.ProductSubCategory, error)
FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.ProductSubCategory, error)
FindVisible(ctx context.Context) ([]*entities.ProductSubCategory, error)
FindEnabled(ctx context.Context) ([]*entities.ProductSubCategory, error)
}

View File

@@ -20,6 +20,7 @@ import (
type ProductManagementService struct {
productRepo repositories.ProductRepository
categoryRepo repositories.ProductCategoryRepository
subCategoryRepo repositories.ProductSubCategoryRepository
logger *zap.Logger
}
@@ -27,11 +28,13 @@ type ProductManagementService struct {
func NewProductManagementService(
productRepo repositories.ProductRepository,
categoryRepo repositories.ProductCategoryRepository,
subCategoryRepo repositories.ProductSubCategoryRepository,
logger *zap.Logger,
) *ProductManagementService {
return &ProductManagementService{
productRepo: productRepo,
categoryRepo: categoryRepo,
subCategoryRepo: subCategoryRepo,
logger: logger,
}
}
@@ -306,6 +309,21 @@ func (s *ProductManagementService) ValidateProduct(product *entities.Product) er
}
}
// 验证二级分类是否存在(如果设置了二级分类)
if product.SubCategoryID != nil && *product.SubCategoryID != "" {
subCategory, err := s.subCategoryRepo.GetByID(context.Background(), *product.SubCategoryID)
if err != nil {
return fmt.Errorf("产品二级分类不存在: %w", err)
}
if !subCategory.IsValid() {
return errors.New("产品二级分类已禁用或删除")
}
// 验证二级分类是否属于指定的一级分类
if subCategory.CategoryID != product.CategoryID {
return errors.New("二级分类不属于指定的一级分类")
}
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More