package service import ( "context" "database/sql" "encoding/json" "fmt" "time" "ycc-server/app/main/model" "github.com/Masterminds/squirrel" "github.com/zeromicro/go-zero/core/logx" ) // TianyuanapiCallLogService 天元API调用记录服务 type TianyuanapiCallLogService struct { tianyuanapiCallLogModel model.TianyuanapiCallLogModel featureModel model.FeatureModel } // NewTianyuanapiCallLogService 创建天元API调用记录服务 func NewTianyuanapiCallLogService( tianyuanapiCallLogModel model.TianyuanapiCallLogModel, featureModel model.FeatureModel, ) *TianyuanapiCallLogService { return &TianyuanapiCallLogService{ tianyuanapiCallLogModel: tianyuanapiCallLogModel, featureModel: featureModel, } } // CallLogOptions 调用记录选项 type CallLogOptions struct { FeatureID string // 功能ID ApiID string // API标识(如:YYSYBE08) OrderID string // 订单ID(可选) QueryID string // 查询ID(可选) CallStatus int64 // 调用状态:0=失败,1=成功 ResponseTime int64 // 响应耗时(毫秒) ErrorCode string // 错误码(失败时) ErrorMessage string // 错误信息(失败时) RequestParams interface{} // 请求参数(可选) ResponseData interface{} // 响应数据(可选) TransactionID string // 天元API流水号 } // RecordCall 记录天元API调用 func (s *TianyuanapiCallLogService) RecordCall(ctx context.Context, opts CallLogOptions) error { // 1. 获取feature的成本价 costPrice := 0.00 if opts.CallStatus == 1 { // 只有成功才计算成本 if opts.FeatureID == "" { logx.Infof("记录API调用时feature_id为空,api_id=%s,无法获取成本价", opts.ApiID) } else { feature, err := s.featureModel.FindOne(ctx, opts.FeatureID) if err == nil { costPrice = feature.CostPrice logx.Infof("记录API调用 - feature_id=%s, api_id=%s, cost_price=%f", opts.FeatureID, opts.ApiID, costPrice) } else { logx.Errorf("查询feature成本价失败,feature_id=%s, api_id=%s, err=%v", opts.FeatureID, opts.ApiID, err) } } } // 2. 转换参数和响应为JSON字符串 var requestParamsStr, responseDataStr *string if opts.RequestParams != nil { if bytes, err := json.Marshal(opts.RequestParams); err == nil { jsonStr := string(bytes) requestParamsStr = &jsonStr } } if opts.ResponseData != nil { // 只记录响应数据的前1000个字符,避免存储过大 if bytes, err := json.Marshal(opts.ResponseData); err == nil { jsonStr := string(bytes) if len(jsonStr) > 1000 { jsonStr = jsonStr[:1000] + "...[truncated]" } responseDataStr = &jsonStr } } // 3. 构建调用记录 callTime := time.Now() deleteTime := sql.NullTime{} callLog := &model.TianyuanapiCallLog{ FeatureId: opts.FeatureID, ApiId: opts.ApiID, OrderId: sql.NullString{}, QueryId: sql.NullString{}, CallStatus: opts.CallStatus, CallTime: callTime, ResponseTime: sql.NullInt64{}, CostPrice: costPrice, ErrorCode: sql.NullString{}, ErrorMessage: sql.NullString{}, RequestParams: sql.NullString{}, ResponseData: sql.NullString{}, TransactionId: sql.NullString{}, CreateTime: callTime, UpdateTime: callTime, DeleteTime: deleteTime, DelState: 0, Version: 0, } // 4. 填充可选字段 if opts.OrderID != "" { callLog.OrderId = sql.NullString{String: opts.OrderID, Valid: true} } if opts.QueryID != "" { callLog.QueryId = sql.NullString{String: opts.QueryID, Valid: true} } if opts.ResponseTime > 0 { callLog.ResponseTime = sql.NullInt64{Int64: opts.ResponseTime, Valid: true} } if opts.ErrorCode != "" { callLog.ErrorCode = sql.NullString{String: opts.ErrorCode, Valid: true} } if opts.ErrorMessage != "" { callLog.ErrorMessage = sql.NullString{String: opts.ErrorMessage, Valid: true} } if requestParamsStr != nil { callLog.RequestParams = sql.NullString{String: *requestParamsStr, Valid: true} } if responseDataStr != nil { callLog.ResponseData = sql.NullString{String: *responseDataStr, Valid: true} } if opts.TransactionID != "" { callLog.TransactionId = sql.NullString{String: opts.TransactionID, Valid: true} } // 5. 插入记录 _, err := s.tianyuanapiCallLogModel.Insert(ctx, nil, callLog) if err != nil { logx.Errorf("插入天元API调用记录失败,feature_id=%s, api_id=%s, err=%v", opts.FeatureID, opts.ApiID, err) return fmt.Errorf("插入调用记录失败: %w", err) } return nil } // GetStatistics 获取统计信息 type StatisticsFilter struct { FeatureID string // 功能ID ApiID string // API标识 StartDate time.Time // 开始日期 EndDate time.Time // 结束日期 } // Statistics 统计信息 type Statistics struct { TotalCalls int64 // 总调用次数 SuccessCalls int64 // 成功次数 FailedCalls int64 // 失败次数 TotalCost float64 // 总成本(成功调用的成本之和) } // GetStatistics 获取统计信息 func (s *TianyuanapiCallLogService) GetStatistics(ctx context.Context, filter StatisticsFilter) (*Statistics, error) { builder := s.tianyuanapiCallLogModel.SelectBuilder() // 添加过滤条件 if filter.FeatureID != "" { builder = builder.Where(squirrel.Eq{"feature_id": filter.FeatureID}) } if filter.ApiID != "" { builder = builder.Where(squirrel.Eq{"api_id": filter.ApiID}) } if !filter.StartDate.IsZero() { builder = builder.Where(squirrel.GtOrEq{"call_time": filter.StartDate}) logx.Infof("API成本统计 - 开始时间: %v", filter.StartDate) } if !filter.EndDate.IsZero() { builder = builder.Where(squirrel.Lt{"call_time": filter.EndDate}) logx.Infof("API成本统计 - 结束时间: %v", filter.EndDate) } // 统计总调用次数 totalCalls, err := s.tianyuanapiCallLogModel.FindCount(ctx, builder, "id") if err != nil { return nil, fmt.Errorf("统计总调用次数失败: %w", err) } logx.Infof("API成本统计 - 总调用次数: %d", totalCalls) // 统计成功次数 successBuilder := builder.Where(squirrel.Eq{"call_status": 1}) successCalls, err := s.tianyuanapiCallLogModel.FindCount(ctx, successBuilder, "id") if err != nil { return nil, fmt.Errorf("统计成功次数失败: %w", err) } logx.Infof("API成本统计 - 成功调用次数: %d", successCalls) // 统计失败次数 failedCalls := totalCalls - successCalls // 统计总成本(仅成功调用) // 先打印SQL以便调试(复制builder避免影响后续查询) debugBuilder := successBuilder query, values, _ := debugBuilder.Columns("IFNULL(SUM(cost_price),0)").Where("del_state = ?", 0).ToSql() logx.Infof("API成本统计 - SQL: %s, 参数: %v", query, values) totalCost, err := s.tianyuanapiCallLogModel.FindSum(ctx, successBuilder, "cost_price") if err != nil { return nil, fmt.Errorf("统计总成本失败: %w", err) } logx.Infof("API成本统计 - 总成本: %f", totalCost) return &Statistics{ TotalCalls: totalCalls, SuccessCalls: successCalls, FailedCalls: failedCalls, TotalCost: totalCost, }, nil }