This commit is contained in:
2025-12-05 14:59:23 +08:00
parent bfedec249f
commit 05b6623e75
17 changed files with 677 additions and 37 deletions

1
go.mod
View File

@@ -91,6 +91,7 @@ require (
github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.4 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/smartwalle/ncrypto v1.0.4 // indirect github.com/smartwalle/ncrypto v1.0.4 // indirect
github.com/smartwalle/ngx v1.0.9 // indirect github.com/smartwalle/ngx v1.0.9 // indirect
github.com/smartwalle/nsign v1.0.9 // indirect github.com/smartwalle/nsign v1.0.9 // indirect

2
go.sum
View File

@@ -208,6 +208,8 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/smartwalle/alipay/v3 v3.2.25 h1:cRDN+fpDWTVHnuHIF/vsJETskRXS/S+fDOdAkzXmV/Q= github.com/smartwalle/alipay/v3 v3.2.25 h1:cRDN+fpDWTVHnuHIF/vsJETskRXS/S+fDOdAkzXmV/Q=
github.com/smartwalle/alipay/v3 v3.2.25/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE= github.com/smartwalle/alipay/v3 v3.2.25/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8= github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=

View File

@@ -10,6 +10,7 @@ import (
"tyapi-server/internal/application/product/dto/commands" "tyapi-server/internal/application/product/dto/commands"
appQueries "tyapi-server/internal/application/product/dto/queries" appQueries "tyapi-server/internal/application/product/dto/queries"
"tyapi-server/internal/application/product/dto/responses" "tyapi-server/internal/application/product/dto/responses"
domain_api_repo "tyapi-server/internal/domains/api/repositories"
"tyapi-server/internal/domains/product/entities" "tyapi-server/internal/domains/product/entities"
repoQueries "tyapi-server/internal/domains/product/repositories/queries" repoQueries "tyapi-server/internal/domains/product/repositories/queries"
product_service "tyapi-server/internal/domains/product/services" product_service "tyapi-server/internal/domains/product/services"
@@ -21,6 +22,7 @@ import (
type SubscriptionApplicationServiceImpl struct { type SubscriptionApplicationServiceImpl struct {
productSubscriptionService *product_service.ProductSubscriptionService productSubscriptionService *product_service.ProductSubscriptionService
userRepo user_repositories.UserRepository userRepo user_repositories.UserRepository
apiCallRepository domain_api_repo.ApiCallRepository
logger *zap.Logger logger *zap.Logger
} }
@@ -28,11 +30,13 @@ type SubscriptionApplicationServiceImpl struct {
func NewSubscriptionApplicationService( func NewSubscriptionApplicationService(
productSubscriptionService *product_service.ProductSubscriptionService, productSubscriptionService *product_service.ProductSubscriptionService,
userRepo user_repositories.UserRepository, userRepo user_repositories.UserRepository,
apiCallRepository domain_api_repo.ApiCallRepository,
logger *zap.Logger, logger *zap.Logger,
) SubscriptionApplicationService { ) SubscriptionApplicationService {
return &SubscriptionApplicationServiceImpl{ return &SubscriptionApplicationServiceImpl{
productSubscriptionService: productSubscriptionService, productSubscriptionService: productSubscriptionService,
userRepo: userRepo, userRepo: userRepo,
apiCallRepository: apiCallRepository,
logger: logger, logger: logger,
} }
} }
@@ -262,17 +266,30 @@ func (s *SubscriptionApplicationServiceImpl) GetProductSubscriptions(ctx context
} }
// GetSubscriptionUsage 获取订阅使用情况 // GetSubscriptionUsage 获取订阅使用情况
// 业务流程1. 获取订阅使用情况 2. 构建响应数据 // 业务流程1. 获取订阅信息 2. 根据产品ID和用户ID统计API调用次数 3. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) { func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) {
// 获取订阅信息
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID) subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 根据用户ID和产品ID统计API调用次数
apiCallCount, err := s.apiCallRepository.CountByUserIdAndProductId(ctx, subscription.UserID, subscription.ProductID)
if err != nil {
s.logger.Warn("统计API调用次数失败使用订阅记录中的值",
zap.String("subscription_id", subscriptionID),
zap.String("user_id", subscription.UserID),
zap.String("product_id", subscription.ProductID),
zap.Error(err))
// 如果统计失败使用订阅实体中的APIUsed字段作为备选
apiCallCount = subscription.APIUsed
}
return &responses.SubscriptionUsageResponse{ return &responses.SubscriptionUsageResponse{
ID: subscription.ID, ID: subscription.ID,
ProductID: subscription.ProductID, ProductID: subscription.ProductID,
APIUsed: subscription.APIUsed, APIUsed: apiCallCount,
}, nil }, nil
} }

View File

