This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
appQueries "tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
domain_api_repo "tyapi-server/internal/domains/api/repositories"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
type SubscriptionApplicationServiceImpl struct {
|
||||
productSubscriptionService *product_service.ProductSubscriptionService
|
||||
userRepo user_repositories.UserRepository
|
||||
apiCallRepository domain_api_repo.ApiCallRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
@@ -28,11 +30,13 @@ type SubscriptionApplicationServiceImpl struct {
|
||||
func NewSubscriptionApplicationService(
|
||||
productSubscriptionService *product_service.ProductSubscriptionService,
|
||||
userRepo user_repositories.UserRepository,
|
||||
apiCallRepository domain_api_repo.ApiCallRepository,
|
||||
logger *zap.Logger,
|
||||
) SubscriptionApplicationService {
|
||||
return &SubscriptionApplicationServiceImpl{
|
||||
productSubscriptionService: productSubscriptionService,
|
||||
userRepo: userRepo,
|
||||
apiCallRepository: apiCallRepository,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -262,17 +266,30 @@ func (s *SubscriptionApplicationServiceImpl) GetProductSubscriptions(ctx context
|
||||
}
|
||||
|
||||
// GetSubscriptionUsage 获取订阅使用情况
|
||||
// 业务流程:1. 获取订阅使用情况 2. 构建响应数据
|
||||
// 业务流程:1. 获取订阅信息 2. 根据产品ID和用户ID统计API调用次数 3. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) {
|
||||
// 获取订阅信息
|
||||
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 根据用户ID和产品ID统计API调用次数
|
||||
apiCallCount, err := s.apiCallRepository.CountByUserIdAndProductId(ctx, subscription.UserID, subscription.ProductID)
|
||||
if err != nil {
|
||||
s.logger.Warn("统计API调用次数失败,使用订阅记录中的值",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
zap.String("user_id", subscription.UserID),
|
||||
zap.String("product_id", subscription.ProductID),
|
||||
zap.Error(err))
|
||||
// 如果统计失败,使用订阅实体中的APIUsed字段作为备选
|
||||
apiCallCount = subscription.APIUsed
|
||||
}
|
||||
|
||||
return &responses.SubscriptionUsageResponse{
|
||||
ID: subscription.ID,
|
||||
ProductID: subscription.ProductID,
|
||||
APIUsed: subscription.APIUsed,
|
||||
APIUsed: apiCallCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -195,6 +195,18 @@ type IVYZGZ08Req struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type IVYZ2B2TReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
QueryReasonId int64 `json:"query_reason_id" validate:"required"`
|
||||
}
|
||||
|
||||
type 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 {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
|
||||
@@ -25,6 +25,9 @@ type ApiCallRepository interface {
|
||||
// 新增:统计用户API调用次数
|
||||
CountByUserId(ctx context.Context, userId string) (int64, error)
|
||||
|
||||
// 新增:根据用户ID和产品ID统计API调用次数
|
||||
CountByUserIdAndProductId(ctx context.Context, userId string, productId string) (int64, error)
|
||||
|
||||
// 新增:根据TransactionID查询
|
||||
FindByTransactionId(ctx context.Context, transactionId string) (*entities.ApiCall, error)
|
||||
|
||||
|
||||
@@ -202,6 +202,8 @@ func registerAllProcessors(combService *comb.CombService) {
|
||||
"IVYZ9K2L": ivyz.ProcessIVYZ9K2LRequest,
|
||||
"IVYZ2C1P": ivyz.ProcessIVYZ2C1PRequest,
|
||||
"IVYZP2Q6": ivyz.ProcessIVYZP2Q6Request,
|
||||
"IVYZ2B2T": ivyz.ProcessIVYZ2B2TRequest, //能力资质核验(学历)
|
||||
"IVYZ5A9O": ivyz.ProcessIVYZ5A9ORequest, //全国⾃然⼈⻛险评估评分模型
|
||||
|
||||
// COMB系列处理器 - 只注册有自定义逻辑的组合包
|
||||
"COMB86PM": comb.ProcessCOMB86PMRequest, // 有自定义逻辑:重命名ApiCode
|
||||
|
||||
@@ -195,6 +195,8 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
|
||||
"QYGL5A9T": &dto.QYGL5A9TReq{}, //全国企业各类工商风险统计数量查询
|
||||
"JRZQ3P01": &dto.JRZQ3P01Req{}, //天远风控决策
|
||||
"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=")
|
||||
frontendRules = append(frontendRules, "可选值: "+values)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return strings.Join(frontendRules, "、")
|
||||
@@ -394,6 +397,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string {
|
||||
"photo_data": "人脸图片",
|
||||
"owner_type": "企业主类型",
|
||||
"type": "查询类型",
|
||||
"query_reason_id": "查询原因ID",
|
||||
}
|
||||
|
||||
if label, exists := labelMap[jsonTag]; exists {
|
||||
@@ -438,6 +442,7 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso
|
||||
"photo_data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"ownerType": "1",
|
||||
"type": "per",
|
||||
"query_reason_id": "1",
|
||||
}
|
||||
|
||||
if example, exists := exampleMap[jsonTag]; exists {
|
||||
@@ -491,6 +496,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st
|
||||
"photo_data": "请输入base64编码的人脸图片(支持JPG、BMP、PNG格式)",
|
||||
"ownerType": "请选择企业主类型",
|
||||
"type": "请选择查询类型",
|
||||
"query_reason_id": "请选择查询原因ID",
|
||||
}
|
||||
|
||||
if placeholder, exists := placeholderMap[jsonTag]; exists {
|
||||
@@ -546,6 +552,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s
|
||||
"photo_data": "人脸图片(必填):base64编码的图片数据,仅支持JPG、BMP、PNG三种格式",
|
||||
"owner_type": "企业主类型编码:1-法定代表人;2-主要人员;3-自然人股东;4-法定代表人及自然人股东;5-其他",
|
||||
"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 {
|
||||
|
||||
@@ -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"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/zhicha"
|
||||
|
||||
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
"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)
|
||||
}
|
||||
|
||||
// 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调用次数
|
||||
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)
|
||||
@@ -304,8 +310,29 @@ func (r *GormApiCallRepository) ListWithFiltersAndProductName(ctx context.Contex
|
||||
|
||||
// 应用筛选条件
|
||||
if filters != nil {
|
||||
// 用户ID筛选
|
||||
if userId, ok := filters["user_id"].(string); ok && userId != "" {
|
||||
// 用户ID筛选(支持单个user_id和多个user_ids)
|
||||
// 如果同时存在,优先使用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 = ?"
|
||||
whereArgs = append(whereArgs, userId)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
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 {
|
||||
for key, value := range options.Filters {
|
||||
// 特殊处理时间范围过滤器
|
||||
if key == "start_time" {
|
||||
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" {
|
||||
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" {
|
||||
// 处理最小金额,支持string、int、int64类型
|
||||
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" {
|
||||
// 处理最大金额,支持string、int、int64类型
|
||||
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 {
|
||||
// 其他过滤器使用等值查询
|
||||
query = query.Where(key+" = ?", value)
|
||||
if hasCompanyNameFilter {
|
||||
query = query.Where("rr."+key+" = ?", value)
|
||||
} else {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if options.Search != "" {
|
||||
query = query.Where("user_id LIKE ? OR transfer_order_id LIKE ? OR alipay_order_id LIKE ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+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 ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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 {
|
||||
for key, value := range options.Filters {
|
||||
// 特殊处理 user_ids 过滤器
|
||||
if key == "user_ids" {
|
||||
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" {
|
||||
// 处理开始时间范围
|
||||
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" {
|
||||
// 处理结束时间范围
|
||||
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" {
|
||||
// 处理最小金额,支持string、int、int64类型
|
||||
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" {
|
||||
// 处理最大金额,支持string、int、int64类型
|
||||
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 {
|
||||
// 其他过滤器使用等值查询
|
||||
query = query.Where(key+" = ?", value)
|
||||
if hasCompanyNameFilter {
|
||||
query = query.Where("rr."+key+" = ?", value)
|
||||
} else {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("user_id LIKE ? OR transfer_order_id LIKE ? OR alipay_order_id LIKE ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+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 ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
}
|
||||
|
||||
if options.Sort != "" {
|
||||
@@ -178,9 +280,17 @@ func (r *GormRechargeRecordRepository) List(ctx context.Context, options interfa
|
||||
if options.Order == "desc" || options.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 {
|
||||
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 {
|
||||
|
||||
@@ -382,12 +382,26 @@ func (r *GormWalletTransactionRepository) ListWithFiltersAndProductName(ctx cont
|
||||
|
||||
// 应用筛选条件
|
||||
if filters != nil {
|
||||
// 用户ID筛选
|
||||
if userId, ok := filters["user_id"].(string); ok && userId != "" {
|
||||
// 用户ID筛选(支持单个和多个)
|
||||
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 = ?"
|
||||
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 {
|
||||
whereCondition += " AND wt.created_at >= ?"
|
||||
|
||||
@@ -34,7 +34,7 @@ type ZhichaResp struct {
|
||||
type ZhichaConfig struct {
|
||||
URL string
|
||||
AppID string
|
||||
AppSecret string
|
||||
AppSecret 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() {
|
||||
// 检查是否是网络超时错误
|
||||
isTimeout = true
|
||||
} else if errStr := err.Error();
|
||||
errStr == "context deadline exceeded" ||
|
||||
errStr == "timeout" ||
|
||||
errStr == "Client.Timeout exceeded" ||
|
||||
errStr == "net/http: request canceled" {
|
||||
} else if errStr := err.Error(); errStr == "context deadline exceeded" ||
|
||||
errStr == "timeout" ||
|
||||
errStr == "Client.Timeout exceeded" ||
|
||||
errStr == "net/http: request canceled" {
|
||||
isTimeout = true
|
||||
}
|
||||
|
||||
|
||||
if isTimeout {
|
||||
// 超时错误应该返回数据源异常,而不是系统异常
|
||||
err = errors.Join(ErrDatasource, fmt.Errorf("API请求超时: %v", err))
|
||||
|
||||
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"tyapi-server/internal/application/api"
|
||||
"tyapi-server/internal/application/finance"
|
||||
@@ -1237,13 +1238,27 @@ func (h *ProductAdminHandler) ExportAdminWalletTransactions(c *gin.Context) {
|
||||
|
||||
// 时间范围筛选
|
||||
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 {
|
||||
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 != "" {
|
||||
// 处理URL编码的+号,转换为空格
|
||||
endTime = strings.ReplaceAll(endTime, "+", " ")
|
||||
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 企业名称筛选
|
||||
if companyName := c.Query("company_name"); companyName != "" {
|
||||
filters["company_name"] = companyName
|
||||
}
|
||||
|
||||
// 金额范围筛选
|
||||
if minAmount := c.Query("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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1364,6 +1393,11 @@ func (h *ProductAdminHandler) GetAdminRechargeRecords(c *gin.Context) {
|
||||
filters["max_amount"] = maxAmount
|
||||
}
|
||||
|
||||
// 企业名称筛选
|
||||
if companyName := c.Query("company_name"); companyName != "" {
|
||||
filters["company_name"] = companyName
|
||||
}
|
||||
|
||||
// 构建分页选项
|
||||
options := interfaces.ListOptions{
|
||||
Page: page,
|
||||
@@ -1442,7 +1476,16 @@ func (h *ProductAdminHandler) ExportAdminRechargeRecords(c *gin.Context) {
|
||||
fileData, err := h.financeAppService.ExportAdminRechargeRecords(c.Request.Context(), filters, format)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1468,7 +1511,10 @@ func (h *ProductAdminHandler) GetAdminApiCalls(c *gin.Context) {
|
||||
// 构建筛选条件
|
||||
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 != "" {
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,13 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
|
||||
"github.com/jung-kurt/gofpdf/v2"
|
||||
qrcode "github.com/skip2/go-qrcode"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -298,6 +300,9 @@ func (pb *PageBuilder) AddDocumentationPages(pdf *gofpdf.Fpdf, doc *entities.Pro
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加说明文字和二维码
|
||||
pb.addAdditionalInfo(pdf, doc, chineseFontAvailable)
|
||||
}
|
||||
|
||||
// addSection 添加章节
|
||||
@@ -829,3 +834,248 @@ func (pb *PageBuilder) safeSplitText(pdf *gofpdf.Fpdf, text string, width float6
|
||||
|
||||
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, "")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user