2025-07-28 01:46:39 +08:00
|
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
2025-08-18 14:13:16 +08:00
|
|
|
|
"encoding/json"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"errors"
|
2025-09-12 01:15:09 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"time"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"tyapi-server/internal/application/api/commands"
|
|
|
|
|
|
"tyapi-server/internal/application/api/dto"
|
2025-07-28 23:44:01 +08:00
|
|
|
|
"tyapi-server/internal/application/api/utils"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"tyapi-server/internal/config"
|
|
|
|
|
|
entities "tyapi-server/internal/domains/api/entities"
|
2025-09-12 01:15:09 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"tyapi-server/internal/domains/api/repositories"
|
|
|
|
|
|
"tyapi-server/internal/domains/api/services"
|
|
|
|
|
|
"tyapi-server/internal/domains/api/services/processors"
|
|
|
|
|
|
finance_services "tyapi-server/internal/domains/finance/services"
|
2025-09-12 01:15:09 +08:00
|
|
|
|
product_entities "tyapi-server/internal/domains/product/entities"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
product_services "tyapi-server/internal/domains/product/services"
|
2025-08-02 02:54:21 +08:00
|
|
|
|
user_repositories "tyapi-server/internal/domains/user/repositories"
|
2025-09-12 01:15:09 +08:00
|
|
|
|
task_entities "tyapi-server/internal/infrastructure/task/entities"
|
|
|
|
|
|
"tyapi-server/internal/infrastructure/task/interfaces"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"tyapi-server/internal/shared/crypto"
|
|
|
|
|
|
"tyapi-server/internal/shared/database"
|
2025-09-12 01:15:09 +08:00
|
|
|
|
"tyapi-server/internal/shared/export"
|
|
|
|
|
|
shared_interfaces "tyapi-server/internal/shared/interfaces"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
"github.com/shopspring/decimal"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type ApiApplicationService interface {
|
|
|
|
|
|
CallApi(ctx context.Context, cmd *commands.ApiCallCommand) (string, string, error)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户API密钥
|
|
|
|
|
|
GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error)
|
|
|
|
|
|
|
|
|
|
|
|
// 用户白名单管理
|
|
|
|
|
|
GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error)
|
|
|
|
|
|
AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
|
|
|
|
|
DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户API调用记录
|
2025-09-12 01:15:09 +08:00
|
|
|
|
GetUserApiCalls(ctx context.Context, userID string, filters map[string]interface{}, options shared_interfaces.ListOptions) (*dto.ApiCallListResponse, error)
|
2025-08-04 22:02:09 +08:00
|
|
|
|
|
2025-08-02 02:54:21 +08:00
|
|
|
|
// 管理端API调用记录
|
2025-09-12 01:15:09 +08:00
|
|
|
|
GetAdminApiCalls(ctx context.Context, filters map[string]interface{}, options shared_interfaces.ListOptions) (*dto.ApiCallListResponse, error)
|
|
|
|
|
|
|
|
|
|
|
|
// 导出功能
|
|
|
|
|
|
ExportAdminApiCalls(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error)
|
2025-08-18 14:13:16 +08:00
|
|
|
|
|
|
|
|
|
|
// 加密参数接口
|
|
|
|
|
|
EncryptParams(ctx context.Context, userID string, cmd *commands.EncryptCommand) (string, error)
|
2025-08-27 22:19:19 +08:00
|
|
|
|
|
2025-08-18 14:13:16 +08:00
|
|
|
|
// 解密参数接口
|
|
|
|
|
|
DecryptParams(ctx context.Context, userID string, cmd *commands.DecryptCommand) (map[string]interface{}, error)
|
2025-08-27 22:19:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取表单配置
|
|
|
|
|
|
GetFormConfig(ctx context.Context, apiCode string) (*dto.FormConfigResponse, error)
|
2025-09-12 01:15:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 异步任务处理接口
|
|
|
|
|
|
SaveApiCall(ctx context.Context, cmd *commands.SaveApiCallCommand) error
|
|
|
|
|
|
ProcessDeduction(ctx context.Context, cmd *commands.ProcessDeductionCommand) error
|
|
|
|
|
|
UpdateUsageStats(ctx context.Context, cmd *commands.UpdateUsageStatsCommand) error
|
|
|
|
|
|
RecordApiLog(ctx context.Context, cmd *commands.RecordApiLogCommand) error
|
|
|
|
|
|
ProcessCompensation(ctx context.Context, cmd *commands.ProcessCompensationCommand) error
|
|
|
|
|
|
|
|
|
|
|
|
// 余额预警设置
|
|
|
|
|
|
GetUserBalanceAlertSettings(ctx context.Context, userID string) (map[string]interface{}, error)
|
|
|
|
|
|
UpdateUserBalanceAlertSettings(ctx context.Context, userID string, enabled bool, threshold float64, alertPhone string) error
|
|
|
|
|
|
TestBalanceAlertSms(ctx context.Context, userID string, phone string, balance float64, alertType string) error
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type ApiApplicationServiceImpl struct {
|
2025-09-12 01:15:09 +08:00
|
|
|
|
apiCallService services.ApiCallAggregateService
|
|
|
|
|
|
apiUserService services.ApiUserAggregateService
|
|
|
|
|
|
apiRequestService *services.ApiRequestService
|
|
|
|
|
|
formConfigService services.FormConfigService
|
|
|
|
|
|
apiCallRepository repositories.ApiCallRepository
|
|
|
|
|
|
contractInfoService user_repositories.ContractInfoRepository
|
|
|
|
|
|
productManagementService *product_services.ProductManagementService
|
|
|
|
|
|
userRepo user_repositories.UserRepository
|
|
|
|
|
|
txManager *database.TransactionManager
|
|
|
|
|
|
config *config.Config
|
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
taskManager interfaces.TaskManager
|
|
|
|
|
|
exportManager *export.ExportManager
|
|
|
|
|
|
|
|
|
|
|
|
// 其他域的服务
|
|
|
|
|
|
walletService finance_services.WalletAggregateService
|
|
|
|
|
|
subscriptionService *product_services.ProductSubscriptionService
|
|
|
|
|
|
balanceAlertService finance_services.BalanceAlertService
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
func NewApiApplicationService(
|
|
|
|
|
|
apiCallService services.ApiCallAggregateService,
|
|
|
|
|
|
apiUserService services.ApiUserAggregateService,
|
|
|
|
|
|
apiRequestService *services.ApiRequestService,
|
|
|
|
|
|
formConfigService services.FormConfigService,
|
|
|
|
|
|
apiCallRepository repositories.ApiCallRepository,
|
|
|
|
|
|
productManagementService *product_services.ProductManagementService,
|
|
|
|
|
|
userRepo user_repositories.UserRepository,
|
|
|
|
|
|
txManager *database.TransactionManager,
|
|
|
|
|
|
config *config.Config,
|
|
|
|
|
|
logger *zap.Logger,
|
|
|
|
|
|
contractInfoService user_repositories.ContractInfoRepository,
|
|
|
|
|
|
taskManager interfaces.TaskManager,
|
|
|
|
|
|
walletService finance_services.WalletAggregateService,
|
|
|
|
|
|
subscriptionService *product_services.ProductSubscriptionService,
|
|
|
|
|
|
exportManager *export.ExportManager,
|
|
|
|
|
|
balanceAlertService finance_services.BalanceAlertService,
|
|
|
|
|
|
) ApiApplicationService {
|
|
|
|
|
|
service := &ApiApplicationServiceImpl{
|
|
|
|
|
|
apiCallService: apiCallService,
|
|
|
|
|
|
apiUserService: apiUserService,
|
|
|
|
|
|
apiRequestService: apiRequestService,
|
|
|
|
|
|
formConfigService: formConfigService,
|
|
|
|
|
|
apiCallRepository: apiCallRepository,
|
|
|
|
|
|
productManagementService: productManagementService,
|
|
|
|
|
|
userRepo: userRepo,
|
|
|
|
|
|
txManager: txManager,
|
|
|
|
|
|
config: config,
|
|
|
|
|
|
logger: logger,
|
|
|
|
|
|
contractInfoService: contractInfoService,
|
|
|
|
|
|
taskManager: taskManager,
|
|
|
|
|
|
exportManager: exportManager,
|
|
|
|
|
|
walletService: walletService,
|
|
|
|
|
|
subscriptionService: subscriptionService,
|
|
|
|
|
|
balanceAlertService: balanceAlertService,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return service
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// CallApi 优化后的应用服务层统一入口
|
2025-07-28 01:46:39 +08:00
|
|
|
|
func (s *ApiApplicationServiceImpl) CallApi(ctx context.Context, cmd *commands.ApiCallCommand) (string, string, error) {
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// ==================== 第一阶段:同步关键验证 ====================
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 创建ApiCall(内存中,不保存)
|
|
|
|
|
|
apiCall, err := entities.NewApiCall(cmd.AccessId, cmd.Data, cmd.ClientIP)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("创建ApiCall失败", zap.Error(err))
|
|
|
|
|
|
return "", "", ErrSystem
|
|
|
|
|
|
}
|
|
|
|
|
|
transactionId := apiCall.TransactionId
|
|
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// 2. 同步验证用户和产品(关键路径)
|
|
|
|
|
|
validationResult, err := s.validateApiCall(ctx, cmd, apiCall)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 异步记录失败状态
|
|
|
|
|
|
go s.asyncRecordFailure(context.Background(), apiCall, err)
|
|
|
|
|
|
return "", "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 同步调用外部API(核心业务)
|
|
|
|
|
|
response, err := s.callExternalApi(ctx, cmd, validationResult)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if err != nil {
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// 异步记录失败状态
|
|
|
|
|
|
go s.asyncRecordFailure(context.Background(), apiCall, err)
|
|
|
|
|
|
return "", "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 同步加密响应
|
|
|
|
|
|
encryptedResponse, err := crypto.AesEncrypt([]byte(response), validationResult.GetSecretKey())
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("加密响应失败", zap.Error(err))
|
|
|
|
|
|
go s.asyncRecordFailure(context.Background(), apiCall, err)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return "", "", ErrSystem
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// ==================== 第二阶段:异步处理非关键操作 ====================
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// 5. 异步保存API调用记录
|
|
|
|
|
|
go s.asyncSaveApiCall(context.Background(), apiCall, validationResult, response)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// 6. 异步扣款处理
|
|
|
|
|
|
go s.asyncProcessDeduction(context.Background(), apiCall, validationResult)
|
2025-08-04 22:02:09 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// 7. 异步更新使用统计
|
|
|
|
|
|
// go s.asyncUpdateUsageStats(context.Background(), validationResult)
|
2025-08-27 22:19:19 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// ==================== 第三阶段:立即返回结果 ====================
|
2025-08-27 22:19:19 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
s.logger.Info("API调用成功,异步处理后续操作",
|
|
|
|
|
|
zap.String("transaction_id", transactionId),
|
|
|
|
|
|
zap.String("user_id", validationResult.GetUserID()),
|
|
|
|
|
|
zap.String("api_name", cmd.ApiName))
|
|
|
|
|
|
|
|
|
|
|
|
return transactionId, string(encryptedResponse), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// validateApiCall 同步验证用户和产品信息
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) validateApiCall(ctx context.Context, cmd *commands.ApiCallCommand, apiCall *entities.ApiCall) (*dto.ApiCallValidationResult, error) {
|
|
|
|
|
|
result := dto.NewApiCallValidationResult()
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 验证ApiUser
|
|
|
|
|
|
apiUser, err := s.apiUserService.LoadApiUserByAccessId(ctx, cmd.AccessId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查ApiUser失败", zap.Error(err))
|
|
|
|
|
|
return nil, ErrInvalidAccessId
|
|
|
|
|
|
}
|
|
|
|
|
|
result.SetApiUser(apiUser)
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 验证产品
|
|
|
|
|
|
product, err := s.productManagementService.GetProductByCode(ctx, cmd.ApiName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查产品失败", zap.Error(err))
|
|
|
|
|
|
return nil, ErrProductNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
result.SetProduct(product)
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 验证用户状态
|
|
|
|
|
|
if apiUser.IsFrozen() {
|
|
|
|
|
|
s.logger.Error("账户已冻结", zap.String("userId", apiUser.UserId))
|
|
|
|
|
|
return nil, ErrFrozenAccount
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 验证IP白名单(非开发环境)
|
|
|
|
|
|
if !s.config.App.IsDevelopment() && !cmd.Options.IsDebug {
|
|
|
|
|
|
if !apiUser.IsWhiteListed(cmd.ClientIP) {
|
|
|
|
|
|
s.logger.Error("IP不在白名单内", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP))
|
|
|
|
|
|
return nil, ErrInvalidIP
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
2025-09-12 01:15:09 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// 5. 验证钱包状态
|
|
|
|
|
|
if err := s.validateWalletStatus(ctx, apiUser.UserId, product); err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-14 16:34:55 +08:00
|
|
|
|
// 6. 验证订阅状态并获取订阅信息
|
|
|
|
|
|
subscription, err := s.validateSubscriptionStatus(ctx, apiUser.UserId, product)
|
|
|
|
|
|
if err != nil {
|
2025-09-12 01:15:09 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-09-14 16:34:55 +08:00
|
|
|
|
result.SetSubscription(subscription)
|
2025-09-12 01:15:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 7. 解密参数
|
|
|
|
|
|
requestParams, err := crypto.AesDecrypt(cmd.Data, apiUser.SecretKey)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("解密参数失败", zap.Error(err))
|
|
|
|
|
|
return nil, ErrDecryptFail
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将解密后的字节数组转换为map
|
|
|
|
|
|
var paramsMap map[string]interface{}
|
|
|
|
|
|
if err := json.Unmarshal(requestParams, ¶msMap); err != nil {
|
|
|
|
|
|
s.logger.Error("解析解密参数失败", zap.Error(err))
|
|
|
|
|
|
return nil, ErrDecryptFail
|
|
|
|
|
|
}
|
|
|
|
|
|
result.SetRequestParams(paramsMap)
|
|
|
|
|
|
|
|
|
|
|
|
// 8. 获取合同信息
|
|
|
|
|
|
contractInfo, err := s.contractInfoService.FindByUserID(ctx, apiUser.UserId)
|
|
|
|
|
|
if err == nil && len(contractInfo) > 0 {
|
|
|
|
|
|
result.SetContractCode(contractInfo[0].ContractCode)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新ApiCall信息
|
|
|
|
|
|
apiCall.ProductId = &product.ID
|
|
|
|
|
|
apiCall.UserId = &apiUser.UserId
|
|
|
|
|
|
result.SetApiCall(apiCall)
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// callExternalApi 同步调用外部API
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) callExternalApi(ctx context.Context, cmd *commands.ApiCallCommand, validation *dto.ApiCallValidationResult) (string, error) {
|
|
|
|
|
|
// 创建CallContext
|
|
|
|
|
|
callContext := &processors.CallContext{
|
|
|
|
|
|
ContractCode: validation.ContractCode,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将transactionId放入ctx中
|
|
|
|
|
|
ctxWithTransactionId := context.WithValue(ctx, "transaction_id", validation.ApiCall.TransactionId)
|
|
|
|
|
|
|
|
|
|
|
|
// 将map转换为字节数组
|
|
|
|
|
|
requestParamsBytes, err := json.Marshal(validation.RequestParams)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("序列化请求参数失败", zap.Error(err))
|
|
|
|
|
|
return "", ErrSystem
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用外部API
|
|
|
|
|
|
response, err := s.apiRequestService.PreprocessRequestApi(
|
|
|
|
|
|
ctxWithTransactionId,
|
|
|
|
|
|
cmd.ApiName,
|
|
|
|
|
|
requestParamsBytes,
|
|
|
|
|
|
&cmd.Options,
|
|
|
|
|
|
callContext)
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, processors.ErrDatasource) {
|
|
|
|
|
|
return "", ErrSystem
|
|
|
|
|
|
} else if errors.Is(err, processors.ErrInvalidParam) {
|
|
|
|
|
|
return "", ErrInvalidParam
|
|
|
|
|
|
} else if errors.Is(err, processors.ErrNotFound) {
|
|
|
|
|
|
return "", ErrQueryEmpty
|
2025-07-28 01:46:39 +08:00
|
|
|
|
} else {
|
2025-09-12 01:15:09 +08:00
|
|
|
|
return "", ErrSystem
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
return string(response), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// asyncSaveApiCall 异步保存API调用记录
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) asyncSaveApiCall(ctx context.Context, apiCall *entities.ApiCall, validation *dto.ApiCallValidationResult, response string) {
|
|
|
|
|
|
// 标记为成功
|
|
|
|
|
|
apiCall.MarkSuccess(validation.GetAmount())
|
|
|
|
|
|
|
|
|
|
|
|
// 检查TransactionID是否已存在,避免重复创建
|
|
|
|
|
|
existingCall, err := s.apiCallRepository.FindByTransactionId(ctx, apiCall.TransactionId)
|
|
|
|
|
|
if err == nil && existingCall != nil {
|
|
|
|
|
|
s.logger.Warn("API调用记录已存在,跳过创建",
|
|
|
|
|
|
zap.String("transaction_id", apiCall.TransactionId),
|
|
|
|
|
|
zap.String("user_id", validation.GetUserID()))
|
|
|
|
|
|
return // 静默返回,不报错
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接保存到数据库
|
|
|
|
|
|
if err := s.apiCallRepository.Create(ctx, apiCall); err != nil {
|
|
|
|
|
|
s.logger.Error("异步保存API调用记录失败", zap.Error(err))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建任务工厂
|
|
|
|
|
|
taskFactory := task_entities.NewTaskFactoryWithManager(s.taskManager)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建并异步入队API调用日志任务
|
|
|
|
|
|
if err := taskFactory.CreateAndEnqueueApiLogTask(
|
|
|
|
|
|
ctx,
|
|
|
|
|
|
apiCall.TransactionId,
|
|
|
|
|
|
validation.GetUserID(),
|
|
|
|
|
|
validation.Product.Code,
|
|
|
|
|
|
validation.Product.Code,
|
|
|
|
|
|
); err != nil {
|
|
|
|
|
|
s.logger.Error("创建并入队API日志任务失败", zap.Error(err))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// asyncProcessDeduction 异步扣款处理
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) asyncProcessDeduction(ctx context.Context, apiCall *entities.ApiCall, validation *dto.ApiCallValidationResult) {
|
|
|
|
|
|
// 创建任务工厂
|
|
|
|
|
|
taskFactory := task_entities.NewTaskFactoryWithManager(s.taskManager)
|
|
|
|
|
|
|
|
|
|
|
|
// 为扣款任务生成独立的TransactionID,避免与API调用的TransactionID冲突
|
|
|
|
|
|
deductionTransactionID := entities.GenerateTransactionID()
|
|
|
|
|
|
|
|
|
|
|
|
// 创建并异步入队扣款任务
|
|
|
|
|
|
if err := taskFactory.CreateAndEnqueueDeductionTask(
|
|
|
|
|
|
ctx,
|
|
|
|
|
|
apiCall.ID,
|
|
|
|
|
|
validation.GetUserID(),
|
|
|
|
|
|
validation.GetProductID(),
|
|
|
|
|
|
validation.GetAmount().String(),
|
|
|
|
|
|
deductionTransactionID, // 使用独立的TransactionID
|
|
|
|
|
|
); err != nil {
|
|
|
|
|
|
s.logger.Error("创建并入队扣款任务失败", zap.Error(err))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
2025-09-12 01:15:09 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// asyncUpdateUsageStats 异步更新使用统计
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) asyncUpdateUsageStats(ctx context.Context, validation *dto.ApiCallValidationResult) {
|
|
|
|
|
|
// 创建任务工厂
|
|
|
|
|
|
taskFactory := task_entities.NewTaskFactoryWithManager(s.taskManager)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建并异步入队使用统计任务
|
|
|
|
|
|
if err := taskFactory.CreateAndEnqueueUsageStatsTask(
|
|
|
|
|
|
ctx,
|
|
|
|
|
|
validation.GetSubscriptionID(),
|
|
|
|
|
|
validation.GetUserID(),
|
|
|
|
|
|
validation.GetProductID(),
|
|
|
|
|
|
1,
|
|
|
|
|
|
); err != nil {
|
|
|
|
|
|
s.logger.Error("创建并入队使用统计任务失败", zap.Error(err))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
2025-09-12 01:15:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// asyncRecordFailure 异步记录失败状态
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) asyncRecordFailure(ctx context.Context, apiCall *entities.ApiCall, err error) {
|
|
|
|
|
|
// 根据错误类型标记失败状态
|
|
|
|
|
|
var errorType string
|
|
|
|
|
|
var errorMsg string
|
|
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
|
case errors.Is(err, ErrInvalidAccessId):
|
|
|
|
|
|
errorType = entities.ApiCallErrorInvalidAccess
|
|
|
|
|
|
errorMsg = err.Error()
|
|
|
|
|
|
case errors.Is(err, ErrFrozenAccount):
|
|
|
|
|
|
errorType = entities.ApiCallErrorFrozenAccount
|
|
|
|
|
|
case errors.Is(err, ErrInvalidIP):
|
|
|
|
|
|
errorType = entities.ApiCallErrorInvalidIP
|
|
|
|
|
|
case errors.Is(err, ErrArrears):
|
|
|
|
|
|
errorType = entities.ApiCallErrorArrears
|
|
|
|
|
|
case errors.Is(err, ErrInsufficientBalance):
|
|
|
|
|
|
errorType = entities.ApiCallErrorArrears
|
|
|
|
|
|
case errors.Is(err, ErrProductNotFound):
|
|
|
|
|
|
errorType = entities.ApiCallErrorProductNotFound
|
|
|
|
|
|
errorMsg = err.Error()
|
|
|
|
|
|
case errors.Is(err, ErrProductDisabled):
|
|
|
|
|
|
errorType = entities.ApiCallErrorProductDisabled
|
|
|
|
|
|
case errors.Is(err, ErrNotSubscribed):
|
|
|
|
|
|
errorType = entities.ApiCallErrorNotSubscribed
|
|
|
|
|
|
case errors.Is(err, ErrProductNotSubscribed):
|
|
|
|
|
|
errorType = entities.ApiCallErrorNotSubscribed
|
|
|
|
|
|
case errors.Is(err, ErrSubscriptionExpired):
|
|
|
|
|
|
errorType = entities.ApiCallErrorNotSubscribed
|
|
|
|
|
|
case errors.Is(err, ErrSubscriptionSuspended):
|
|
|
|
|
|
errorType = entities.ApiCallErrorNotSubscribed
|
|
|
|
|
|
case errors.Is(err, ErrDecryptFail):
|
|
|
|
|
|
errorType = entities.ApiCallErrorDecryptFail
|
|
|
|
|
|
errorMsg = err.Error()
|
|
|
|
|
|
case errors.Is(err, ErrInvalidParam):
|
|
|
|
|
|
errorType = entities.ApiCallErrorInvalidParam
|
|
|
|
|
|
errorMsg = err.Error()
|
|
|
|
|
|
case errors.Is(err, ErrQueryEmpty):
|
|
|
|
|
|
errorType = entities.ApiCallErrorQueryEmpty
|
|
|
|
|
|
default:
|
|
|
|
|
|
errorType = entities.ApiCallErrorSystem
|
|
|
|
|
|
errorMsg = err.Error()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
apiCall.MarkFailed(errorType, errorMsg)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// 失败请求不创建任务,只记录日志
|
|
|
|
|
|
s.logger.Info("API调用失败,记录失败状态",
|
|
|
|
|
|
zap.String("transaction_id", apiCall.TransactionId),
|
|
|
|
|
|
zap.String("error_type", errorType),
|
|
|
|
|
|
zap.String("error_msg", errorMsg))
|
|
|
|
|
|
|
|
|
|
|
|
// 可选:如果需要统计失败请求,可以在这里添加计数器
|
|
|
|
|
|
// s.failureCounter.Inc()
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetUserApiKeys 获取用户API密钥
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error) {
|
|
|
|
|
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &dto.ApiKeysResponse{
|
|
|
|
|
|
ID: apiUser.ID,
|
|
|
|
|
|
UserID: apiUser.UserId,
|
|
|
|
|
|
AccessID: apiUser.AccessId,
|
|
|
|
|
|
SecretKey: apiUser.SecretKey,
|
|
|
|
|
|
Status: apiUser.Status,
|
|
|
|
|
|
CreatedAt: apiUser.CreatedAt,
|
|
|
|
|
|
UpdatedAt: apiUser.UpdatedAt,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetUserWhiteList 获取用户白名单列表
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error) {
|
|
|
|
|
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保WhiteList不为nil
|
|
|
|
|
|
if apiUser.WhiteList == nil {
|
|
|
|
|
|
apiUser.WhiteList = []string{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将白名单字符串数组转换为响应格式
|
|
|
|
|
|
var items []dto.WhiteListResponse
|
|
|
|
|
|
for _, ip := range apiUser.WhiteList {
|
|
|
|
|
|
items = append(items, dto.WhiteListResponse{
|
|
|
|
|
|
ID: apiUser.ID, // 使用API用户ID作为标识
|
|
|
|
|
|
UserID: apiUser.UserId,
|
|
|
|
|
|
IPAddress: ip,
|
|
|
|
|
|
CreatedAt: apiUser.CreatedAt, // 使用API用户创建时间
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &dto.WhiteListListResponse{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: len(items),
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddWhiteListIP 添加白名单IP
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error {
|
|
|
|
|
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保WhiteList不为nil
|
|
|
|
|
|
if apiUser.WhiteList == nil {
|
|
|
|
|
|
apiUser.WhiteList = []string{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用实体的领域方法添加IP到白名单
|
|
|
|
|
|
err = apiUser.AddToWhiteList(ipAddress)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存更新
|
|
|
|
|
|
err = s.apiUserService.SaveApiUser(ctx, apiUser)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DeleteWhiteListIP 删除白名单IP
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error {
|
|
|
|
|
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保WhiteList不为nil
|
|
|
|
|
|
if apiUser.WhiteList == nil {
|
|
|
|
|
|
apiUser.WhiteList = []string{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用实体的领域方法删除IP
|
|
|
|
|
|
err = apiUser.RemoveFromWhiteList(ipAddress)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存更新
|
|
|
|
|
|
err = s.apiUserService.SaveApiUser(ctx, apiUser)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetUserApiCalls 获取用户API调用记录
|
2025-09-12 01:15:09 +08:00
|
|
|
|
func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID string, filters map[string]interface{}, options shared_interfaces.ListOptions) (*dto.ApiCallListResponse, error) {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 查询API调用记录(包含产品名称)
|
|
|
|
|
|
productNameMap, calls, total, err := s.apiCallRepository.ListByUserIdWithFiltersAndProductName(ctx, userID, filters, options)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询API调用记录失败", zap.Error(err), zap.String("userID", userID))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应DTO
|
|
|
|
|
|
var items []dto.ApiCallRecordResponse
|
|
|
|
|
|
for _, call := range calls {
|
|
|
|
|
|
item := dto.ApiCallRecordResponse{
|
|
|
|
|
|
ID: call.ID,
|
|
|
|
|
|
AccessId: call.AccessId,
|
|
|
|
|
|
UserId: *call.UserId,
|
|
|
|
|
|
TransactionId: call.TransactionId,
|
|
|
|
|
|
ClientIp: call.ClientIp,
|
|
|
|
|
|
Status: call.Status,
|
|
|
|
|
|
StartAt: call.StartAt.Format("2006-01-02 15:04:05"),
|
|
|
|
|
|
CreatedAt: call.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
|
|
|
|
UpdatedAt: call.UpdatedAt.Format("2006-01-02 15:04:05"),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理可选字段
|
|
|
|
|
|
if call.ProductId != nil {
|
|
|
|
|
|
item.ProductId = call.ProductId
|
|
|
|
|
|
}
|
|
|
|
|
|
// 从映射中获取产品名称
|
|
|
|
|
|
if productName, exists := productNameMap[call.ID]; exists {
|
|
|
|
|
|
item.ProductName = &productName
|
|
|
|
|
|
}
|
|
|
|
|
|
if call.EndAt != nil {
|
|
|
|
|
|
endAt := call.EndAt.Format("2006-01-02 15:04:05")
|
|
|
|
|
|
item.EndAt = &endAt
|
|
|
|
|
|
}
|
|
|
|
|
|
if call.Cost != nil {
|
|
|
|
|
|
cost := call.Cost.String()
|
|
|
|
|
|
item.Cost = &cost
|
|
|
|
|
|
}
|
|
|
|
|
|
if call.ErrorType != nil {
|
|
|
|
|
|
item.ErrorType = call.ErrorType
|
|
|
|
|
|
}
|
|
|
|
|
|
if call.ErrorMsg != nil {
|
|
|
|
|
|
item.ErrorMsg = call.ErrorMsg
|
2025-07-28 23:44:01 +08:00
|
|
|
|
// 添加翻译后的错误信息
|
|
|
|
|
|
item.TranslatedErrorMsg = utils.TranslateErrorMsg(call.ErrorType, call.ErrorMsg)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items = append(items, item)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &dto.ApiCallListResponse{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
2025-08-02 02:54:21 +08:00
|
|
|
|
|
|
|
|
|
|
// GetAdminApiCalls 获取管理端API调用记录
|
2025-09-12 01:15:09 +08:00
|
|
|
|
func (s *ApiApplicationServiceImpl) GetAdminApiCalls(ctx context.Context, filters map[string]interface{}, options shared_interfaces.ListOptions) (*dto.ApiCallListResponse, error) {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
// 查询API调用记录(包含产品名称)
|
|
|
|
|
|
productNameMap, calls, total, err := s.apiCallRepository.ListWithFiltersAndProductName(ctx, filters, options)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询API调用记录失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应DTO
|
|
|
|
|
|
var items []dto.ApiCallRecordResponse
|
|
|
|
|
|
for _, call := range calls {
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 基础字段安全检查
|
|
|
|
|
|
if call.ID == "" {
|
|
|
|
|
|
s.logger.Warn("跳过无效的API调用记录:ID为空")
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-02 02:54:21 +08:00
|
|
|
|
item := dto.ApiCallRecordResponse{
|
|
|
|
|
|
ID: call.ID,
|
|
|
|
|
|
AccessId: call.AccessId,
|
|
|
|
|
|
TransactionId: call.TransactionId,
|
|
|
|
|
|
ClientIp: call.ClientIp,
|
|
|
|
|
|
Status: call.Status,
|
2025-08-29 16:14:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 安全设置用户ID
|
|
|
|
|
|
if call.UserId != nil && *call.UserId != "" {
|
|
|
|
|
|
item.UserId = *call.UserId
|
|
|
|
|
|
} else {
|
|
|
|
|
|
item.UserId = "未知用户"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 安全设置时间字段
|
|
|
|
|
|
if !call.StartAt.IsZero() {
|
|
|
|
|
|
item.StartAt = call.StartAt.Format("2006-01-02 15:04:05")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
item.StartAt = "未知时间"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !call.CreatedAt.IsZero() {
|
|
|
|
|
|
item.CreatedAt = call.CreatedAt.Format("2006-01-02 15:04:05")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
item.CreatedAt = "未知时间"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !call.UpdatedAt.IsZero() {
|
|
|
|
|
|
item.UpdatedAt = call.UpdatedAt.Format("2006-01-02 15:04:05")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
item.UpdatedAt = "未知时间"
|
2025-08-02 02:54:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理可选字段
|
2025-08-29 16:14:36 +08:00
|
|
|
|
if call.ProductId != nil && *call.ProductId != "" {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
item.ProductId = call.ProductId
|
|
|
|
|
|
}
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-02 02:54:21 +08:00
|
|
|
|
// 从映射中获取产品名称
|
2025-08-29 16:14:36 +08:00
|
|
|
|
if productName, exists := productNameMap[call.ID]; exists && productName != "" {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
item.ProductName = &productName
|
|
|
|
|
|
}
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 安全设置结束时间
|
|
|
|
|
|
if call.EndAt != nil && !call.EndAt.IsZero() {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
endAt := call.EndAt.Format("2006-01-02 15:04:05")
|
|
|
|
|
|
item.EndAt = &endAt
|
|
|
|
|
|
}
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 安全设置费用
|
2025-08-02 02:54:21 +08:00
|
|
|
|
if call.Cost != nil {
|
|
|
|
|
|
cost := call.Cost.String()
|
2025-08-29 16:14:36 +08:00
|
|
|
|
if cost != "" {
|
|
|
|
|
|
item.Cost = &cost
|
|
|
|
|
|
}
|
2025-08-02 02:54:21 +08:00
|
|
|
|
}
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 安全设置错误类型
|
|
|
|
|
|
if call.ErrorType != nil && *call.ErrorType != "" {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
item.ErrorType = call.ErrorType
|
|
|
|
|
|
}
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 安全设置错误信息
|
|
|
|
|
|
if call.ErrorMsg != nil && *call.ErrorMsg != "" {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
item.ErrorMsg = call.ErrorMsg
|
|
|
|
|
|
// 添加翻译后的错误信息
|
2025-08-29 16:14:36 +08:00
|
|
|
|
if call.ErrorType != nil && *call.ErrorType != "" {
|
|
|
|
|
|
item.TranslatedErrorMsg = utils.TranslateErrorMsg(call.ErrorType, call.ErrorMsg)
|
|
|
|
|
|
}
|
2025-08-02 02:54:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 获取用户信息和企业名称(增强空指针防护)
|
|
|
|
|
|
if call.UserId != nil && *call.UserId != "" {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, *call.UserId)
|
2025-08-29 16:14:36 +08:00
|
|
|
|
if err == nil && user.ID != "" {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
companyName := "未知企业"
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 安全获取企业名称
|
|
|
|
|
|
if user.EnterpriseInfo != nil && user.EnterpriseInfo.CompanyName != "" {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
companyName = user.EnterpriseInfo.CompanyName
|
|
|
|
|
|
}
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-02 02:54:21 +08:00
|
|
|
|
item.CompanyName = &companyName
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 安全构建用户响应
|
2025-08-02 02:54:21 +08:00
|
|
|
|
item.User = &dto.UserSimpleResponse{
|
|
|
|
|
|
ID: user.ID,
|
|
|
|
|
|
CompanyName: companyName,
|
|
|
|
|
|
Phone: user.Phone,
|
|
|
|
|
|
}
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 验证用户数据的完整性
|
|
|
|
|
|
if user.Phone == "" {
|
2025-09-01 18:29:59 +08:00
|
|
|
|
s.logger.Warn("用户手机号为空",
|
2025-08-29 16:14:36 +08:00
|
|
|
|
zap.String("user_id", user.ID),
|
|
|
|
|
|
zap.String("call_id", call.ID))
|
|
|
|
|
|
item.User.Phone = "未知手机号"
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 用户查询失败或用户数据不完整时的处理
|
|
|
|
|
|
if err != nil {
|
2025-09-01 18:29:59 +08:00
|
|
|
|
s.logger.Warn("获取用户信息失败",
|
2025-08-29 16:14:36 +08:00
|
|
|
|
zap.String("user_id", *call.UserId),
|
|
|
|
|
|
zap.String("call_id", call.ID),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
} else if user.ID == "" {
|
2025-09-01 18:29:59 +08:00
|
|
|
|
s.logger.Warn("用户ID为空",
|
2025-08-29 16:14:36 +08:00
|
|
|
|
zap.String("call_user_id", *call.UserId),
|
|
|
|
|
|
zap.String("call_id", call.ID))
|
|
|
|
|
|
}
|
2025-09-01 18:29:59 +08:00
|
|
|
|
|
2025-08-29 16:14:36 +08:00
|
|
|
|
// 设置默认值
|
|
|
|
|
|
defaultCompanyName := "未知企业"
|
|
|
|
|
|
item.CompanyName = &defaultCompanyName
|
|
|
|
|
|
item.User = &dto.UserSimpleResponse{
|
|
|
|
|
|
ID: "未知用户",
|
|
|
|
|
|
CompanyName: defaultCompanyName,
|
|
|
|
|
|
Phone: "未知手机号",
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 用户ID为空时的处理
|
|
|
|
|
|
defaultCompanyName := "未知企业"
|
|
|
|
|
|
item.CompanyName = &defaultCompanyName
|
|
|
|
|
|
item.User = &dto.UserSimpleResponse{
|
|
|
|
|
|
ID: "未知用户",
|
|
|
|
|
|
CompanyName: defaultCompanyName,
|
|
|
|
|
|
Phone: "未知手机号",
|
2025-08-02 02:54:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items = append(items, item)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &dto.ApiCallListResponse{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
2025-08-18 14:13:16 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// ExportAdminApiCalls 导出管理端API调用记录
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) ExportAdminApiCalls(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error) {
|
|
|
|
|
|
const batchSize = 1000 // 每批处理1000条记录
|
|
|
|
|
|
var allCalls []*entities.ApiCall
|
|
|
|
|
|
var productNameMap map[string]string
|
|
|
|
|
|
|
|
|
|
|
|
// 分批获取数据
|
|
|
|
|
|
page := 1
|
|
|
|
|
|
for {
|
|
|
|
|
|
// 查询当前批次的数据
|
|
|
|
|
|
batchProductNameMap, calls, _, err := s.apiCallRepository.ListWithFiltersAndProductName(ctx, filters, shared_interfaces.ListOptions{
|
|
|
|
|
|
Page: page,
|
|
|
|
|
|
PageSize: batchSize,
|
|
|
|
|
|
Sort: "created_at",
|
|
|
|
|
|
Order: "desc",
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询导出API调用记录失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 合并产品名称映射
|
|
|
|
|
|
if productNameMap == nil {
|
|
|
|
|
|
productNameMap = batchProductNameMap
|
|
|
|
|
|
} else {
|
|
|
|
|
|
for k, v := range batchProductNameMap {
|
|
|
|
|
|
productNameMap[k] = v
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到总数据中
|
|
|
|
|
|
allCalls = append(allCalls, calls...)
|
|
|
|
|
|
|
|
|
|
|
|
// 如果当前批次数据少于批次大小,说明已经是最后一批
|
|
|
|
|
|
if len(calls) < batchSize {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
page++
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量获取企业名称映射,避免N+1查询问题
|
|
|
|
|
|
companyNameMap, err := s.batchGetCompanyNamesForApiCalls(ctx, allCalls)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Warn("批量获取企业名称失败,使用默认值", zap.Error(err))
|
|
|
|
|
|
companyNameMap = make(map[string]string)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 准备导出数据
|
|
|
|
|
|
headers := []string{"企业名称", "产品名称", "交易ID", "客户端IP", "状态", "开始时间", "结束时间"}
|
|
|
|
|
|
columnWidths := []float64{30, 20, 40, 15, 10, 20, 20}
|
|
|
|
|
|
|
|
|
|
|
|
data := make([][]interface{}, len(allCalls))
|
|
|
|
|
|
for i, call := range allCalls {
|
|
|
|
|
|
// 从映射中获取企业名称
|
|
|
|
|
|
companyName := "未知企业"
|
|
|
|
|
|
if call.UserId != nil {
|
|
|
|
|
|
companyName = companyNameMap[*call.UserId]
|
|
|
|
|
|
if companyName == "" {
|
|
|
|
|
|
companyName = "未知企业"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取产品名称
|
|
|
|
|
|
productName := "未知产品"
|
|
|
|
|
|
if call.ID != "" {
|
|
|
|
|
|
productName = productNameMap[call.ID]
|
|
|
|
|
|
if productName == "" {
|
|
|
|
|
|
productName = "未知产品"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化时间
|
|
|
|
|
|
startAt := call.StartAt.Format("2006-01-02 15:04:05")
|
|
|
|
|
|
endAt := ""
|
|
|
|
|
|
if call.EndAt != nil {
|
|
|
|
|
|
endAt = call.EndAt.Format("2006-01-02 15:04:05")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data[i] = []interface{}{
|
|
|
|
|
|
companyName,
|
|
|
|
|
|
productName,
|
|
|
|
|
|
call.TransactionId,
|
|
|
|
|
|
call.ClientIp,
|
|
|
|
|
|
call.Status,
|
|
|
|
|
|
startAt,
|
|
|
|
|
|
endAt,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建导出配置
|
|
|
|
|
|
config := &export.ExportConfig{
|
|
|
|
|
|
SheetName: "API调用记录",
|
|
|
|
|
|
Headers: headers,
|
|
|
|
|
|
Data: data,
|
|
|
|
|
|
ColumnWidths: columnWidths,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用导出管理器生成文件
|
|
|
|
|
|
return s.exportManager.Export(ctx, config, format)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-18 14:13:16 +08:00
|
|
|
|
// EncryptParams 加密参数
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) EncryptParams(ctx context.Context, userID string, cmd *commands.EncryptCommand) (string, error) {
|
|
|
|
|
|
// 1. 将数据转换为JSON字节数组
|
|
|
|
|
|
jsonData, err := json.Marshal(cmd.Data)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("序列化参数失败", zap.Error(err))
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
2025-08-27 22:19:19 +08:00
|
|
|
|
|
2025-08-18 14:13:16 +08:00
|
|
|
|
// 2. 使用前端传来的SecretKey进行加密
|
|
|
|
|
|
encryptedData, err := crypto.AesEncrypt(jsonData, cmd.SecretKey)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("加密参数失败", zap.Error(err))
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return encryptedData, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DecryptParams 解密参数
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) DecryptParams(ctx context.Context, userID string, cmd *commands.DecryptCommand) (map[string]interface{}, error) {
|
|
|
|
|
|
// 1. 使用前端传来的SecretKey进行解密
|
|
|
|
|
|
decryptedData, err := crypto.AesDecrypt(cmd.EncryptedData, cmd.SecretKey)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("解密参数失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-08-27 22:19:19 +08:00
|
|
|
|
|
2025-08-18 14:13:16 +08:00
|
|
|
|
// 2. 将解密后的JSON字节数组转换为map
|
|
|
|
|
|
var result map[string]interface{}
|
|
|
|
|
|
err = json.Unmarshal(decryptedData, &result)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("反序列化解密数据失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
2025-08-27 22:19:19 +08:00
|
|
|
|
|
|
|
|
|
|
// GetFormConfig 获取指定API的表单配置
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) GetFormConfig(ctx context.Context, apiCode string) (*dto.FormConfigResponse, error) {
|
|
|
|
|
|
// 调用领域服务获取表单配置
|
|
|
|
|
|
config, err := s.formConfigService.GetFormConfig(apiCode)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("获取表单配置失败", zap.String("api_code", apiCode), zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if config == nil {
|
|
|
|
|
|
return nil, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为应用层DTO
|
|
|
|
|
|
response := &dto.FormConfigResponse{
|
|
|
|
|
|
ApiCode: config.ApiCode,
|
|
|
|
|
|
Fields: make([]dto.FormField, len(config.Fields)),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for i, field := range config.Fields {
|
|
|
|
|
|
response.Fields[i] = dto.FormField{
|
|
|
|
|
|
Name: field.Name,
|
|
|
|
|
|
Label: field.Label,
|
|
|
|
|
|
Type: field.Type,
|
|
|
|
|
|
Required: field.Required,
|
|
|
|
|
|
Validation: field.Validation,
|
|
|
|
|
|
Description: field.Description,
|
|
|
|
|
|
Example: field.Example,
|
|
|
|
|
|
Placeholder: field.Placeholder,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
|
|
}
|
2025-09-12 01:15:09 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== 异步任务处理方法 ====================
|
|
|
|
|
|
|
|
|
|
|
|
// SaveApiCall 保存API调用记录
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) SaveApiCall(ctx context.Context, cmd *commands.SaveApiCallCommand) error {
|
|
|
|
|
|
s.logger.Debug("开始保存API调用记录",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID))
|
|
|
|
|
|
|
|
|
|
|
|
// 创建ApiCall实体
|
|
|
|
|
|
apiCall := &entities.ApiCall{
|
|
|
|
|
|
ID: cmd.ApiCallID,
|
|
|
|
|
|
AccessId: "", // SaveApiCallCommand中没有AccessID字段
|
|
|
|
|
|
UserId: &cmd.UserID,
|
|
|
|
|
|
TransactionId: cmd.TransactionID,
|
|
|
|
|
|
ClientIp: cmd.ClientIP,
|
|
|
|
|
|
Status: cmd.Status,
|
|
|
|
|
|
StartAt: time.Now(), // SaveApiCallCommand中没有StartAt字段
|
|
|
|
|
|
EndAt: nil, // SaveApiCallCommand中没有EndAt字段
|
|
|
|
|
|
Cost: &[]decimal.Decimal{decimal.NewFromFloat(cmd.Cost)}[0], // 转换float64为*decimal.Decimal
|
|
|
|
|
|
ErrorType: &cmd.ErrorType, // 转换string为*string
|
|
|
|
|
|
ErrorMsg: &cmd.ErrorMsg, // 转换string为*string
|
|
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
|
|
UpdatedAt: time.Now(),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存到数据库
|
|
|
|
|
|
if err := s.apiCallRepository.Create(ctx, apiCall); err != nil {
|
|
|
|
|
|
s.logger.Error("保存API调用记录失败", zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("API调用记录保存成功",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.String("status", cmd.Status))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ProcessDeduction 处理扣款
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) ProcessDeduction(ctx context.Context, cmd *commands.ProcessDeductionCommand) error {
|
|
|
|
|
|
s.logger.Debug("开始处理扣款",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("amount", cmd.Amount))
|
|
|
|
|
|
|
|
|
|
|
|
// 直接调用钱包服务进行扣款
|
|
|
|
|
|
amount, err := decimal.NewFromString(cmd.Amount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("金额格式错误", zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.walletService.Deduct(ctx, cmd.UserID, amount, cmd.ApiCallID, cmd.TransactionID, cmd.ProductID); err != nil {
|
|
|
|
|
|
s.logger.Error("扣款处理失败",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("扣款处理成功",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UpdateUsageStats 更新使用统计
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) UpdateUsageStats(ctx context.Context, cmd *commands.UpdateUsageStatsCommand) error {
|
|
|
|
|
|
s.logger.Debug("开始更新使用统计",
|
|
|
|
|
|
zap.String("subscription_id", cmd.SubscriptionID),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.Int("increment", cmd.Increment))
|
|
|
|
|
|
|
|
|
|
|
|
// 直接调用订阅服务更新使用统计
|
|
|
|
|
|
if err := s.subscriptionService.IncrementSubscriptionAPIUsage(ctx, cmd.SubscriptionID, int64(cmd.Increment)); err != nil {
|
|
|
|
|
|
s.logger.Error("更新使用统计失败",
|
|
|
|
|
|
zap.String("subscription_id", cmd.SubscriptionID),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("使用统计更新成功",
|
|
|
|
|
|
zap.String("subscription_id", cmd.SubscriptionID),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RecordApiLog 记录API日志
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) RecordApiLog(ctx context.Context, cmd *commands.RecordApiLogCommand) error {
|
|
|
|
|
|
s.logger.Debug("开始记录API日志",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.String("api_name", cmd.ApiName),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID))
|
|
|
|
|
|
|
|
|
|
|
|
// 记录结构化日志
|
|
|
|
|
|
s.logger.Info("API调用日志",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("api_name", cmd.ApiName),
|
|
|
|
|
|
zap.String("client_ip", cmd.ClientIP),
|
|
|
|
|
|
zap.Int64("response_size", cmd.ResponseSize),
|
|
|
|
|
|
zap.Time("timestamp", time.Now()))
|
|
|
|
|
|
|
|
|
|
|
|
// 这里可以添加其他日志记录逻辑
|
|
|
|
|
|
// 例如:写入专门的日志文件、发送到日志系统、写入数据库等
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("API日志记录成功",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.String("api_name", cmd.ApiName),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ProcessCompensation 处理补偿
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) ProcessCompensation(ctx context.Context, cmd *commands.ProcessCompensationCommand) error {
|
|
|
|
|
|
s.logger.Debug("开始处理补偿",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.String("type", cmd.Type))
|
|
|
|
|
|
|
|
|
|
|
|
// 根据补偿类型处理不同的补偿逻辑
|
|
|
|
|
|
switch cmd.Type {
|
|
|
|
|
|
case "refund":
|
|
|
|
|
|
// 退款补偿 - ProcessCompensationCommand中没有Amount字段,暂时只记录日志
|
|
|
|
|
|
s.logger.Info("退款补偿处理", zap.String("transaction_id", cmd.TransactionID))
|
|
|
|
|
|
case "credit":
|
|
|
|
|
|
// 积分补偿 - ProcessCompensationCommand中没有CreditAmount字段,暂时只记录日志
|
|
|
|
|
|
s.logger.Info("积分补偿处理", zap.String("transaction_id", cmd.TransactionID))
|
|
|
|
|
|
case "subscription_extension":
|
|
|
|
|
|
// 订阅延期补偿 - ProcessCompensationCommand中没有ExtensionDays字段,暂时只记录日志
|
|
|
|
|
|
s.logger.Info("订阅延期补偿处理", zap.String("transaction_id", cmd.TransactionID))
|
|
|
|
|
|
default:
|
|
|
|
|
|
s.logger.Warn("未知的补偿类型", zap.String("type", cmd.Type))
|
|
|
|
|
|
return fmt.Errorf("未知的补偿类型: %s", cmd.Type)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("补偿处理成功",
|
|
|
|
|
|
zap.String("transaction_id", cmd.TransactionID),
|
|
|
|
|
|
zap.String("type", cmd.Type))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// validateWalletStatus 验证钱包状态
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) validateWalletStatus(ctx context.Context, userID string, product *product_entities.Product) error {
|
|
|
|
|
|
// 1. 获取用户钱包信息
|
|
|
|
|
|
wallet, err := s.walletService.LoadWalletByUserId(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("获取钱包信息失败",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return ErrSystem
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 检查钱包是否激活
|
|
|
|
|
|
if !wallet.IsActive {
|
|
|
|
|
|
s.logger.Error("钱包未激活",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("wallet_id", wallet.ID))
|
|
|
|
|
|
return ErrFrozenAccount
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 检查钱包余额是否充足
|
|
|
|
|
|
requiredAmount := product.Price
|
|
|
|
|
|
if wallet.Balance.LessThan(requiredAmount) {
|
|
|
|
|
|
s.logger.Error("钱包余额不足",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("balance", wallet.Balance.String()),
|
|
|
|
|
|
zap.String("required_amount", requiredAmount.String()),
|
|
|
|
|
|
zap.String("product_code", product.Code))
|
|
|
|
|
|
return ErrInsufficientBalance
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 检查是否欠费
|
|
|
|
|
|
if wallet.IsArrears() {
|
|
|
|
|
|
s.logger.Error("钱包存在欠费",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("wallet_id", wallet.ID))
|
|
|
|
|
|
return ErrFrozenAccount
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("钱包状态验证通过",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("wallet_id", wallet.ID),
|
|
|
|
|
|
zap.String("balance", wallet.Balance.String()))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-14 16:34:55 +08:00
|
|
|
|
// validateSubscriptionStatus 验证订阅状态并返回订阅信息
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) validateSubscriptionStatus(ctx context.Context, userID string, product *product_entities.Product) (*product_entities.Subscription, error) {
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// 1. 检查用户是否已订阅该产品
|
|
|
|
|
|
subscription, err := s.subscriptionService.UserSubscribedProductByCode(ctx, userID, product.Code)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 如果没有找到订阅记录,说明用户未订阅
|
|
|
|
|
|
s.logger.Error("用户未订阅该产品",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("product_code", product.Code),
|
|
|
|
|
|
zap.Error(err))
|
2025-09-14 16:34:55 +08:00
|
|
|
|
return nil, ErrProductNotSubscribed
|
2025-09-12 01:15:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 检查订阅是否有效(未删除)
|
|
|
|
|
|
if !subscription.IsValid() {
|
|
|
|
|
|
s.logger.Error("订阅已失效",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("subscription_id", subscription.ID),
|
|
|
|
|
|
zap.String("product_code", product.Code))
|
2025-09-14 16:34:55 +08:00
|
|
|
|
return nil, ErrSubscriptionExpired
|
2025-09-12 01:15:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("订阅状态验证通过",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("subscription_id", subscription.ID),
|
|
|
|
|
|
zap.String("product_code", product.Code),
|
2025-09-14 16:34:55 +08:00
|
|
|
|
zap.String("subscription_price", subscription.Price.String()),
|
2025-09-12 01:15:09 +08:00
|
|
|
|
zap.Int64("api_used", subscription.APIUsed))
|
|
|
|
|
|
|
2025-09-14 16:34:55 +08:00
|
|
|
|
return subscription, nil
|
2025-09-12 01:15:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// batchGetCompanyNamesForApiCalls 批量获取企业名称映射(用于API调用记录)
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) batchGetCompanyNamesForApiCalls(ctx context.Context, calls []*entities.ApiCall) (map[string]string, error) {
|
|
|
|
|
|
// 收集所有唯一的用户ID
|
|
|
|
|
|
userIDSet := make(map[string]bool)
|
|
|
|
|
|
for _, call := range calls {
|
|
|
|
|
|
if call.UserId != nil && *call.UserId != "" {
|
|
|
|
|
|
userIDSet[*call.UserId] = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为切片
|
|
|
|
|
|
userIDs := make([]string, 0, len(userIDSet))
|
|
|
|
|
|
for userID := range userIDSet {
|
|
|
|
|
|
userIDs = append(userIDs, userID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量查询用户信息
|
|
|
|
|
|
users, err := s.userRepo.BatchGetByIDsWithEnterpriseInfo(ctx, userIDs)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建企业名称映射
|
|
|
|
|
|
companyNameMap := make(map[string]string)
|
|
|
|
|
|
for _, user := range users {
|
|
|
|
|
|
companyName := "未知企业"
|
|
|
|
|
|
if user.EnterpriseInfo != nil {
|
|
|
|
|
|
companyName = user.EnterpriseInfo.CompanyName
|
|
|
|
|
|
}
|
|
|
|
|
|
companyNameMap[user.ID] = companyName
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return companyNameMap, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetUserBalanceAlertSettings 获取用户余额预警设置
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) GetUserBalanceAlertSettings(ctx context.Context, userID string) (map[string]interface{}, error) {
|
|
|
|
|
|
// 获取API用户信息
|
|
|
|
|
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("获取API用户信息失败",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("获取API用户信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if apiUser == nil {
|
|
|
|
|
|
return nil, fmt.Errorf("API用户不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 返回预警设置
|
|
|
|
|
|
settings := map[string]interface{}{
|
|
|
|
|
|
"enabled": apiUser.BalanceAlertEnabled,
|
|
|
|
|
|
"threshold": apiUser.BalanceAlertThreshold,
|
|
|
|
|
|
"alert_phone": apiUser.AlertPhone,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return settings, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UpdateUserBalanceAlertSettings 更新用户余额预警设置
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) UpdateUserBalanceAlertSettings(ctx context.Context, userID string, enabled bool, threshold float64, alertPhone string) error {
|
|
|
|
|
|
// 获取API用户信息
|
|
|
|
|
|
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("获取API用户信息失败",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("获取API用户信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if apiUser == nil {
|
|
|
|
|
|
return fmt.Errorf("API用户不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新预警设置
|
|
|
|
|
|
if err := apiUser.UpdateBalanceAlertSettings(enabled, threshold, alertPhone); err != nil {
|
|
|
|
|
|
s.logger.Error("更新预警设置失败",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("更新预警设置失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存到数据库
|
|
|
|
|
|
if err := s.apiUserService.SaveApiUser(ctx, apiUser); err != nil {
|
|
|
|
|
|
s.logger.Error("保存API用户信息失败",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("保存API用户信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("用户余额预警设置更新成功",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.Bool("enabled", enabled),
|
|
|
|
|
|
zap.Float64("threshold", threshold),
|
|
|
|
|
|
zap.String("alert_phone", alertPhone))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestBalanceAlertSms 测试余额预警短信
|
|
|
|
|
|
func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, userID string, phone string, balance float64, alertType string) error {
|
|
|
|
|
|
// 获取用户信息以获取企业名称
|
|
|
|
|
|
user, err := s.userRepo.GetByID(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("获取用户信息失败",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("获取用户信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取企业名称
|
|
|
|
|
|
enterpriseName := "天远数据用户"
|
|
|
|
|
|
if user.EnterpriseInfo != nil && user.EnterpriseInfo.CompanyName != "" {
|
|
|
|
|
|
enterpriseName = user.EnterpriseInfo.CompanyName
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用短信服务发送测试短信
|
|
|
|
|
|
if err := s.balanceAlertService.CheckAndSendAlert(ctx, userID, decimal.NewFromFloat(balance)); err != nil {
|
|
|
|
|
|
s.logger.Error("发送测试预警短信失败",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("phone", phone),
|
|
|
|
|
|
zap.Float64("balance", balance),
|
|
|
|
|
|
zap.String("alert_type", alertType),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("发送测试短信失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("测试预警短信发送成功",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("phone", phone),
|
|
|
|
|
|
zap.Float64("balance", balance),
|
|
|
|
|
|
zap.String("alert_type", alertType),
|
|
|
|
|
|
zap.String("enterprise_name", enterpriseName))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|