@@ -195,6 +195,18 @@ type IVYZGZ08Req struct {
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`
} }
type IVYZ2B2TReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
QueryReasonId int64 `json:"query_reason_id" validate:"required"`
}
type IVYZ5A9tReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"`
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"`
}
type FLXG8A3FReq struct { type FLXG8A3FReq struct {
IDCard string `json:"id_card" validate:"required,validIDCard"` IDCard string `json:"id_card" validate:"required,validIDCard"`
Name string `json:"name" validate:"required,min=1,validName"` Name string `json:"name" validate:"required,min=1,validName"`

View File

@@ -25,6 +25,9 @@ type ApiCallRepository interface {
// 新增统计用户API调用次数 // 新增统计用户API调用次数
CountByUserId(ctx context.Context, userId string) (int64, error) CountByUserId(ctx context.Context, userId string) (int64, error)
// 新增根据用户ID和产品ID统计API调用次数
CountByUserIdAndProductId(ctx context.Context, userId string, productId string) (int64, error)
// 新增根据TransactionID查询 // 新增根据TransactionID查询
FindByTransactionId(ctx context.Context, transactionId string) (*entities.ApiCall, error) FindByTransactionId(ctx context.Context, transactionId string) (*entities.ApiCall, error)

View File

@@ -202,6 +202,8 @@ func registerAllProcessors(combService *comb.CombService) {
"IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest, "IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest,
"IVYZ2C1P": ivyz.ProcessIVYZ2C1PRequest, "IVYZ2C1P": ivyz.ProcessIVYZ2C1PRequest,
"IVYZP2Q6": ivyz.ProcessIVYZP2Q6Request, "IVYZP2Q6": ivyz.ProcessIVYZP2Q6Request,
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
// COMB系列处理器 - 只注册有自定义逻辑的组合包 // COMB系列处理器 - 只注册有自定义逻辑的组合包
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑重命名ApiCode "COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑重命名ApiCode

View File

@@ -195,6 +195,8 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
"QYGL5A9T": &dto.QYGL5A9TReq{}, //全国企业各类工商风险统计数量查询 "QYGL5A9T": &dto.QYGL5A9TReq{}, //全国企业各类工商风险统计数量查询
"JRZQ3P01": &dto.JRZQ3P01Req{}, //天远风控决策 "JRZQ3P01": &dto.JRZQ3P01Req{}, //天远风控决策
"JRZQ3AG6": &dto.JRZQ3AG6Req{}, //轻松查公积 "JRZQ3AG6": &dto.JRZQ3AG6Req{}, //轻松查公积
"IVYZ2B2T": &dto.IVYZ2B2TReq{}, //能力资质核验(学历)
"IVYZ5A9O": &dto.IVYZ5A9tReq{}, //全国⾃然⼈⻛险评估评分模型
} }
@@ -319,6 +321,7 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string
values := strings.TrimPrefix(rule, "oneof=") values := strings.TrimPrefix(rule, "oneof=")
frontendRules = append(frontendRules, "可选值: "+values) frontendRules = append(frontendRules, "可选值: "+values)
} }
} }
return strings.Join(frontendRules, "、") return strings.Join(frontendRules, "、")
@@ -394,6 +397,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
"photo_data": "人脸图片", "photo_data": "人脸图片",
"owner_type": "企业主类型", "owner_type": "企业主类型",
"type": "查询类型", "type": "查询类型",
"query_reason_id": "查询原因ID",
} }
if label, exists := labelMap[jsonTag]; exists { if label, exists := labelMap[jsonTag]; exists {
@@ -438,6 +442,7 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
"photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"ownerType": "1", "ownerType": "1",
"type": "per", "type": "per",
"query_reason_id": "1",
} }
if example, exists := exampleMap[jsonTag]; exists { if example, exists := exampleMap[jsonTag]; exists {
@@ -491,6 +496,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
"photo_data": "请输入base64编码的人脸图片支持JPG、BMP、PNG格式", "photo_data": "请输入base64编码的人脸图片支持JPG、BMP、PNG格式",
"ownerType": "请选择企业主类型", "ownerType": "请选择企业主类型",
"type": "请选择查询类型", "type": "请选择查询类型",
"query_reason_id": "请选择查询原因ID",
} }
if placeholder, exists := placeholderMap[jsonTag]; exists { if placeholder, exists := placeholderMap[jsonTag]; exists {
@@ -546,6 +552,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
"photo_data": "人脸图片必填base64编码的图片数据仅支持JPG、BMP、PNG三种格式", "photo_data": "人脸图片必填base64编码的图片数据仅支持JPG、BMP、PNG三种格式",
"owner_type": "企业主类型编码1-法定代表人2-主要人员3-自然人股东4-法定代表人及自然人股东5-其他", "owner_type": "企业主类型编码1-法定代表人2-主要人员3-自然人股东4-法定代表人及自然人股东5-其他",
"type": "查询类型per-人员ent-企业 1", "type": "查询类型per-人员ent-企业 1",
"query_reason_id": "查询原因ID1-授信审批2-贷中管理3-贷后管理4-异议处理5-担保查询6-租赁资质审查7-融资租赁审批8-借贷撮合查询9-保险审批10-资质审核11-风控审核12-企业背调",
} }
if desc, exists := descMap[jsonTag]; exists { if desc, exists := descMap[jsonTag]; exists {

View File

@@ -0,0 +1,58 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"strconv"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessIVYZ2B2TRequest IVYZ2B2T API处理方法 能力资质核验(学历)
func ProcessIVYZ2B2TRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ2B2TReq
if err := json.Unmarshal(params, &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.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedQueryReasonId, err := deps.WestDexService.Encrypt(strconv.FormatInt(paramsDto.QueryReasonId, 10))
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"idCard": encryptedIDCard,
"name": encryptedName,
"queryReasonId": encryptedQueryReasonId,
},
}
respBytes, err := deps.WestDexService.CallAPI(ctx, "G11JX01", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -0,0 +1,57 @@
package ivyz
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/westdex"
)
// ProcessIVYZ5A9ORequest IVYZ5A9O API处理方法 全国⾃然⼈⻛险评估评分模型
func ProcessIVYZ5A9ORequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.IVYZ5A9tReq
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.WestDexService.Encrypt(paramsDto.Name)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
encryptedAuthAuthorizeFileCode, err := deps.WestDexService.Encrypt(paramsDto.AuthAuthorizeFileCode)
if err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
reqData := map[string]interface{}{
"data": map[string]interface{}{
"idcard": encryptedIDCard,
"name": encryptedName,
"auth_authorizeFileCode": encryptedAuthAuthorizeFileCode,
},
}
respBytes, err := deps.WestDexService.CallAPI(ctx, "G01SC01", reqData)
if err != nil {
if errors.Is(err, westdex.ErrDatasource) {
return nil, errors.Join(processors.ErrDatasource, err)
} else {
return nil, errors.Join(processors.ErrSystem, err)
}
}
return respBytes, nil
}

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/zhicha" "tyapi-server/internal/infrastructure/external/zhicha"

