217 lines
7.0 KiB
Go
217 lines
7.0 KiB
Go
|
|
package service
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bdqr-server/app/main/model"
|
|||
|
|
"context"
|
|||
|
|
"database/sql"
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"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
|
|||
|
|
}
|