This commit is contained in:
1
go.mod
1
go.mod
@@ -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
2
go.sum
@@ -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=
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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": "查询原因ID:1-授信审批;2-贷中管理;3-贷后管理;4-异议处理;5-担保查询;6-租赁资质审查;7-融资租赁审批;8-借贷撮合查询;9-保险审批;10-资质审核;11-风控审核;12-企业背调",
|
||||||
}
|
}
|
||||||
|
|
||||||
if desc, exists := descMap[jsonTag]; exists {
|
if desc, exists := descMap[jsonTag]; exists {
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package ivyz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/services/processors"
|
||||||
|
"tyapi-server/internal/infrastructure/external/westdex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessIVYZ2B2TRequest IVYZ2B2T API处理方法 能力资质核验(学历)
|
||||||
|
func ProcessIVYZ2B2TRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||||
|
|
||||||
|
var paramsDto dto.IVYZ2B2TReq
|
||||||
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
encryptedQueryReasonId, err := deps.WestDexService.Encrypt(strconv.FormatInt(paramsDto.QueryReasonId, 10))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"idCard": encryptedIDCard,
|
||||||
|
"name": encryptedName,
|
||||||
|
"queryReasonId": encryptedQueryReasonId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.WestDexService.CallAPI(ctx, "G11JX01", reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, westdex.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,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, ¶msDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
encryptedAuthAuthorizeFileCode, err := deps.WestDexService.Encrypt(paramsDto.AuthAuthorizeFileCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"idcard": encryptedIDCard,
|
||||||
|
"name": encryptedName,
|
||||||
|
"auth_authorizeFileCode": encryptedAuthAuthorizeFileCode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := deps.WestDexService.CallAPI(ctx, "G01SC01", reqData)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, westdex.ErrDatasource) {
|
||||||
|
return nil, errors.Join(processors.ErrDatasource, err)
|
||||||
|
} else {
|
||||||
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,38 +89,87 @@ 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 {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr.created_at >= ?", startTime)
|
||||||
|
} else {
|
||||||
query = query.Where("created_at >= ?", startTime)
|
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 {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr.created_at <= ?", endTime)
|
||||||
|
} else {
|
||||||
query = query.Where("created_at <= ?", endTime)
|
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 {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr.amount >= ?", amount)
|
||||||
|
} else {
|
||||||
query = query.Where("amount >= ?", amount)
|
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 {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr.amount <= ?", amount)
|
||||||
|
} else {
|
||||||
query = query.Where("amount <= ?", amount)
|
query = query.Where("amount <= ?", amount)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 其他过滤器使用等值查询
|
// 其他过滤器使用等值查询
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr."+key+" = ?", value)
|
||||||
|
} else {
|
||||||
query = query.Where(key+" = ?", value)
|
query = query.Where(key+" = ?", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if options.Search != "" {
|
if options.Search != "" {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
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 ?",
|
query = query.Where("user_id LIKE ? OR transfer_order_id LIKE ? OR alipay_order_id LIKE ?",
|
||||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return count, query.Count(&count).Error
|
return count, query.Count(&count).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,56 +181,117 @@ 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 != "" {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr.user_id IN ?", strings.Split(userIds, ","))
|
||||||
|
} else {
|
||||||
query = query.Where("user_id IN ?", strings.Split(userIds, ","))
|
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 {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr.created_at >= ?", startTime)
|
||||||
|
} else {
|
||||||
query = query.Where("created_at >= ?", startTime)
|
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 {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr.created_at <= ?", endTime)
|
||||||
|
} else {
|
||||||
query = query.Where("created_at <= ?", endTime)
|
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 {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr.amount >= ?", amount)
|
||||||
|
} else {
|
||||||
query = query.Where("amount >= ?", amount)
|
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 {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr.amount <= ?", amount)
|
||||||
|
} else {
|
||||||
query = query.Where("amount <= ?", amount)
|
query = query.Where("amount <= ?", amount)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 其他过滤器使用等值查询
|
// 其他过滤器使用等值查询
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Where("rr."+key+" = ?", value)
|
||||||
|
} else {
|
||||||
query = query.Where(key+" = ?", value)
|
query = query.Where(key+" = ?", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if options.Search != "" {
|
if options.Search != "" {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
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 ?",
|
query = query.Where("user_id LIKE ? OR transfer_order_id LIKE ? OR alipay_order_id LIKE ?",
|
||||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if options.Sort != "" {
|
if options.Sort != "" {
|
||||||
order := "ASC"
|
order := "ASC"
|
||||||
if options.Order == "desc" || options.Order == "DESC" {
|
if options.Order == "desc" || options.Order == "DESC" {
|
||||||
order = "DESC"
|
order = "DESC"
|
||||||
}
|
}
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Order("rr." + options.Sort + " " + order)
|
||||||
|
} else {
|
||||||
query = query.Order(options.Sort + " " + order)
|
query = query.Order(options.Sort + " " + order)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if hasCompanyNameFilter {
|
||||||
|
query = query.Order("rr.created_at DESC")
|
||||||
} else {
|
} else {
|
||||||
query = query.Order("created_at DESC")
|
query = query.Order("created_at DESC")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if options.Page > 0 && options.PageSize > 0 {
|
if options.Page > 0 && options.PageSize > 0 {
|
||||||
offset := (options.Page - 1) * options.PageSize
|
offset := (options.Page - 1) * options.PageSize
|
||||||
|
|||||||
@@ -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 >= ?"
|
||||||
|
|||||||
@@ -133,8 +133,7 @@ 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" {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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, "")
|
||||||
|
}
|
||||||
|
|||||||
27
resources/pdf/后勤服务.txt
Normal file
27
resources/pdf/后勤服务.txt
Normal 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包中的相关内容,以顺利完成返回结果的解密操作。
|
||||||
|
|
||||||
|
|
||||||
|
若您在接入过程中有任何疑问或需要进一步的帮助,请随时与我们联系。您可以通过上述的商务邮箱和商务联系电话与我们的团队沟通,我们将竭诚为您服务。
|
||||||
|
|
||||||
Reference in New Issue
Block a user