View File

@@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"time" "time"
"tyapi-server/internal/domains/api/entities" "tyapi-server/internal/domains/api/entities"
"tyapi-server/internal/domains/api/repositories" "tyapi-server/internal/domains/api/repositories"
@@ -229,6 +230,11 @@ func (r *GormApiCallRepository) CountByUserId(ctx context.Context, userId string
return r.CountWhere(ctx, &entities.ApiCall{}, "user_id = ?", userId) return r.CountWhere(ctx, &entities.ApiCall{}, "user_id = ?", userId)
} }
// CountByUserIdAndProductId 按用户ID和产品ID统计API调用次数
func (r *GormApiCallRepository) CountByUserIdAndProductId(ctx context.Context, userId string, productId string) (int64, error) {
return r.CountWhere(ctx, &entities.ApiCall{}, "user_id = ? AND product_id = ?", userId, productId)
}
// CountByUserIdAndDateRange 按用户ID和日期范围统计API调用次数 // CountByUserIdAndDateRange 按用户ID和日期范围统计API调用次数
func (r *GormApiCallRepository) CountByUserIdAndDateRange(ctx context.Context, userId string, startDate, endDate time.Time) (int64, error) { func (r *GormApiCallRepository) CountByUserIdAndDateRange(ctx context.Context, userId string, startDate, endDate time.Time) (int64, error) {
return r.CountWhere(ctx, &entities.ApiCall{}, "user_id = ? AND created_at >= ? AND created_at < ?", userId, startDate, endDate) return r.CountWhere(ctx, &entities.ApiCall{}, "user_id = ? AND created_at >= ? AND created_at < ?", userId, startDate, endDate)
@@ -304,8 +310,29 @@ func (r *GormApiCallRepository) ListWithFiltersAndProductName(ctx context.Contex
// 应用筛选条件 // 应用筛选条件
if filters != nil { if filters != nil {
// 用户ID筛选 // 用户ID筛选支持单个user_id和多个user_ids
if userId, ok := filters["user_id"].(string); ok && userId != "" { // 如果同时存在优先使用user_ids批量查询
if userIds, ok := filters["user_ids"].(string); ok && userIds != "" {
// 解析逗号分隔的用户ID列表
userIdsList := strings.Split(userIds, ",")
// 去除空白字符
var cleanUserIds []string
for _, id := range userIdsList {
id = strings.TrimSpace(id)
if id != "" {
cleanUserIds = append(cleanUserIds, id)
}
}
if len(cleanUserIds) > 0 {
placeholders := strings.Repeat("?,", len(cleanUserIds))
placeholders = placeholders[:len(placeholders)-1] // 移除最后一个逗号
whereCondition += " AND ac.user_id IN (" + placeholders + ")"
for _, id := range cleanUserIds {
whereArgs = append(whereArgs, id)
}
}
} else if userId, ok := filters["user_id"].(string); ok && userId != "" {
// 单个用户ID筛选
whereCondition += " AND ac.user_id = ?" whereCondition += " AND ac.user_id = ?"
whereArgs = append(whereArgs, userId) whereArgs = append(whereArgs, userId)
} }

View File

@@ -89,37 +89,86 @@ func (r *GormRechargeRecordRepository) UpdateStatus(ctx context.Context, id stri
func (r *GormRechargeRecordRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) { func (r *GormRechargeRecordRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
var count int64 var count int64
query := r.GetDB(ctx).Model(&entities.RechargeRecord{})
// 检查是否有 company_name 筛选,如果有则需要 JOIN 表
hasCompanyNameFilter := false
if options.Filters != nil {
if companyName, ok := options.Filters["company_name"].(string); ok && companyName != "" {
hasCompanyNameFilter = true
}
}
var query *gorm.DB
if hasCompanyNameFilter {
// 使用 JOIN 查询以支持企业名称筛选
query = r.GetDB(ctx).Table("recharge_records rr").
Joins("LEFT JOIN users u ON rr.user_id = u.id").
Joins("LEFT JOIN enterprise_infos ei ON u.id = ei.user_id")
} else {
// 普通查询
query = r.GetDB(ctx).Model(&entities.RechargeRecord{})
}
if options.Filters != nil { if options.Filters != nil {
for key, value := range options.Filters { for key, value := range options.Filters {
// 特殊处理时间范围过滤器 // 特殊处理时间范围过滤器
if key == "start_time" { if key == "start_time" {
if startTime, ok := value.(time.Time); ok { if startTime, ok := value.(time.Time); ok {
query = query.Where("created_at >= ?", startTime) if hasCompanyNameFilter {
query = query.Where("rr.created_at >= ?", startTime)
} else {
query = query.Where("created_at >= ?", startTime)
}
} }
} else if key == "end_time" { } else if key == "end_time" {
if endTime, ok := value.(time.Time); ok { if endTime, ok := value.(time.Time); ok {
query = query.Where("created_at <= ?", endTime) if hasCompanyNameFilter {
query = query.Where("rr.created_at <= ?", endTime)
} else {
query = query.Where("created_at <= ?", endTime)
}
}
} else if key == "company_name" {
// 处理企业名称筛选
if companyName, ok := value.(string); ok && companyName != "" {
query = query.Where("ei.company_name LIKE ?", "%"+companyName+"%")
} }
} else if key == "min_amount" { } else if key == "min_amount" {
// 处理最小金额支持string、int、int64类型 // 处理最小金额支持string、int、int64类型
if amount, err := r.parseAmount(value); err == nil { if amount, err := r.parseAmount(value); err == nil {
query = query.Where("amount >= ?", amount) if hasCompanyNameFilter {
query = query.Where("rr.amount >= ?", amount)
} else {
query = query.Where("amount >= ?", amount)
}
} }
} else if key == "max_amount" { } else if key == "max_amount" {
// 处理最大金额支持string、int、int64类型 // 处理最大金额支持string、int、int64类型
if amount, err := r.parseAmount(value); err == nil { if amount, err := r.parseAmount(value); err == nil {
query = query.Where("amount <= ?", amount) if hasCompanyNameFilter {
query = query.Where("rr.amount <= ?", amount)
} else {
query = query.Where("amount <= ?", amount)
}
} }
} else { } else {
// 其他过滤器使用等值查询 // 其他过滤器使用等值查询
query = query.Where(key+" = ?", value) if hasCompanyNameFilter {
query = query.Where("rr."+key+" = ?", value)
} else {
query = query.Where(key+" = ?", value)
}
} }
} }
} }
if options.Search != "" { if options.Search != "" {
query = query.Where("user_id LIKE ? OR transfer_order_id LIKE ? OR alipay_order_id LIKE ?", if hasCompanyNameFilter {
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%") query = query.Where("rr.user_id LIKE ? OR rr.transfer_order_id LIKE ? OR rr.alipay_order_id LIKE ?",
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
} else {
query = query.Where("user_id LIKE ? OR transfer_order_id LIKE ? OR alipay_order_id LIKE ?",
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
}
} }
return count, query.Count(&count).Error return count, query.Count(&count).Error
} }
@@ -132,45 +181,98 @@ func (r *GormRechargeRecordRepository) Exists(ctx context.Context, id string) (b
func (r *GormRechargeRecordRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.RechargeRecord, error) { func (r *GormRechargeRecordRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.RechargeRecord, error) {
var records []entities.RechargeRecord var records []entities.RechargeRecord
query := r.GetDB(ctx).Model(&entities.RechargeRecord{})
// 检查是否有 company_name 筛选,如果有则需要 JOIN 表
hasCompanyNameFilter := false
if options.Filters != nil {
if companyName, ok := options.Filters["company_name"].(string); ok && companyName != "" {
hasCompanyNameFilter = true
}
}
var query *gorm.DB
if hasCompanyNameFilter {
// 使用 JOIN 查询以支持企业名称筛选
query = r.GetDB(ctx).Table("recharge_records rr").
Select("rr.*").
Joins("LEFT JOIN users u ON rr.user_id = u.id").
Joins("LEFT JOIN enterprise_infos ei ON u.id = ei.user_id")
} else {
// 普通查询
query = r.GetDB(ctx).Model(&entities.RechargeRecord{})
}
if options.Filters != nil { if options.Filters != nil {
for key, value := range options.Filters { for key, value := range options.Filters {
// 特殊处理 user_ids 过滤器 // 特殊处理 user_ids 过滤器
if key == "user_ids" { if key == "user_ids" {
if userIds, ok := value.(string); ok && userIds != "" { if userIds, ok := value.(string); ok && userIds != "" {
query = query.Where("user_id IN ?", strings.Split(userIds, ",")) if hasCompanyNameFilter {
query = query.Where("rr.user_id IN ?", strings.Split(userIds, ","))
} else {
query = query.Where("user_id IN ?", strings.Split(userIds, ","))
}
}
} else if key == "company_name" {
// 处理企业名称筛选
if companyName, ok := value.(string); ok && companyName != "" {
query = query.Where("ei.company_name LIKE ?", "%"+companyName+"%")
} }
} else if key == "start_time" { } else if key == "start_time" {
// 处理开始时间范围 // 处理开始时间范围
if startTime, ok := value.(time.Time); ok { if startTime, ok := value.(time.Time); ok {
query = query.Where("created_at >= ?", startTime) if hasCompanyNameFilter {
query = query.Where("rr.created_at >= ?", startTime)
} else {
query = query.Where("created_at >= ?", startTime)
}
} }
} else if key == "end_time" { } else if key == "end_time" {
// 处理结束时间范围 // 处理结束时间范围
if endTime, ok := value.(time.Time); ok { if endTime, ok := value.(time.Time); ok {
query = query.Where("created_at <= ?", endTime) if hasCompanyNameFilter {
query = query.Where("rr.created_at <= ?", endTime)
} else {
query = query.Where("created_at <= ?", endTime)
}
} }
} else if key == "min_amount" { } else if key == "min_amount" {
// 处理最小金额支持string、int、int64类型 // 处理最小金额支持string、int、int64类型
if amount, err := r.parseAmount(value); err == nil { if amount, err := r.parseAmount(value); err == nil {
query = query.Where("amount >= ?", amount) if hasCompanyNameFilter {
query = query.Where("rr.amount >= ?", amount)
} else {
query = query.Where("amount >= ?", amount)
}
} }
} else if key == "max_amount" { } else if key == "max_amount" {
// 处理最大金额支持string、int、int64类型 // 处理最大金额支持string、int、int64类型
if amount, err := r.parseAmount(value); err == nil { if amount, err := r.parseAmount(value); err == nil {
query = query.Where("amount <= ?", amount) if hasCompanyNameFilter {
query = query.Where("rr.amount <= ?", amount)
} else {
query = query.Where("amount <= ?", amount)
}
} }
} else { } else {
// 其他过滤器使用等值查询 // 其他过滤器使用等值查询
query = query.Where(key+" = ?", value) if hasCompanyNameFilter {
query = query.Where("rr."+key+" = ?", value)
} else {
query = query.Where(key+" = ?", value)
}
} }
} }
} }
if options.Search != "" { if options.Search != "" {
query = query.Where("user_id LIKE ? OR transfer_order_id LIKE ? OR alipay_order_id LIKE ?", if hasCompanyNameFilter {
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%") query = query.Where("rr.user_id LIKE ? OR rr.transfer_order_id LIKE ? OR rr.alipay_order_id LIKE ?",
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
} else {
query = query.Where("user_id LIKE ? OR transfer_order_id LIKE ? OR alipay_order_id LIKE ?",
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
}
} }
if options.Sort != "" { if options.Sort != "" {
@@ -178,9 +280,17 @@ func (r *GormRechargeRecordRepository) List(ctx context.Context, options interfa
if options.Order == "desc" || options.Order == "DESC" { if options.Order == "desc" || options.Order == "DESC" {
order = "DESC" order = "DESC"
} }
query = query.Order(options.Sort + " " + order) if hasCompanyNameFilter {
query = query.Order("rr." + options.Sort + " " + order)
} else {
query = query.Order(options.Sort + " " + order)
}
} else { } else {
query = query.Order("created_at DESC") if hasCompanyNameFilter {
query = query.Order("rr.created_at DESC")
} else {
query = query.Order("created_at DESC")
}
} }
if options.Page > 0 && options.PageSize > 0 { if options.Page > 0 && options.PageSize > 0 {

View File

@@ -382,12 +382,26 @@ func (r *GormWalletTransactionRepository) ListWithFiltersAndProductName(ctx cont
// 应用筛选条件 // 应用筛选条件
if filters != nil { if filters != nil {
// 用户ID筛选 // 用户ID筛选(支持单个和多个)
if userId, ok := filters["user_id"].(string); ok && userId != "" { if userIds, ok := filters["user_ids"].(string); ok && userIds != "" {
// 多个用户ID逗号分隔
userIdsList := strings.Split(userIds, ",")
whereCondition += " AND wt.user_id IN ?"
whereArgs = append(whereArgs, userIdsList)
} else if userId, ok := filters["user_id"].(string); ok && userId != "" {
// 单个用户ID
whereCondition += " AND wt.user_id = ?" whereCondition += " AND wt.user_id = ?"
whereArgs = append(whereArgs, userId) whereArgs = append(whereArgs, userId)
} }
// 产品ID筛选支持多个
if productIds, ok := filters["product_ids"].(string); ok && productIds != "" {
// 多个产品ID逗号分隔
productIdsList := strings.Split(productIds, ",")
whereCondition += " AND wt.product_id IN ?"
whereArgs = append(whereArgs, productIdsList)
}
// 时间范围筛选 // 时间范围筛选
if startTime, ok := filters["start_time"].(time.Time); ok { if startTime, ok := filters["start_time"].(time.Time); ok {
whereCondition += " AND wt.created_at >= ?" whereCondition += " AND wt.created_at >= ?"

View File

@@ -34,7 +34,7 @@ type ZhichaResp struct {
type ZhichaConfig struct { type ZhichaConfig struct {
URL string URL string
AppID string AppID string
AppSecret string AppSecret string
EncryptKey string EncryptKey string
} }
@@ -133,14 +133,13 @@ func (z *ZhichaService) CallAPI(ctx context.Context, proID string, params map[st
} else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() { } else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
// 检查是否是网络超时错误 // 检查是否是网络超时错误
isTimeout = true isTimeout = true
} else if errStr := err.Error(); } else if errStr := err.Error(); errStr == "context deadline exceeded" ||
errStr == "context deadline exceeded" || errStr == "timeout" ||
errStr == "timeout" || errStr == "Client.Timeout exceeded" ||
errStr == "Client.Timeout exceeded" || errStr == "net/http: request canceled" {
errStr == "net/http: request canceled" {
isTimeout = true isTimeout = true
} }
if isTimeout { if isTimeout {
// 超时错误应该返回数据源异常,而不是系统异常 // 超时错误应该返回数据源异常,而不是系统异常
err = errors.Join(ErrDatasource, fmt.Errorf("API请求超时: %v", err)) err = errors.Join(ErrDatasource, fmt.Errorf("API请求超时: %v", err))

View File

@@ -2,6 +2,7 @@ package handlers
import ( import (
"strconv" "strconv"
"strings"
"time" "time"
"tyapi-server/internal/application/api" "tyapi-server/internal/application/api"
"tyapi-server/internal/application/finance" "tyapi-server/internal/application/finance"
@@ -1237,13 +1238,27 @@ func (h *ProductAdminHandler) ExportAdminWalletTransactions(c *gin.Context) {
// 时间范围筛选 // 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" { if startTime := c.Query("start_time"); startTime != "" {
// 处理URL编码的+号,转换为空格
startTime = strings.ReplaceAll(startTime, "+", " ")
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil { if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t filters["start_time"] = t
} else {
// 尝试其他格式
if t, err := time.Parse("2006-01-02T15:04:05", startTime); err == nil {
filters["start_time"] = t
}
} }
} }
if endTime := c.Query("end_time"); endTime != "" { if endTime := c.Query("end_time"); endTime != "" {
// 处理URL编码的+号,转换为空格
endTime = strings.ReplaceAll(endTime, "+", " ")
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil { if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t filters["end_time"] = t
} else {
// 尝试其他格式
if t, err := time.Parse("2006-01-02T15:04:05", endTime); err == nil {
filters["end_time"] = t
}
} }
} }
@@ -1262,6 +1277,11 @@ func (h *ProductAdminHandler) ExportAdminWalletTransactions(c *gin.Context) {
filters["product_ids"] = productIds filters["product_ids"] = productIds
} }
// 企业名称筛选
if companyName := c.Query("company_name"); companyName != "" {
filters["company_name"] = companyName
}
// 金额范围筛选 // 金额范围筛选
if minAmount := c.Query("min_amount"); minAmount != "" { if minAmount := c.Query("min_amount"); minAmount != "" {
filters["min_amount"] = minAmount filters["min_amount"] = minAmount
@@ -1281,7 +1301,16 @@ func (h *ProductAdminHandler) ExportAdminWalletTransactions(c *gin.Context) {
fileData, err := h.financeAppService.ExportAdminWalletTransactions(c.Request.Context(), filters, format) fileData, err := h.financeAppService.ExportAdminWalletTransactions(c.Request.Context(), filters, format)
if err != nil { if err != nil {
h.logger.Error("导出消费记录失败", zap.Error(err)) h.logger.Error("导出消费记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "导出消费记录失败")
// 根据错误信息返回具体的提示
errMsg := err.Error()
if strings.Contains(errMsg, "没有找到符合条件的数据") || strings.Contains(errMsg, "没有数据") {
h.responseBuilder.NotFound(c, "没有找到符合筛选条件的数据,请调整筛选条件后重试")
} else if strings.Contains(errMsg, "参数") || strings.Contains(errMsg, "参数错误") {
h.responseBuilder.BadRequest(c, errMsg)
} else {
h.responseBuilder.BadRequest(c, "导出消费记录失败:"+errMsg)
}
return return
} }
@@ -1364,6 +1393,11 @@ func (h *ProductAdminHandler) GetAdminRechargeRecords(c *gin.Context) {
filters["max_amount"] = maxAmount filters["max_amount"] = maxAmount
} }
// 企业名称筛选
if companyName := c.Query("company_name"); companyName != "" {
filters["company_name"] = companyName
}
// 构建分页选项 // 构建分页选项
options := interfaces.ListOptions{ options := interfaces.ListOptions{
Page: page, Page: page,
@@ -1442,7 +1476,16 @@ func (h *ProductAdminHandler) ExportAdminRechargeRecords(c *gin.Context) {
fileData, err := h.financeAppService.ExportAdminRechargeRecords(c.Request.Context(), filters, format) fileData, err := h.financeAppService.ExportAdminRechargeRecords(c.Request.Context(), filters, format)
if err != nil { if err != nil {
h.logger.Error("导出充值记录失败", zap.Error(err)) h.logger.Error("导出充值记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "导出充值记录失败")
// 根据错误信息返回具体的提示
errMsg := err.Error()
if strings.Contains(errMsg, "没有找到符合条件的数据") || strings.Contains(errMsg, "没有数据") {
h.responseBuilder.NotFound(c, "没有找到符合筛选条件的充值记录,请调整筛选条件后重试")
} else if strings.Contains(errMsg, "参数") || strings.Contains(errMsg, "参数错误") {
h.responseBuilder.BadRequest(c, errMsg)
} else {
h.responseBuilder.BadRequest(c, "导出充值记录失败:"+errMsg)
}
return return
} }
@@ -1468,7 +1511,10 @@ func (h *ProductAdminHandler) GetAdminApiCalls(c *gin.Context) {
// 构建筛选条件 // 构建筛选条件
filters := make(map[string]interface{}) filters := make(map[string]interface{})
// 用户ID筛选 // 用户ID筛选支持单个user_id和多个user_ids根据需求使用
if userId := c.Query("user_id"); userId != "" {
filters["user_id"] = userId
}
if userIds := c.Query("user_ids"); userIds != "" { if userIds := c.Query("user_ids"); userIds != "" {
filters["user_ids"] = userIds filters["user_ids"] = userIds
} }
@@ -1566,7 +1612,16 @@ func (h *ProductAdminHandler) ExportAdminApiCalls(c *gin.Context) {
fileData, err := h.apiAppService.ExportAdminApiCalls(c.Request.Context(), filters, format) fileData, err := h.apiAppService.ExportAdminApiCalls(c.Request.Context(), filters, format)
if err != nil { if err != nil {
h.logger.Error("导出API调用记录失败", zap.Error(err)) h.logger.Error("导出API调用记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "导出API调用记录失败")
// 根据错误信息返回具体的提示
errMsg := err.Error()
if strings.Contains(errMsg, "没有找到符合条件的数据") || strings.Contains(errMsg, "没有数据") {
h.responseBuilder.NotFound(c, "没有找到符合筛选条件的API调用记录请调整筛选条件后重试")
} else if strings.Contains(errMsg, "参数") || strings.Contains(errMsg, "参数错误") {
h.responseBuilder.BadRequest(c, errMsg)
} else {
h.responseBuilder.BadRequest(c, "导出API调用记录失败"+errMsg)
}
return return
} }

View File

@@ -5,11 +5,13 @@ import (
"fmt" "fmt"
"math" "math"
"os" "os"
"path/filepath"
"strings" "strings"
"tyapi-server/internal/domains/product/entities" "tyapi-server/internal/domains/product/entities"
"github.com/jung-kurt/gofpdf/v2" "github.com/jung-kurt/gofpdf/v2"
qrcode "github.com/skip2/go-qrcode"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -298,6 +300,9 @@ func (pb *PageBuilder) AddDocumentationPages(pdf *gofpdf.Fpdf, doc *entities.Pro
} }
} }
} }
// 添加说明文字和二维码
pb.addAdditionalInfo(pdf, doc, chineseFontAvailable)
} }
// addSection 添加章节 // addSection 添加章节
@@ -829,3 +834,248 @@ func (pb *PageBuilder) safeSplitText(pdf *gofpdf.Fpdf, text string, width float6
return lines return lines
} }
// addAdditionalInfo 添加说明文字和二维码
func (pb *PageBuilder) addAdditionalInfo(pdf *gofpdf.Fpdf, doc *entities.ProductDocumentation, chineseFontAvailable bool) {
// 检查是否需要换页
pageWidth, pageHeight := pdf.GetPageSize()
_, _, _, bottomMargin := pdf.GetMargins()
currentY := pdf.GetY()
remainingHeight := pageHeight - currentY - bottomMargin
// 如果剩余空间不足,添加新页
if remainingHeight < 100 {
pdf.AddPage()
pb.addHeader(pdf, chineseFontAvailable)
pb.addWatermark(pdf, chineseFontAvailable)
pdf.SetY(45)
}
// 添加分隔线
pdf.Ln(10)
pdf.SetLineWidth(0.5)
pdf.SetDrawColor(200, 200, 200)
pdf.Line(15, pdf.GetY(), pageWidth-15, pdf.GetY())
pdf.SetDrawColor(0, 0, 0)
// 添加说明文字标题
pdf.Ln(15)
pdf.SetTextColor(0, 0, 0)
pb.fontManager.SetFont(pdf, "B", 16)
_, lineHt := pdf.GetFontSize()
pdf.CellFormat(0, lineHt, "接入流程说明", "", 1, "L", false, 0, "")
// 读取说明文本文件
explanationText := pb.readExplanationText()
if explanationText != "" {
pb.logger.Debug("开始渲染说明文本",
zap.Int("text_length", len(explanationText)),
zap.Int("line_count", len(strings.Split(explanationText, "\n"))),
)
pdf.Ln(5)
pb.fontManager.SetFont(pdf, "", 11)
_, lineHt = pdf.GetFontSize()
// 处理说明文本,按行分割并显示
lines := strings.Split(explanationText, "\n")
renderedLines := 0
for i, line := range lines {
// 保留原始行用于日志
originalLine := line
line = strings.TrimSpace(line)
// 处理空行
if line == "" {
pdf.Ln(3)
continue
}
// 清理文本(保留中文字符和标点)
cleanLine := pb.textProcessor.CleanText(line)
// 检查清理后的文本是否为空
if strings.TrimSpace(cleanLine) == "" {
pb.logger.Warn("文本行清理后为空,跳过渲染",
zap.Int("line_number", i+1),
zap.String("original_line", originalLine),
)
continue
}
// 渲染文本行
// 使用MultiCell自动换行支持长文本
pdf.MultiCell(0, lineHt*1.4, cleanLine, "", "L", false)
renderedLines++
}
pb.logger.Info("说明文本渲染完成",
zap.Int("total_lines", len(lines)),
zap.Int("rendered_lines", renderedLines),
)
} else {
pb.logger.Warn("说明文本为空,跳过渲染")
}
// 添加二维码生成方法和使用方法说明
pb.addQRCodeSection(pdf, doc, chineseFontAvailable)
}
// readExplanationText 读取说明文本文件
func (pb *PageBuilder) readExplanationText() string {
resourcesPDFDir := GetResourcesPDFDir()
textFilePath := filepath.Join(resourcesPDFDir, "后勤服务.txt")
// 检查文件是否存在
if _, err := os.Stat(textFilePath); os.IsNotExist(err) {
pb.logger.Warn("说明文本文件不存在", zap.String("path", textFilePath))
return ""
}
// 尝试读取文件使用os.ReadFile替代已废弃的ioutil.ReadFile
content, err := os.ReadFile(textFilePath)
if err != nil {
pb.logger.Error("读取说明文本文件失败",
zap.String("path", textFilePath),
zap.Error(err),
)
return ""
}
// 转换为字符串
text := string(content)
// 记录读取成功的信息
pb.logger.Info("成功读取说明文本文件",
zap.String("path", textFilePath),
zap.Int("file_size", len(content)),
zap.Int("text_length", len(text)),
zap.Int("line_count", len(strings.Split(text, "\n"))),
)
// 返回文本内容
return text
}
// addQRCodeSection 添加二维码生成方法和使用方法说明
func (pb *PageBuilder) addQRCodeSection(pdf *gofpdf.Fpdf, doc *entities.ProductDocumentation, chineseFontAvailable bool) {
_, pageHeight := pdf.GetPageSize()
_, _, _, bottomMargin := pdf.GetMargins()
currentY := pdf.GetY()
// 检查是否需要换页(为二维码预留空间)
if pageHeight-currentY-bottomMargin < 120 {
pdf.AddPage()
pb.addHeader(pdf, chineseFontAvailable)
pb.addWatermark(pdf, chineseFontAvailable)
pdf.SetY(45)
}
// 添加二维码标题
pdf.Ln(15)
pdf.SetTextColor(0, 0, 0)
pb.fontManager.SetFont(pdf, "B", 16)
_, lineHt := pdf.GetFontSize()
pdf.CellFormat(0, lineHt, "天远api官网二维码", "", 1, "L", false, 0, "")
// 先生成并添加二维码图片(确保二维码能够正常显示)
pb.addQRCodeImage(pdf, "https://tianyuanapi.com/", chineseFontAvailable)
// 二维码说明文字(简化版,放在二维码之后)
pdf.Ln(10)
pb.fontManager.SetFont(pdf, "", 11)
_, lineHt = pdf.GetFontSize()
qrCodeExplanation := "使用手机扫描上方二维码可直接跳转到天远API官网https://tianyuanapi.com/),获取更多接口文档和资源。\n\n" +
"二维码使用方法:\n" +
"1. 使用手机相机或二维码扫描应用扫描二维码\n" +
"2. 扫描后会自动跳转到天远API官网首页\n" +
"3. 在官网可以查看完整的产品列表、接口文档和使用说明"
// 处理说明文本,按行分割并显示
lines := strings.Split(qrCodeExplanation, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
pdf.Ln(2)
continue
}
// 普通文本行
cleanLine := pb.textProcessor.CleanText(line)
if strings.TrimSpace(cleanLine) != "" {
pdf.MultiCell(0, lineHt*1.3, cleanLine, "", "L", false)
}
}
}
// addQRCodeImage 生成并添加二维码图片到PDF
func (pb *PageBuilder) addQRCodeImage(pdf *gofpdf.Fpdf, content string, chineseFontAvailable bool) {
// 检查是否需要换页
pageWidth, pageHeight := pdf.GetPageSize()
_, _, _, bottomMargin := pdf.GetMargins()
currentY := pdf.GetY()
// 二维码大小40mm
qrSize := 40.0
if pageHeight-currentY-bottomMargin < qrSize+20 {
pdf.AddPage()
pb.addHeader(pdf, chineseFontAvailable)
pb.addWatermark(pdf, chineseFontAvailable)
pdf.SetY(45)
}
// 生成二维码
qr, err := qrcode.New(content, qrcode.Medium)
if err != nil {
pb.logger.Warn("生成二维码失败", zap.Error(err))
return
}
// 将二维码转换为PNG字节
qrBytes, err := qr.PNG(256)
if err != nil {
pb.logger.Warn("转换二维码为PNG失败", zap.Error(err))
return
}
// 创建临时文件保存二维码使用os.CreateTemp替代已废弃的ioutil.TempFile
tmpFile, err := os.CreateTemp("", "qrcode_*.png")
if err != nil {
pb.logger.Warn("创建临时文件失败", zap.Error(err))
return
}
defer os.Remove(tmpFile.Name()) // 清理临时文件
// 写入二维码数据
if _, err := tmpFile.Write(qrBytes); err != nil {
pb.logger.Warn("写入二维码数据失败", zap.Error(err))
tmpFile.Close()
return
}
tmpFile.Close()
// 添加二维码说明
pdf.Ln(10)
pb.fontManager.SetFont(pdf, "", 10)
_, lineHt := pdf.GetFontSize()
pdf.CellFormat(0, lineHt, "官网二维码:", "", 1, "L", false, 0, "")
// 计算二维码位置(居中)
qrX := (pageWidth - qrSize) / 2
// 添加二维码图片
pdf.Ln(5)
pdf.ImageOptions(tmpFile.Name(), qrX, pdf.GetY(), qrSize, qrSize, false, gofpdf.ImageOptions{}, 0, "")
// 添加二维码下方的说明文字
pdf.SetY(pdf.GetY() + qrSize + 5)
pb.fontManager.SetFont(pdf, "", 9)
_, lineHt = pdf.GetFontSize()
qrNote := "使用手机扫描上方二维码可访问官网获取更多详情"
noteWidth := pdf.GetStringWidth(qrNote)
noteX := (pageWidth - noteWidth) / 2
pdf.SetX(noteX)
pdf.CellFormat(noteWidth, lineHt, qrNote, "", 1, "C", false, 0, "")
}

View File

@@ -0,0 +1,27 @@
天远数据安全测试接入流程说明
若您希望接入天远数据的安全测试服务,可按照以下详细流程进行操作:
1. 联系商务了解接入流程
请您首先与天远数据的商务团队取得联系,深入了解安全测试接入的具体流程、要求以及相关注意事项。您可以通过以下方式联系我们的商务人员:
商务邮箱jiaowuzhe@aitoolpath.com
商务联系电话13876051080 微信同号
获得更多详情请访问 [https://www.tianyuanapi.com/]
2. 提供正式生产环境公网 IP
在与商务团队沟通并了解清楚接入流程后,请您将正式生产环境的公网 IP 提供给天远数据。我们将依据您提供的公网 IP 进行 IP 访问设置,以确保后续接口调用的顺利进行。
3. 构造并加密请求报文
您需要构造 JSON 明文请求报文,然后使用 AES-128 算法基于账户获得的16进制字符串密钥/Access Key对该明文请求报文进行加密处理。加密时采用AES-CBC模式密钥长度128位/16字节填充方式PKCS7每次加密随机生成16字节128位的IV初始化向量将IV与加密后的密文拼接在一起最后通过Base64编码形成可传输的字符串并将该Base64字符串放入请求体的data字段传参。此步骤中涉及的代码部分您可参考我们提供的demo包里面有详细的示例和说明能帮助您顺利完成报文的构造、加密及Base64编码操作。
4. 调用接口获取返回结果
完成请求报文的构造、加密及Base64编码后您可以使用处理好的报文即包含Base64编码数据的数据体调用天远数据的接口。调用接口后您将获得相应的返回结果该返回结果为经过Base64编码且拼接了IV的密文数据
5. 解密获得明文结果
当您获得接口返回的结果后需要先对Base64解码后的数据提取前16字节作为IV再使用该IV通过AES-CBC模式解密剩余密文最后去除PKCS7填充得到原始明文。同样关于Base64解码及AES解密含IV提取、填充去除的代码实现您可参考test包中的相关内容以顺利完成返回结果的解密操作。
若您在接入过程中有任何疑问或需要进一步的帮助,请随时与我们联系。您可以通过上述的商务邮箱和商务联系电话与我们的团队沟通,我们将竭诚为您服务。