From 04df8460a40cae11d4ee735d06df9d44514ff852 Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Tue, 13 Jan 2026 18:30:10 +0800 Subject: [PATCH] f --- app/main/api/desc/admin/dashboard.api | 17 + app/main/api/desc/front/agent.api | 20 +- .../handler/agent/checkorderagenthandler.go | 29 ++ app/main/api/internal/handler/routes.go | 5 + .../admingetdashboardstatisticslogic.go | 96 ++++- .../logic/agent/checkorderagentlogic.go | 46 ++ .../logic/query/querysingletestlogic.go | 2 +- .../api/internal/queue/paySuccessNotify.go | 2 +- .../api/internal/service/apirequestService.go | 220 ++++++---- .../service/tianyuanapiCallLogService.go | 201 +++++++++ .../api/internal/service/whitelistService.go | 3 + app/main/api/internal/svc/servicecontext.go | 11 +- app/main/api/internal/types/agent.go | 8 + app/main/api/internal/types/types.go | 26 +- app/main/model/featureModel_gen.go | 15 +- app/main/model/tianyuanapiCallLogModel.go | 27 ++ app/main/model/tianyuanapiCallLogModel_gen.go | 398 ++++++++++++++++++ deploy/script/gen_models.ps1 | 11 +- deploy/sql/Context参数优化说明.md | 158 +++++++ deploy/sql/generate_tianyuanapi_models.sh | 35 ++ deploy/sql/tianyuanapi_cost_migration.sql | 64 +++ deploy/sql/优化后的批量修改说明.md | 183 ++++++++ deploy/sql/利润统计API成本计入说明.md | 135 ++++++ deploy/sql/利润统计面板改造说明.md | 149 +++++++ deploy/sql/天元API调用记录修改说明.md | 159 +++++++ deploy/sql/天元API调用记录功能完成总结.md | 297 +++++++++++++ deploy/sql/批量修改方法签名.sh | 83 ++++ deploy/sql/批量替换API调用方法.ps1 | 86 ++++ deploy/sql/批量替换API调用说明.md | 97 +++++ 29 files changed, 2472 insertions(+), 111 deletions(-) create mode 100644 app/main/api/internal/handler/agent/checkorderagenthandler.go create mode 100644 app/main/api/internal/logic/agent/checkorderagentlogic.go create mode 100644 app/main/api/internal/service/tianyuanapiCallLogService.go create mode 100644 app/main/model/tianyuanapiCallLogModel.go create mode 100644 app/main/model/tianyuanapiCallLogModel_gen.go create mode 100644 deploy/sql/Context参数优化说明.md create mode 100644 deploy/sql/generate_tianyuanapi_models.sh create mode 100644 deploy/sql/tianyuanapi_cost_migration.sql create mode 100644 deploy/sql/优化后的批量修改说明.md create mode 100644 deploy/sql/利润统计API成本计入说明.md create mode 100644 deploy/sql/利润统计面板改造说明.md create mode 100644 deploy/sql/天元API调用记录修改说明.md create mode 100644 deploy/sql/天元API调用记录功能完成总结.md create mode 100644 deploy/sql/批量修改方法签名.sh create mode 100644 deploy/sql/批量替换API调用方法.ps1 create mode 100644 deploy/sql/批量替换API调用说明.md diff --git a/app/main/api/desc/admin/dashboard.api b/app/main/api/desc/admin/dashboard.api index 306c93f..7b3cef1 100644 --- a/app/main/api/desc/admin/dashboard.api +++ b/app/main/api/desc/admin/dashboard.api @@ -67,6 +67,23 @@ type ( TodayProfitRate float64 `json:"today_profit_rate"` // 今日利润率 MonthProfitRate float64 `json:"month_profit_rate"` // 当月利润率 TotalProfitRate float64 `json:"total_profit_rate"` // 总利润率 + // 今日明细 + TodayDetail AdminProfitDetail `json:"today_detail"` + // 当月明细 + MonthDetail AdminProfitDetail `json:"month_detail"` + // 总计明细 + TotalDetail AdminProfitDetail `json:"total_detail"` + } + // 利润明细 + AdminProfitDetail { + Revenue float64 `json:"revenue"` // 营收 + Commission float64 `json:"commission"` // 佣金 + Rebate float64 `json:"rebate"` // 返利 + CompanyTax float64 `json:"company_tax"` // 税务成本 + ApiCost float64 `json:"api_cost"` // API调用成本 + TaxIncome float64 `json:"tax_income"` // 提现收税 + Profit float64 `json:"profit"` // 利润 + ProfitRate float64 `json:"profit_rate"` // 利润率 } // 趋势数据 AdminTrendData { diff --git a/app/main/api/desc/front/agent.api b/app/main/api/desc/front/agent.api index a03eee1..87585cb 100644 --- a/app/main/api/desc/front/agent.api +++ b/app/main/api/desc/front/agent.api @@ -279,6 +279,10 @@ service main { // 下架单个模块(创建订单并支付) @handler OfflineFeature post /whitelist/offline (OfflineFeatureReq) returns (OfflineFeatureResp) + + // 检查订单是否属于当前代理推广 + @handler CheckOrderAgent + get /order/agent (CheckOrderAgentReq) returns (CheckOrderAgentResp) } type ( @@ -683,16 +687,16 @@ type ( } // 检查模块白名单状态请求 CheckFeatureWhitelistStatusReq { - IdCard string `form:"id_card"` // 身份证号 + IdCard string `form:"id_card"` // 身份证号 FeatureApiId string `form:"feature_api_id"` // Feature的API标识 QueryId string `form:"query_id,optional"` // 查询记录ID(可选,用于检查报告数据是否已删除) } // 检查模块白名单状态响应 CheckFeatureWhitelistStatusResp { - IsWhitelisted bool `json:"is_whitelisted"` // 是否在白名单中 + IsWhitelisted bool `json:"is_whitelisted"` // 是否在白名单中 WhitelistPrice float64 `json:"whitelist_price"` // 屏蔽价格(单位:元),如果为0表示不支持下架 - FeatureId string `json:"feature_id"` // Feature的UUID - DataDeleted bool `json:"data_deleted"` // 报告数据是否已删除(仅当提供了query_id时有效) + FeatureId string `json:"feature_id"` // Feature的UUID + DataDeleted bool `json:"data_deleted"` // 报告数据是否已删除(仅当提供了query_id时有效) } // 下架单个模块请求 OfflineFeatureReq { @@ -705,6 +709,14 @@ type ( NeedPay bool `json:"need_pay"` // 是否需要发起支付 Amount float64 `json:"amount"` // 需要支付的金额(单位:元),0表示无需支付 } + // 检查订单是否属于当前代理请求 + CheckOrderAgentReq { + OrderId string `form:"order_id"` // 订单ID + } + // 检查订单是否属于当前代理响应 + CheckOrderAgentResp { + IsAgentOrder bool `json:"is_agent_order"` // 是否是当前代理推广的订单 + } ) // ============================================ diff --git a/app/main/api/internal/handler/agent/checkorderagenthandler.go b/app/main/api/internal/handler/agent/checkorderagenthandler.go new file mode 100644 index 0000000..4b0f0ae --- /dev/null +++ b/app/main/api/internal/handler/agent/checkorderagenthandler.go @@ -0,0 +1,29 @@ +package agent + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "ycc-server/app/main/api/internal/logic/agent" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" +) + +func CheckOrderAgentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.CheckOrderAgentReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := agent.NewCheckOrderAgentLogic(r.Context(), svcCtx) + resp, err := l.CheckOrderAgent(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/routes.go b/app/main/api/internal/handler/routes.go index 6d147d1..228d9fb 100644 --- a/app/main/api/internal/handler/routes.go +++ b/app/main/api/internal/handler/routes.go @@ -715,6 +715,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/level/privilege", Handler: agent.GetLevelPrivilegeHandler(serverCtx), }, + { + Method: http.MethodGet, + Path: "/order/agent", + Handler: agent.CheckOrderAgentHandler(serverCtx), + }, { Method: http.MethodGet, Path: "/product_config", diff --git a/app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go b/app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go index 5be9d91..762f5ac 100644 --- a/app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go +++ b/app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" + "ycc-server/app/main/api/internal/service" "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" @@ -224,7 +225,7 @@ func (l *AdminGetDashboardStatisticsLogic) calculateAgentStatistics(todayStart, func (l *AdminGetDashboardStatisticsLogic) calculateProfitStatistics(todayStart, todayEnd, monthStart, monthEnd time.Time, revenueStats types.AdminRevenueStatistics) (types.AdminProfitStatistics, error) { var stats types.AdminProfitStatistics - // 公司交税比例(6%) + // 税务成本比例(6%) const companyTaxRate = 0.06 // 今日利润计算 @@ -244,20 +245,44 @@ func (l *AdminGetDashboardStatisticsLogic) calculateProfitStatistics(todayStart, if err != nil { return stats, err } - // 今日公司交税(订单金额的6%) + // 今日税务成本(订单金额的6%) todayCompanyTax := todayRevenue * companyTaxRate - // 今日平台收入税(agent_withdrawal_tax表中tax_status=2的tax_amount总和) + // 今日提现收税(agent_withdrawal_tax表中tax_status=2的tax_amount总和) todayTaxIncomeBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). Where("del_state = ? AND tax_status = ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 2, todayStart, todayEnd) todayTaxIncome, err := l.svcCtx.AgentWithdrawalTaxModel.FindSum(l.ctx, todayTaxIncomeBuilder, "tax_amount") if err != nil { return stats, err } - // 今日利润 = 营收 - 佣金 - 返利 - 公司交税 + 平台收入税 - stats.TodayProfit = todayRevenue - todayCommission - todayRebate - todayCompanyTax + todayTaxIncome + // 今日API调用成本 + todayApiCost := 0.0 + if l.svcCtx.TianyuanapiCallLogService != nil { + todayApiStats, err := l.svcCtx.TianyuanapiCallLogService.GetStatistics(l.ctx, service.StatisticsFilter{ + StartDate: todayStart, + EndDate: todayEnd, + }) + if err != nil { + logx.Errorf("获取今日API调用成本失败: %v", err) + } else { + todayApiCost = todayApiStats.TotalCost + } + } + // 今日利润 = 营收 - 佣金 - 返利 - 税务成本 - API调用成本 + 提现收税 + stats.TodayProfit = todayRevenue - todayCommission - todayRebate - todayCompanyTax - todayApiCost + todayTaxIncome if todayRevenue > 0 { stats.TodayProfitRate = stats.TodayProfit / todayRevenue * 100 } + // 今日明细 + stats.TodayDetail = types.AdminProfitDetail{ + Revenue: todayRevenue, + Commission: todayCommission, + Rebate: todayRebate, + CompanyTax: todayCompanyTax, + ApiCost: todayApiCost, + TaxIncome: todayTaxIncome, + Profit: stats.TodayProfit, + ProfitRate: stats.TodayProfitRate, + } // 当月利润计算 // 当月营收 @@ -276,20 +301,44 @@ func (l *AdminGetDashboardStatisticsLogic) calculateProfitStatistics(todayStart, if err != nil { return stats, err } - // 当月公司交税 + // 当月税务成本 monthCompanyTax := monthRevenue * companyTaxRate - // 当月平台收入税 + // 当月提现收税 monthTaxIncomeBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). Where("del_state = ? AND tax_status = ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 2, monthStart, monthEnd) monthTaxIncome, err := l.svcCtx.AgentWithdrawalTaxModel.FindSum(l.ctx, monthTaxIncomeBuilder, "tax_amount") if err != nil { return stats, err } + // 当月API调用成本 + monthApiCost := 0.0 + if l.svcCtx.TianyuanapiCallLogService != nil { + monthApiStats, err := l.svcCtx.TianyuanapiCallLogService.GetStatistics(l.ctx, service.StatisticsFilter{ + StartDate: monthStart, + EndDate: monthEnd, + }) + if err != nil { + logx.Errorf("获取当月API调用成本失败: %v", err) + } else { + monthApiCost = monthApiStats.TotalCost + } + } // 当月利润 - stats.MonthProfit = monthRevenue - monthCommission - monthRebate - monthCompanyTax + monthTaxIncome + stats.MonthProfit = monthRevenue - monthCommission - monthRebate - monthCompanyTax - monthApiCost + monthTaxIncome if monthRevenue > 0 { stats.MonthProfitRate = stats.MonthProfit / monthRevenue * 100 } + // 当月明细 + stats.MonthDetail = types.AdminProfitDetail{ + Revenue: monthRevenue, + Commission: monthCommission, + Rebate: monthRebate, + CompanyTax: monthCompanyTax, + ApiCost: monthApiCost, + TaxIncome: monthTaxIncome, + Profit: stats.MonthProfit, + ProfitRate: stats.MonthProfitRate, + } // 总利润计算 // 总营收 @@ -303,25 +352,46 @@ func (l *AdminGetDashboardStatisticsLogic) calculateProfitStatistics(todayStart, } // 总返利 totalRebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("del_state = ? AND status != ?", globalkey.DelStateNo, 3) + Where("status != ?", 3) totalRebate, err := l.svcCtx.AgentRebateModel.FindSum(l.ctx, totalRebateBuilder, "rebate_amount") if err != nil { return stats, err } - // 总公司交税 + // 总税务成本 totalCompanyTax := totalRevenue * companyTaxRate - // 总平台收入税 + // 总提现收税 totalTaxIncomeBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). - Where("del_state = ? AND tax_status = ?", globalkey.DelStateNo, 2) + Where("tax_status = ?", 2) totalTaxIncome, err := l.svcCtx.AgentWithdrawalTaxModel.FindSum(l.ctx, totalTaxIncomeBuilder, "tax_amount") if err != nil { return stats, err } + // 总API调用成本 + totalApiCost := 0.0 + if l.svcCtx.TianyuanapiCallLogService != nil { + totalApiStats, err := l.svcCtx.TianyuanapiCallLogService.GetStatistics(l.ctx, service.StatisticsFilter{}) + if err != nil { + logx.Errorf("获取总API调用成本失败: %v", err) + } else { + totalApiCost = totalApiStats.TotalCost + } + } // 总利润 - stats.TotalProfit = totalRevenue - totalCommission - totalRebate - totalCompanyTax + totalTaxIncome + stats.TotalProfit = totalRevenue - totalCommission - totalRebate - totalCompanyTax - totalApiCost + totalTaxIncome if totalRevenue > 0 { stats.TotalProfitRate = stats.TotalProfit / totalRevenue * 100 } + // 总计明细 + stats.TotalDetail = types.AdminProfitDetail{ + Revenue: totalRevenue, + Commission: totalCommission, + Rebate: totalRebate, + CompanyTax: totalCompanyTax, + ApiCost: totalApiCost, + TaxIncome: totalTaxIncome, + Profit: stats.TotalProfit, + ProfitRate: stats.TotalProfitRate, + } return stats, nil } diff --git a/app/main/api/internal/logic/agent/checkorderagentlogic.go b/app/main/api/internal/logic/agent/checkorderagentlogic.go new file mode 100644 index 0000000..96aba87 --- /dev/null +++ b/app/main/api/internal/logic/agent/checkorderagentlogic.go @@ -0,0 +1,46 @@ +package agent + +import ( + "context" + + "ycc-server/app/main/api/internal/logic/query" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + + "github.com/pkg/errors" + "ycc-server/common/ctxdata" + "ycc-server/common/xerr" + "github.com/zeromicro/go-zero/core/logx" +) + +type CheckOrderAgentLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCheckOrderAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckOrderAgentLogic { + return &CheckOrderAgentLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CheckOrderAgentLogic) CheckOrderAgent(req *types.CheckOrderAgentReq) (resp *types.CheckOrderAgentResp, err error) { + // 获取当前用户ID + userId, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err) + } + + // 检查订单是否属于当前代理推广 + isAgent, err := query.IsOrderAgent(l.ctx, l.svcCtx, userId, req.OrderId) + if err != nil { + return nil, err + } + + return &types.CheckOrderAgentResp{ + IsAgentOrder: isAgent, + }, nil +} diff --git a/app/main/api/internal/logic/query/querysingletestlogic.go b/app/main/api/internal/logic/query/querysingletestlogic.go index d16a718..73f9a25 100644 --- a/app/main/api/internal/logic/query/querysingletestlogic.go +++ b/app/main/api/internal/logic/query/querysingletestlogic.go @@ -36,7 +36,7 @@ func (l *QuerySingleTestLogic) QuerySingleTest(req *types.QuerySingleTestReq) (r if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 序列化参数失败 : %d", err) } - apiResp, err := l.svcCtx.ApiRequestService.PreprocessRequestApi(marshalParams, req.Api) + apiResp, err := l.svcCtx.ApiRequestService.PreprocessRequestApi(l.ctx, marshalParams, req.Api) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 获取接口失败 : %d", err) } diff --git a/app/main/api/internal/queue/paySuccessNotify.go b/app/main/api/internal/queue/paySuccessNotify.go index 27d60de..bc42276 100644 --- a/app/main/api/internal/queue/paySuccessNotify.go +++ b/app/main/api/internal/queue/paySuccessNotify.go @@ -158,7 +158,7 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq. encryptData = encryptedEmptyData } else { // 正常模式:调用API请求服务 - combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id) + combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(ctx, decryptData, product.Id) if err != nil { return l.handleError(ctx, err, order, query) } diff --git a/app/main/api/internal/service/apirequestService.go b/app/main/api/internal/service/apirequestService.go index 5afeb61..aa449b7 100644 --- a/app/main/api/internal/service/apirequestService.go +++ b/app/main/api/internal/service/apirequestService.go @@ -32,6 +32,61 @@ func generateAuthDateRange() string { return fmt.Sprintf("%s-%s", start, end) } +// callTianyuanApiWithLog 调用天元API并记录日志 +func (a *ApiRequestService) callTianyuanApiWithLog(ctx context.Context, featureID, apiID string, params map[string]interface{}) (*tianyuanapi.Response, error) { + startTime := time.Now() + resp, err := a.tianyuanapi.CallInterface(apiID, params) + responseTime := time.Since(startTime).Milliseconds() + + // 如果没有提供featureID,尝试从缓存中获取 + if featureID == "" { + a.apiFeatureMapMutex.RLock() + featureID = a.apiFeatureMapCache[apiID] + a.apiFeatureMapMutex.RUnlock() + } + + // 构建调用记录选项 + callStatus := int64(0) // 默认失败 + errorCode := "" + errorMessage := "" + responseData := interface{}(nil) + transactionID := "" + + if err != nil { + // 调用失败 + errorMessage = err.Error() + // 尝试从错误信息中提取错误码 + if code := tianyuanapi.GetCodeByError(err); code != -1 { + errorCode = fmt.Sprintf("%d", code) + } + } else { + // 调用成功 + callStatus = 1 + responseData = resp.Data + transactionID = resp.TransactionID + } + + // 异步记录调用日志(避免影响主流程) + go func() { + logOpts := CallLogOptions{ + FeatureID: featureID, + ApiID: apiID, + CallStatus: callStatus, + ResponseTime: responseTime, + ErrorCode: errorCode, + ErrorMessage: errorMessage, + RequestParams: params, + ResponseData: responseData, + TransactionID: transactionID, + } + if recordErr := a.tianyuanapiCallLogService.RecordCall(context.Background(), logOpts); recordErr != nil { + logx.Errorf("记录天元API调用日志失败,api_id=%s, err=%v", apiID, recordErr) + } + }() + + return resp, err +} + type ApiRequestService struct { config config.Config featureModel model.FeatureModel @@ -39,16 +94,22 @@ type ApiRequestService struct { userFeatureWhitelistModel model.UserFeatureWhitelistModel whitelistService *WhitelistService tianyuanapi *tianyuanapi.Client + tianyuanapiCallLogService *TianyuanapiCallLogService + apiFeatureMapCache map[string]string // apiID -> featureID 缓存 + apiFeatureMapMutex sync.RWMutex } // NewApiRequestService 是一个构造函数,用于初始化 ApiRequestService -func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, userFeatureWhitelistModel model.UserFeatureWhitelistModel, tianyuanapi *tianyuanapi.Client) *ApiRequestService { +func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, userFeatureWhitelistModel model.UserFeatureWhitelistModel, tianyuanapi *tianyuanapi.Client, tianyuanapiCallLogService *TianyuanapiCallLogService, whitelistService *WhitelistService) *ApiRequestService { return &ApiRequestService{ config: c, featureModel: featureModel, productFeatureModel: productFeatureModel, userFeatureWhitelistModel: userFeatureWhitelistModel, tianyuanapi: tianyuanapi, + tianyuanapiCallLogService: tianyuanapiCallLogService, + whitelistService: whitelistService, + apiFeatureMapCache: make(map[string]string), } } @@ -61,15 +122,21 @@ type APIResponseData struct { } // ProcessRequests 处理请求 -func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([]byte, error) { - var ctx, cancel = context.WithCancel(context.Background()) +func (a *ApiRequestService) ProcessRequests(ctx context.Context, params []byte, productID string) ([]byte, error) { + var cancel context.CancelFunc + ctx, cancel = context.WithCancel(ctx) defer cancel() // 从params中提取id_card,用于白名单检查 idCard := gjson.GetBytes(params, "id_card").String() // 查询白名单(如果提供了id_card),集中由 WhitelistService 处理 - whitelistedFeatureApiIds, _ := a.whitelistService.GetWhitelistedFeatureApisByIdCard(ctx, idCard) + var whitelistedFeatureApiIds map[string]bool + if a.whitelistService != nil { + whitelistedFeatureApiIds, _ = a.whitelistService.GetWhitelistedFeatureApisByIdCard(ctx, idCard) + } else { + whitelistedFeatureApiIds = make(map[string]bool) + } build := a.productFeatureModel.SelectBuilder().Where(squirrel.Eq{ "product_id": productID, @@ -95,6 +162,13 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([] if len(featureList) == 0 { return nil, errors.New("处理请求错误,产品无对应接口功能") } + + // 缓存apiID到featureID的映射关系,供后续调用记录使用 + a.apiFeatureMapMutex.Lock() + for _, feature := range featureList { + a.apiFeatureMapCache[feature.ApiId] = feature.Id + } + a.apiFeatureMapMutex.Unlock() var ( wg sync.WaitGroup resultsCh = make(chan APIResponseData, len(featureList)) @@ -140,7 +214,7 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([] tryCount := 0 for { tryCount++ - resp, preprocessErr = a.PreprocessRequestApi(params, feature.ApiId) + resp, preprocessErr = a.PreprocessRequestApi(ctx, params, feature.ApiId) if preprocessErr == nil { break } @@ -197,7 +271,7 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([] } // ------------------------------------请求处理器-------------------------- -var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, error){ +var requestProcessors = map[string]func(*ApiRequestService, context.Context, []byte) ([]byte, error){ "PersonEnterprisePro": (*ApiRequestService).ProcessPersonEnterpriseProRequest, "BehaviorRiskScan": (*ApiRequestService).ProcessBehaviorRiskScanRequest, "YYSYBE08": (*ApiRequestService).ProcessYYSYBE08Request, @@ -232,16 +306,16 @@ var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, err } // PreprocessRequestApi 调用指定的请求处理函数 -func (a *ApiRequestService) PreprocessRequestApi(params []byte, apiID string) ([]byte, error) { +func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, params []byte, apiID string) ([]byte, error) { if processor, exists := requestProcessors[apiID]; exists { - return processor(a, params) // 调用 ApiRequestService 方法 + return processor(a, ctx, params) // 调用 ApiRequestService 方法 } return nil, errors.New("api请求, 未找到相应的处理程序") } // PersonEnterprisePro 人企业关系加强版 -func (a *ApiRequestService) ProcessPersonEnterpriseProRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessPersonEnterpriseProRequest(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") // 设置最大调用次数上限 maxApiCalls := 20 // 允许最多查询20个企业 @@ -250,7 +324,7 @@ func (a *ApiRequestService) ProcessPersonEnterpriseProRequest(params []byte) ([] return nil, errors.New("api请求, PersonEnterprisePro, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("QYGLB4C0", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "QYGLB4C0", map[string]interface{}{ "id_card": idCard.String(), }) if err != nil { @@ -452,7 +526,7 @@ func (a *ApiRequestService) ProcessPersonEnterpriseProRequest(params []byte) ([] } // 调用QYGL8271接口获取企业涉诉信息 - lawsuitResp, err := a.tianyuanapi.CallInterface("QYGL8271", map[string]interface{}{ + lawsuitResp, err := a.callTianyuanApiWithLog(ctx, "", "QYGL8271", map[string]interface{}{ "ent_name": orgName.String(), "ent_code": creditCode.String(), "auth_date": generateAuthDateRange(), @@ -576,14 +650,14 @@ func (a *ApiRequestService) ProcessPersonEnterpriseProRequest(params []byte) ([] } // ProcesFLXG0V4BRequest 个人司法涉诉(详版) -func (a *ApiRequestService) ProcessFLXG0V4BRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessFLXG0V4BRequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") if !name.Exists() || !idCard.Exists() { return nil, errors.New("api请求, FLXG0V4B, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("FLXG0V4B", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "FLXG0V4B", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "auth_date": generateAuthDateRange(), @@ -599,13 +673,13 @@ func (a *ApiRequestService) ProcessFLXG0V4BRequest(params []byte) ([]byte, error } // ProcessFLXG0687Request 反诈反赌核验 -func (a *ApiRequestService) ProcessFLXG0687Request(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessFLXG0687Request(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") if !idCard.Exists() { return nil, errors.New("api请求, FLXG0687, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("FLXG0687", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "FLXG0687", map[string]interface{}{ "id_card": idCard.String(), }) @@ -627,7 +701,7 @@ func (a *ApiRequestService) ProcessFLXG0687Request(params []byte) ([]byte, error } // ProcessFLXG3D56Request 违约失信 -func (a *ApiRequestService) ProcessFLXG3D56Request(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessFLXG3D56Request(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -635,7 +709,7 @@ func (a *ApiRequestService) ProcessFLXG3D56Request(params []byte) ([]byte, error return nil, errors.New("api请求, FLXG3D56, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("FLXG3D56", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "FLXG3D56", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -682,14 +756,14 @@ func (a *ApiRequestService) ProcessFLXG3D56Request(params []byte) ([]byte, error } // ProcessIVYZ5733Request 婚姻状况 -func (a *ApiRequestService) ProcessIVYZ5733Request(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessIVYZ5733Request(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") name := gjson.GetBytes(params, "name") if !idCard.Exists() || !name.Exists() { return nil, errors.New("api请求, IVYZ5733, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("IVYZ5733", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "IVYZ5733", map[string]interface{}{ "id_card": idCard.String(), "name": name.String(), }) @@ -738,14 +812,14 @@ func (a *ApiRequestService) ProcessIVYZ5733Request(params []byte) ([]byte, error } // ProcessIVYZ9A2BRequest 学历查询 -func (a *ApiRequestService) ProcessIVYZ9A2BRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessIVYZ9A2BRequest(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") name := gjson.GetBytes(params, "name") if !idCard.Exists() || !name.Exists() { return nil, errors.New("api请求, IVYZ9A2B, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("IVYZ9A2B", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "IVYZ9A2B", map[string]interface{}{ "id_card": idCard.String(), "name": name.String(), }) @@ -809,14 +883,14 @@ func (a *ApiRequestService) ProcessIVYZ9A2BRequest(params []byte) ([]byte, error } // ProcessYYSYBE08Request 二要素 -func (a *ApiRequestService) ProcessYYSYBE08Request(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessYYSYBE08Request(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") if !name.Exists() || !idCard.Exists() { return nil, errors.New("api请求, YYSYBE08, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("YYSYBE08", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "YYSYBE08", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), }) @@ -850,7 +924,7 @@ func (a *ApiRequestService) ProcessYYSYBE08Request(params []byte) ([]byte, error } // ProcessJRZQ0A03Request 借贷申请 -func (a *ApiRequestService) ProcessJRZQ0A03Request(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessJRZQ0A03Request(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -858,7 +932,7 @@ func (a *ApiRequestService) ProcessJRZQ0A03Request(params []byte) ([]byte, error return nil, errors.New("api请求, JRZQ0A03, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("JRZQ0A03", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "JRZQ0A03", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -907,7 +981,7 @@ func (a *ApiRequestService) ProcessJRZQ0A03Request(params []byte) ([]byte, error } // ProcessJRZQ8203Request 借贷行为 -func (a *ApiRequestService) ProcessJRZQ8203Request(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessJRZQ8203Request(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -915,7 +989,7 @@ func (a *ApiRequestService) ProcessJRZQ8203Request(params []byte) ([]byte, error return nil, errors.New("api请求, JRZQ8203, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("JRZQ8203", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "JRZQ8203", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -964,7 +1038,7 @@ func (a *ApiRequestService) ProcessJRZQ8203Request(params []byte) ([]byte, error } // ProcessJRZQ4AA8Request 还款压力 -func (a *ApiRequestService) ProcessJRZQ4AA8Request(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessJRZQ4AA8Request(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") name := gjson.GetBytes(params, "name") mobile := gjson.GetBytes(params, "mobile") @@ -972,7 +1046,7 @@ func (a *ApiRequestService) ProcessJRZQ4AA8Request(params []byte) ([]byte, error return nil, errors.New("api请求, JRZQ4AA8, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("JRZQ4AA8", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "JRZQ4AA8", map[string]interface{}{ "id_card": idCard.String(), "name": name.String(), "mobile_no": mobile.String(), @@ -1013,7 +1087,7 @@ func (a *ApiRequestService) ProcessJRZQ4AA8Request(params []byte) ([]byte, error } // ProcessQYGL8271Request 企业涉诉 -func (a *ApiRequestService) ProcessQYGL8271Request(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessQYGL8271Request(ctx context.Context, params []byte) ([]byte, error) { entName := gjson.GetBytes(params, "ent_name") entCode := gjson.GetBytes(params, "ent_code") @@ -1021,7 +1095,7 @@ func (a *ApiRequestService) ProcessQYGL8271Request(params []byte) ([]byte, error return nil, errors.New("api请求, QYGL8271, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("QYGL8271", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "QYGL8271", map[string]interface{}{ "ent_name": entName.String(), "ent_code": entCode.String(), }) @@ -1073,13 +1147,13 @@ func (a *ApiRequestService) ProcessQYGL8271Request(params []byte) ([]byte, error } // ProcessQYGL6F2DRequest 人企关联 -func (a *ApiRequestService) ProcessQYGL6F2DRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessQYGL6F2DRequest(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") if !idCard.Exists() { return nil, errors.New("api请求, QYGL6F2D, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("QYGL6F2D", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "QYGL6F2D", map[string]interface{}{ "id_card": idCard.String(), }) @@ -1114,13 +1188,13 @@ func (a *ApiRequestService) ProcessQYGL6F2DRequest(params []byte) ([]byte, error } // ProcessQCXG7A2BRequest 名下车辆 -func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessQCXG7A2BRequest(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") if !idCard.Exists() { return nil, errors.New("api请求, QCXG7A2B, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("QCXG7A2B", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "QCXG7A2B", map[string]interface{}{ "id_card": idCard.String(), }) @@ -1132,7 +1206,7 @@ func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error } // ProcessYYSY09CDRequest 三要素 -func (a *ApiRequestService) ProcessYYSY09CDRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessYYSY09CDRequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1140,7 +1214,7 @@ func (a *ApiRequestService) ProcessYYSY09CDRequest(params []byte) ([]byte, error return nil, errors.New("api请求, YYSY09CD, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("YYSY09CD", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "YYSY09CD", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -1175,7 +1249,7 @@ func (a *ApiRequestService) ProcessYYSY09CDRequest(params []byte) ([]byte, error } // ProcessBehaviorRiskScanRequest 行为风险扫描 -func (a *ApiRequestService) ProcessBehaviorRiskScanRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessBehaviorRiskScanRequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") @@ -1197,7 +1271,7 @@ func (a *ApiRequestService) ProcessBehaviorRiskScanRequest(params []byte) ([]byt // 反赌反诈 go func() { defer wg.Done() - respBytes, err := a.ProcessFLXG0687Request(params) + respBytes, err := a.ProcessFLXG0687Request(ctx, params) results <- apiResult{name: "anti_fraud_gaming", data: respBytes, err: err} }() @@ -1242,7 +1316,7 @@ func (a *ApiRequestService) ProcessBehaviorRiskScanRequest(params []byte) ([]byt } // ProcessDWBG8B4DRequest 谛听多维报告 -func (a *ApiRequestService) ProcessDWBG8B4DRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessDWBG8B4DRequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1251,7 +1325,7 @@ func (a *ApiRequestService) ProcessDWBG8B4DRequest(params []byte) ([]byte, error return nil, errors.New("api请求, DWBG8B4D, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("DWBG8B4D", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "DWBG8B4D", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -1267,7 +1341,7 @@ func (a *ApiRequestService) ProcessDWBG8B4DRequest(params []byte) ([]byte, error } // ProcessDWBG6A2CRequest 司南报告服务 -func (a *ApiRequestService) ProcessDWBG6A2CRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessDWBG6A2CRequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1276,7 +1350,7 @@ func (a *ApiRequestService) ProcessDWBG6A2CRequest(params []byte) ([]byte, error return nil, errors.New("api请求, DWBG6A2C, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("DWBG6A2C", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "DWBG6A2C", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -1292,7 +1366,7 @@ func (a *ApiRequestService) ProcessDWBG6A2CRequest(params []byte) ([]byte, error } // ProcessJRZQ4B6CRequest 探针C风险评估 -func (a *ApiRequestService) ProcessJRZQ4B6CRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessJRZQ4B6CRequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1300,7 +1374,7 @@ func (a *ApiRequestService) ProcessJRZQ4B6CRequest(params []byte) ([]byte, error return nil, errors.New("api请求, JRZQ4B6C, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("JRZQ4B6C", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "JRZQ4B6C", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -1316,7 +1390,7 @@ func (a *ApiRequestService) ProcessJRZQ4B6CRequest(params []byte) ([]byte, error } // ProcessJRZQ09J8Request 收入评估 -func (a *ApiRequestService) ProcessJRZQ09J8Request(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessJRZQ09J8Request(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1324,7 +1398,7 @@ func (a *ApiRequestService) ProcessJRZQ09J8Request(params []byte) ([]byte, error return nil, errors.New("api请求, JRZQ09J8, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("JRZQ09J8", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "JRZQ09J8", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -1340,7 +1414,7 @@ func (a *ApiRequestService) ProcessJRZQ09J8Request(params []byte) ([]byte, error } // ProcessJRZQ5E9FRequest 借选指数 -func (a *ApiRequestService) ProcessJRZQ5E9FRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessJRZQ5E9FRequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1348,7 +1422,7 @@ func (a *ApiRequestService) ProcessJRZQ5E9FRequest(params []byte) ([]byte, error return nil, errors.New("api请求, JRZQ5E9F, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("JRZQ5E9F", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "JRZQ5E9F", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -1364,13 +1438,13 @@ func (a *ApiRequestService) ProcessJRZQ5E9FRequest(params []byte) ([]byte, error } // ProcessQYGL3F8ERequest 人企关系加强版2 -func (a *ApiRequestService) ProcessQYGL3F8ERequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessQYGL3F8ERequest(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") if !idCard.Exists() { return nil, errors.New("api请求, QYGL3F8E, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("QYGL3F8E", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "QYGL3F8E", map[string]interface{}{ "id_card": idCard.String(), }) @@ -1383,14 +1457,14 @@ func (a *ApiRequestService) ProcessQYGL3F8ERequest(params []byte) ([]byte, error } // ProcessIVYZ81NCRequest 婚姻,登记时间版 -func (a *ApiRequestService) ProcessIVYZ81NCRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessIVYZ81NCRequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") if !name.Exists() || !idCard.Exists() { return nil, errors.New("api请求, IVYZ81NC, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("IVYZ81NC", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "IVYZ81NC", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), }) @@ -1404,14 +1478,14 @@ func (a *ApiRequestService) ProcessIVYZ81NCRequest(params []byte) ([]byte, error } // ProcessIVYZ7F3ARequest 学历查询版B -func (a *ApiRequestService) ProcessIVYZ7F3ARequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessIVYZ7F3ARequest(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") name := gjson.GetBytes(params, "name") if !idCard.Exists() || !name.Exists() { return nil, errors.New("api请求, IVYZ7F3A, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("IVYZ7F3A", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "IVYZ7F3A", map[string]interface{}{ "id_card": idCard.String(), "name": name.String(), "authorized": "1", @@ -1426,7 +1500,7 @@ func (a *ApiRequestService) ProcessIVYZ7F3ARequest(params []byte) ([]byte, error } // ProcessDWBG7F3ARequest 多头借贷行业风险版 -func (a *ApiRequestService) ProcessDWBG7F3ARequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessDWBG7F3ARequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1434,7 +1508,7 @@ func (a *ApiRequestService) ProcessDWBG7F3ARequest(params []byte) ([]byte, error return nil, errors.New("api请求, DWBG7F3A, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("DWBG7F3A", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "DWBG7F3A", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -1449,7 +1523,7 @@ func (a *ApiRequestService) ProcessDWBG7F3ARequest(params []byte) ([]byte, error } // ProcessJRZQ8A2DRequest 特殊名单验证B -func (a *ApiRequestService) ProcessJRZQ8A2DRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessJRZQ8A2DRequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1457,7 +1531,7 @@ func (a *ApiRequestService) ProcessJRZQ8A2DRequest(params []byte) ([]byte, error return nil, errors.New("api请求, JRZQ8A2D, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("JRZQ8A2D", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "JRZQ8A2D", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -1473,13 +1547,13 @@ func (a *ApiRequestService) ProcessJRZQ8A2DRequest(params []byte) ([]byte, error } // ProcessYYSY8B1CRequest 手机在网时长B -func (a *ApiRequestService) ProcessYYSY8B1CRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessYYSY8B1CRequest(ctx context.Context, params []byte) ([]byte, error) { mobile := gjson.GetBytes(params, "mobile") if !mobile.Exists() { return nil, errors.New("api请求, YYSY8B1C, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("YYSY8B1C", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "YYSY8B1C", map[string]interface{}{ "mobile_no": mobile.String(), }) @@ -1492,13 +1566,13 @@ func (a *ApiRequestService) ProcessYYSY8B1CRequest(params []byte) ([]byte, error } // ProcessYYSY7D3ERequest 携号转网查询 -func (a *ApiRequestService) ProcessYYSY7D3ERequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessYYSY7D3ERequest(ctx context.Context, params []byte) ([]byte, error) { mobile := gjson.GetBytes(params, "mobile") if !mobile.Exists() { return nil, errors.New("api请求, YYSY7D3E, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("YYSY7D3E", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "YYSY7D3E", map[string]interface{}{ "mobile_no": mobile.String(), }) @@ -1511,7 +1585,7 @@ func (a *ApiRequestService) ProcessYYSY7D3ERequest(params []byte) ([]byte, error } // ProcessFLXG7E8FRequest 个人司法涉诉查询 -func (a *ApiRequestService) ProcessFLXG7E8FRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessFLXG7E8FRequest(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") name := gjson.GetBytes(params, "name") mobile := gjson.GetBytes(params, "mobile") @@ -1519,7 +1593,7 @@ func (a *ApiRequestService) ProcessFLXG7E8FRequest(params []byte) ([]byte, error return nil, errors.New("api请求, FLXG7E8F, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("FLXG7E8F", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "FLXG7E8F", map[string]interface{}{ "id_card": idCard.String(), "name": name.String(), "mobile_no": mobile.String(), @@ -1534,7 +1608,7 @@ func (a *ApiRequestService) ProcessFLXG7E8FRequest(params []byte) ([]byte, error } // ProcessIVYZ8I9JRequest 互联网行为推测 -func (a *ApiRequestService) ProcessIVYZ8I9JRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessIVYZ8I9JRequest(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") name := gjson.GetBytes(params, "name") mobile := gjson.GetBytes(params, "mobile") @@ -1542,7 +1616,7 @@ func (a *ApiRequestService) ProcessIVYZ8I9JRequest(params []byte) ([]byte, error return nil, errors.New("api请求, IVYZ8I9J, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("IVYZ8I9J", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "IVYZ8I9J", map[string]interface{}{ "id_card": idCard.String(), "name": name.String(), "mobile_no": mobile.String(), @@ -1557,7 +1631,7 @@ func (a *ApiRequestService) ProcessIVYZ8I9JRequest(params []byte) ([]byte, error } // ProcessJRZQ7F1ARequest 全景雷达 -func (a *ApiRequestService) ProcessJRZQ7F1ARequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessJRZQ7F1ARequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1565,7 +1639,7 @@ func (a *ApiRequestService) ProcessJRZQ7F1ARequest(params []byte) ([]byte, error return nil, errors.New("api请求, JRZQ7F1A, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("JRZQ7F1A", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "JRZQ7F1A", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), @@ -1581,14 +1655,14 @@ func (a *ApiRequestService) ProcessJRZQ7F1ARequest(params []byte) ([]byte, error } // ProcessIVYZ3P9MRequest 学历实时查询 -func (a *ApiRequestService) ProcessIVYZ3P9MRequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessIVYZ3P9MRequest(ctx context.Context, params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") name := gjson.GetBytes(params, "name") if !idCard.Exists() || !name.Exists() { return nil, errors.New("api请求, IVYZ3P9M, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("IVYZ3P9M", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "IVYZ3P9M", map[string]interface{}{ "id_card": idCard.String(), "name": name.String(), }) @@ -1601,7 +1675,7 @@ func (a *ApiRequestService) ProcessIVYZ3P9MRequest(params []byte) ([]byte, error } // ProcessJRZQ6F2ARequest 借贷申请 -func (a *ApiRequestService) ProcessJRZQ6F2ARequest(params []byte) ([]byte, error) { +func (a *ApiRequestService) ProcessJRZQ6F2ARequest(ctx context.Context, params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") idCard := gjson.GetBytes(params, "id_card") mobile := gjson.GetBytes(params, "mobile") @@ -1609,7 +1683,7 @@ func (a *ApiRequestService) ProcessJRZQ6F2ARequest(params []byte) ([]byte, error return nil, errors.New("api请求, JRZQ6F2A, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("JRZQ6F2A", map[string]interface{}{ + resp, err := a.callTianyuanApiWithLog(ctx, "", "JRZQ6F2A", map[string]interface{}{ "name": name.String(), "id_card": idCard.String(), "mobile_no": mobile.String(), diff --git a/app/main/api/internal/service/tianyuanapiCallLogService.go b/app/main/api/internal/service/tianyuanapiCallLogService.go new file mode 100644 index 0000000..d8adb74 --- /dev/null +++ b/app/main/api/internal/service/tianyuanapiCallLogService.go @@ -0,0 +1,201 @@ +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 { // 只有成功才计算成本 + feature, err := s.featureModel.FindOne(ctx, opts.FeatureID) + if err == nil { + costPrice = feature.CostPrice + } else { + logx.Errorf("查询feature成本价失败,feature_id=%s, err=%v", opts.FeatureID, 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}) + } + if !filter.EndDate.IsZero() { + builder = builder.Where(squirrel.Lt{"call_time": filter.EndDate}) + } + + // 统计总调用次数 + totalCalls, err := s.tianyuanapiCallLogModel.FindCount(ctx, builder, "id") + if err != nil { + return nil, fmt.Errorf("统计总调用次数失败: %w", err) + } + + // 统计成功次数 + 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) + } + + // 统计失败次数 + failedCalls := totalCalls - successCalls + + // 统计总成本(仅成功调用) + totalCost, err := s.tianyuanapiCallLogModel.FindSum(ctx, successBuilder, "cost_price") + if err != nil { + return nil, fmt.Errorf("统计总成本失败: %w", err) + } + + return &Statistics{ + TotalCalls: totalCalls, + SuccessCalls: successCalls, + FailedCalls: failedCalls, + TotalCost: totalCost, + }, nil +} diff --git a/app/main/api/internal/service/whitelistService.go b/app/main/api/internal/service/whitelistService.go index 110b551..f027e87 100644 --- a/app/main/api/internal/service/whitelistService.go +++ b/app/main/api/internal/service/whitelistService.go @@ -134,6 +134,9 @@ func (s *WhitelistService) GetWhitelistedFeatureApisByIdCard( idCard string, ) (map[string]bool, error) { result := make(map[string]bool) + if s == nil { + return result, nil + } if idCard == "" { return result, nil } diff --git a/app/main/api/internal/svc/servicecontext.go b/app/main/api/internal/svc/servicecontext.go index bd77626..0cdf4c1 100644 --- a/app/main/api/internal/svc/servicecontext.go +++ b/app/main/api/internal/svc/servicecontext.go @@ -34,6 +34,10 @@ type ServiceContext struct { FeatureModel model.FeatureModel ProductFeatureModel model.ProductFeatureModel + // 天元API调用记录模型 + TianyuanapiCallLogModel model.TianyuanapiCallLogModel + TianyuanapiCallLogService *service.TianyuanapiCallLogService + // 白名单相关模型 UserFeatureWhitelistModel model.UserFeatureWhitelistModel WhitelistOrderModel model.WhitelistOrderModel @@ -128,6 +132,9 @@ func NewServiceContext(c config.Config) *ServiceContext { featureModel := model.NewFeatureModel(db, cacheConf) productFeatureModel := model.NewProductFeatureModel(db, cacheConf) + // ============================== 天元API调用记录模型 ============================== + tianyuanapiCallLogModel := model.NewTianyuanapiCallLogModel(db, cacheConf) + // ============================== 白名单相关模型 ============================== userFeatureWhitelistModel := model.NewUserFeatureWhitelistModel(db, cacheConf) whitelistOrderModel := model.NewWhitelistOrderModel(db, cacheConf) @@ -196,8 +203,10 @@ func NewServiceContext(c config.Config) *ServiceContext { alipayService := service.NewAliPayService(c) wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey) applePayService := service.NewApplePayService(c) - apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, userFeatureWhitelistModel, tianyuanapi) + tianyuanapiCallLogService := service.NewTianyuanapiCallLogService(tianyuanapiCallLogModel, featureModel) + // 注意:whitelistService 需要在 apiRequestService 之前创建,因为 apiRequestService 依赖它 whitelistService := service.NewWhitelistService(c, userFeatureWhitelistModel, whitelistOrderModel, whitelistOrderItemModel, queryModel, featureModel) + apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, userFeatureWhitelistModel, tianyuanapi, tianyuanapiCallLogService, whitelistService) verificationService := service.NewVerificationService(c, tianyuanapi, apiRequestService) asynqService := service.NewAsynqService(c) agentService := service.NewAgentService(c, orderModel, agentModel, agentWalletModel, diff --git a/app/main/api/internal/types/agent.go b/app/main/api/internal/types/agent.go index 11fa4dc..c60f21b 100644 --- a/app/main/api/internal/types/agent.go +++ b/app/main/api/internal/types/agent.go @@ -80,6 +80,14 @@ type CheckFeatureWhitelistStatusResp struct { DataDeleted bool `json:"data_deleted"` // 报告数据是否已删除(仅当提供了query_id时有效) } +type CheckOrderAgentReq struct { + OrderId string `form:"order_id"` // 订单ID +} + +type CheckOrderAgentResp struct { + IsAgentOrder bool `json:"is_agent_order"` // 是否是当前代理推广的订单 +} + type ConversionRateResp struct { MyConversionRate ConversionRateData `json:"my_conversion_rate"` // 我的转化率 SubordinateConversionRate ConversionRateData `json:"subordinate_conversion_rate"` // 我的下级转化率 diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go index ea15b0c..4100941 100644 --- a/app/main/api/internal/types/types.go +++ b/app/main/api/internal/types/types.go @@ -37,13 +37,27 @@ type AdminOrderStatistics struct { ChangeRate float64 `json:"change_rate"` // 变化率(百分比) } +type AdminProfitDetail struct { + Revenue float64 `json:"revenue"` // 营收 + Commission float64 `json:"commission"` // 佣金 + Rebate float64 `json:"rebate"` // 返利 + CompanyTax float64 `json:"company_tax"` // 税务成本 + ApiCost float64 `json:"api_cost"` // API调用成本 + TaxIncome float64 `json:"tax_income"` // 提现收税 + Profit float64 `json:"profit"` // 利润 + ProfitRate float64 `json:"profit_rate"` // 利润率 +} + type AdminProfitStatistics struct { - TodayProfit float64 `json:"today_profit"` // 今日利润 - MonthProfit float64 `json:"month_profit"` // 当月利润 - TotalProfit float64 `json:"total_profit"` // 总利润 - TodayProfitRate float64 `json:"today_profit_rate"` // 今日利润率 - MonthProfitRate float64 `json:"month_profit_rate"` // 当月利润率 - TotalProfitRate float64 `json:"total_profit_rate"` // 总利润率 + TodayProfit float64 `json:"today_profit"` // 今日利润 + MonthProfit float64 `json:"month_profit"` // 当月利润 + TotalProfit float64 `json:"total_profit"` // 总利润 + TodayProfitRate float64 `json:"today_profit_rate"` // 今日利润率 + MonthProfitRate float64 `json:"month_profit_rate"` // 当月利润率 + TotalProfitRate float64 `json:"total_profit_rate"` // 总利润率 + TodayDetail AdminProfitDetail `json:"today_detail"` + MonthDetail AdminProfitDetail `json:"month_detail"` + TotalDetail AdminProfitDetail `json:"total_detail"` } type AdminQueryItem struct { diff --git a/app/main/model/featureModel_gen.go b/app/main/model/featureModel_gen.go index 430e5a1..e78ef9a 100644 --- a/app/main/model/featureModel_gen.go +++ b/app/main/model/featureModel_gen.go @@ -67,6 +67,7 @@ type ( ApiId string `db:"api_id"` // API标识 Name string `db:"name"` // 描述 WhitelistPrice float64 `db:"whitelist_price"` // 白名单屏蔽价格(单位:元) + CostPrice float64 `db:"cost_price"` // 天远API调用成本价(单位:元) } ) @@ -83,11 +84,11 @@ func (m *defaultFeatureModel) Insert(ctx context.Context, session sqlx.Session, yccFeatureApiIdKey := fmt.Sprintf("%s%v", cacheYccFeatureApiIdPrefix, data.ApiId) yccFeatureIdKey := fmt.Sprintf("%s%v", cacheYccFeatureIdPrefix, data.Id) return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { - query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?)", m.table, featureRowsExpectAutoSet) + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?)", m.table, featureRowsExpectAutoSet) if session != nil { - return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name, data.WhitelistPrice) + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name, data.WhitelistPrice, data.CostPrice) } - return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name, data.WhitelistPrice) + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name, data.WhitelistPrice, data.CostPrice) }, yccFeatureApiIdKey, yccFeatureIdKey) } func (m *defaultFeatureModel) insertUUID(data *Feature) { @@ -154,9 +155,9 @@ func (m *defaultFeatureModel) Update(ctx context.Context, session sqlx.Session, return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, featureRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.Id) + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.CostPrice, newData.Id) } - return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.Id) + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.CostPrice, newData.Id) }, yccFeatureApiIdKey, yccFeatureIdKey) } @@ -177,9 +178,9 @@ func (m *defaultFeatureModel) UpdateWithVersion(ctx context.Context, session sql sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, featureRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.Id, oldVersion) + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.CostPrice, newData.Id, oldVersion) } - return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.Id, oldVersion) + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.CostPrice, newData.Id, oldVersion) }, yccFeatureApiIdKey, yccFeatureIdKey) if err != nil { return err diff --git a/app/main/model/tianyuanapiCallLogModel.go b/app/main/model/tianyuanapiCallLogModel.go new file mode 100644 index 0000000..8f103a4 --- /dev/null +++ b/app/main/model/tianyuanapiCallLogModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ TianyuanapiCallLogModel = (*customTianyuanapiCallLogModel)(nil) + +type ( + // TianyuanapiCallLogModel is an interface to be customized, add more methods here, + // and implement the added methods in customTianyuanapiCallLogModel. + TianyuanapiCallLogModel interface { + tianyuanapiCallLogModel + } + + customTianyuanapiCallLogModel struct { + *defaultTianyuanapiCallLogModel + } +) + +// NewTianyuanapiCallLogModel returns a model for the database table. +func NewTianyuanapiCallLogModel(conn sqlx.SqlConn, c cache.CacheConf) TianyuanapiCallLogModel { + return &customTianyuanapiCallLogModel{ + defaultTianyuanapiCallLogModel: newTianyuanapiCallLogModel(conn, c), + } +} diff --git a/app/main/model/tianyuanapiCallLogModel_gen.go b/app/main/model/tianyuanapiCallLogModel_gen.go new file mode 100644 index 0000000..b1ca825 --- /dev/null +++ b/app/main/model/tianyuanapiCallLogModel_gen.go @@ -0,0 +1,398 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "reflect" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" + "ycc-server/common/globalkey" +) + +var ( + tianyuanapiCallLogFieldNames = builder.RawFieldNames(&TianyuanapiCallLog{}) + tianyuanapiCallLogRows = strings.Join(tianyuanapiCallLogFieldNames, ",") + tianyuanapiCallLogRowsExpectAutoSet = strings.Join(stringx.Remove(tianyuanapiCallLogFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + tianyuanapiCallLogRowsWithPlaceHolder = strings.Join(stringx.Remove(tianyuanapiCallLogFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheYccTianyuanapiCallLogIdPrefix = "cache:ycc:tianyuanapiCallLog:id:" +) + +type ( + tianyuanapiCallLogModel interface { + Insert(ctx context.Context, session sqlx.Session, data *TianyuanapiCallLog) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*TianyuanapiCallLog, error) + Update(ctx context.Context, session sqlx.Session, data *TianyuanapiCallLog) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *TianyuanapiCallLog) error + Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error + SelectBuilder() squirrel.SelectBuilder + DeleteSoft(ctx context.Context, session sqlx.Session, data *TianyuanapiCallLog) error + FindSum(ctx context.Context, sumBuilder squirrel.SelectBuilder, field string) (float64, error) + FindCount(ctx context.Context, countBuilder squirrel.SelectBuilder, field string) (int64, error) + FindAll(ctx context.Context, rowBuilder squirrel.SelectBuilder, orderBy string) ([]*TianyuanapiCallLog, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*TianyuanapiCallLog, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*TianyuanapiCallLog, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*TianyuanapiCallLog, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*TianyuanapiCallLog, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultTianyuanapiCallLogModel struct { + sqlc.CachedConn + table string + } + + TianyuanapiCallLog struct { + Id int64 `db:"id"` // 主键ID + FeatureId string `db:"feature_id"` // 功能ID(关联feature表) + ApiId string `db:"api_id"` // API标识(如:YYSYBE08) + OrderId sql.NullString `db:"order_id"` // 订单ID(关联order表) + QueryId sql.NullString `db:"query_id"` // 查询ID(关联query表) + CallStatus int64 `db:"call_status"` // 调用状态:0=失败,1=成功 + CallTime time.Time `db:"call_time"` // 调用时间 + ResponseTime sql.NullInt64 `db:"response_time"` // 响应耗时(毫秒) + CostPrice float64 `db:"cost_price"` // 本次调用成本(成功时从feature.cost_price获取,失败时为0) + ErrorCode sql.NullString `db:"error_code"` // 错误码(失败时记录) + ErrorMessage sql.NullString `db:"error_message"` // 错误信息(失败时记录) + RequestParams sql.NullString `db:"request_params"` // 请求参数(JSON格式) + ResponseData sql.NullString `db:"response_data"` // 响应数据(JSON格式,仅记录关键信息) + TransactionId sql.NullString `db:"transaction_id"` // 天远API流水号 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) + } +) + +func newTianyuanapiCallLogModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultTianyuanapiCallLogModel { + return &defaultTianyuanapiCallLogModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`tianyuanapi_call_log`", + } +} + +func (m *defaultTianyuanapiCallLogModel) Insert(ctx context.Context, session sqlx.Session, data *TianyuanapiCallLog) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + m.insertUUID(data) + yccTianyuanapiCallLogIdKey := fmt.Sprintf("%s%v", cacheYccTianyuanapiCallLogIdPrefix, data.Id) + return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, tianyuanapiCallLogRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.FeatureId, data.ApiId, data.OrderId, data.QueryId, data.CallStatus, data.CallTime, data.ResponseTime, data.CostPrice, data.ErrorCode, data.ErrorMessage, data.RequestParams, data.ResponseData, data.TransactionId, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.FeatureId, data.ApiId, data.OrderId, data.QueryId, data.CallStatus, data.CallTime, data.ResponseTime, data.CostPrice, data.ErrorCode, data.ErrorMessage, data.RequestParams, data.ResponseData, data.TransactionId, data.DeleteTime, data.DelState, data.Version) + }, yccTianyuanapiCallLogIdKey) +} +func (m *defaultTianyuanapiCallLogModel) insertUUID(data *TianyuanapiCallLog) { + t := reflect.TypeOf(data).Elem() + v := reflect.ValueOf(data).Elem() + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + if sf.Tag.Get("db") == "id" { + f := v.Field(i) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + if f.String() == "" { + f.SetString(uuid.NewString()) + } + } + break + } + } +} + +func (m *defaultTianyuanapiCallLogModel) FindOne(ctx context.Context, id int64) (*TianyuanapiCallLog, error) { + yccTianyuanapiCallLogIdKey := fmt.Sprintf("%s%v", cacheYccTianyuanapiCallLogIdPrefix, id) + var resp TianyuanapiCallLog + err := m.QueryRowCtx(ctx, &resp, yccTianyuanapiCallLogIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", tianyuanapiCallLogRows, m.table) + return conn.QueryRowCtx(ctx, v, query, id, globalkey.DelStateNo) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultTianyuanapiCallLogModel) Update(ctx context.Context, session sqlx.Session, data *TianyuanapiCallLog) (sql.Result, error) { + yccTianyuanapiCallLogIdKey := fmt.Sprintf("%s%v", cacheYccTianyuanapiCallLogIdPrefix, data.Id) + return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, tianyuanapiCallLogRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.FeatureId, data.ApiId, data.OrderId, data.QueryId, data.CallStatus, data.CallTime, data.ResponseTime, data.CostPrice, data.ErrorCode, data.ErrorMessage, data.RequestParams, data.ResponseData, data.TransactionId, data.DeleteTime, data.DelState, data.Version, data.Id) + } + return conn.ExecCtx(ctx, query, data.FeatureId, data.ApiId, data.OrderId, data.QueryId, data.CallStatus, data.CallTime, data.ResponseTime, data.CostPrice, data.ErrorCode, data.ErrorMessage, data.RequestParams, data.ResponseData, data.TransactionId, data.DeleteTime, data.DelState, data.Version, data.Id) + }, yccTianyuanapiCallLogIdKey) +} + +func (m *defaultTianyuanapiCallLogModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *TianyuanapiCallLog) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + yccTianyuanapiCallLogIdKey := fmt.Sprintf("%s%v", cacheYccTianyuanapiCallLogIdPrefix, data.Id) + sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, tianyuanapiCallLogRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.FeatureId, data.ApiId, data.OrderId, data.QueryId, data.CallStatus, data.CallTime, data.ResponseTime, data.CostPrice, data.ErrorCode, data.ErrorMessage, data.RequestParams, data.ResponseData, data.TransactionId, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.FeatureId, data.ApiId, data.OrderId, data.QueryId, data.CallStatus, data.CallTime, data.ResponseTime, data.CostPrice, data.ErrorCode, data.ErrorMessage, data.RequestParams, data.ResponseData, data.TransactionId, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + }, yccTianyuanapiCallLogIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultTianyuanapiCallLogModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *TianyuanapiCallLog) error { + data.DelState = globalkey.DelStateYes + data.DeleteTime = sql.NullTime{Time: time.Now(), Valid: true} + if err := m.UpdateWithVersion(ctx, session, data); err != nil { + return errors.Wrapf(errors.New("delete soft failed "), "TianyuanapiCallLogModel delete err : %+v", err) + } + return nil +} + +func (m *defaultTianyuanapiCallLogModel) FindSum(ctx context.Context, builder squirrel.SelectBuilder, field string) (float64, error) { + + if len(field) == 0 { + return 0, errors.Wrapf(errors.New("FindSum Least One Field"), "FindSum Least One Field") + } + + builder = builder.Columns("IFNULL(SUM(" + field + "),0)") + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql() + if err != nil { + return 0, err + } + + var resp float64 + err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return 0, err + } +} + +func (m *defaultTianyuanapiCallLogModel) FindCount(ctx context.Context, builder squirrel.SelectBuilder, field string) (int64, error) { + + if len(field) == 0 { + return 0, errors.Wrapf(errors.New("FindCount Least One Field"), "FindCount Least One Field") + } + + builder = builder.Columns("COUNT(" + field + ")") + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql() + if err != nil { + return 0, err + } + + var resp int64 + err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return 0, err + } +} + +func (m *defaultTianyuanapiCallLogModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*TianyuanapiCallLog, error) { + + builder = builder.Columns(tianyuanapiCallLogRows) + + if orderBy == "" { + builder = builder.OrderBy("id DESC") + } else { + builder = builder.OrderBy(orderBy) + } + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql() + if err != nil { + return nil, err + } + + var resp []*TianyuanapiCallLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultTianyuanapiCallLogModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*TianyuanapiCallLog, error) { + + builder = builder.Columns(tianyuanapiCallLogRows) + + if orderBy == "" { + builder = builder.OrderBy("id DESC") + } else { + builder = builder.OrderBy(orderBy) + } + + if page < 1 { + page = 1 + } + offset := (page - 1) * pageSize + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, err + } + + var resp []*TianyuanapiCallLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultTianyuanapiCallLogModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*TianyuanapiCallLog, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(tianyuanapiCallLogRows) + + if orderBy == "" { + builder = builder.OrderBy("id DESC") + } else { + builder = builder.OrderBy(orderBy) + } + + if page < 1 { + page = 1 + } + offset := (page - 1) * pageSize + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, total, err + } + + var resp []*TianyuanapiCallLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultTianyuanapiCallLogModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*TianyuanapiCallLog, error) { + + builder = builder.Columns(tianyuanapiCallLogRows) + + if preMinId > 0 { + builder = builder.Where(" id < ? ", preMinId) + } + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id DESC").Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, err + } + + var resp []*TianyuanapiCallLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultTianyuanapiCallLogModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*TianyuanapiCallLog, error) { + + builder = builder.Columns(tianyuanapiCallLogRows) + + if preMaxId > 0 { + builder = builder.Where(" id > ? ", preMaxId) + } + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id ASC").Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, err + } + + var resp []*TianyuanapiCallLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultTianyuanapiCallLogModel) Trans(ctx context.Context, fn func(ctx context.Context, session sqlx.Session) error) error { + + return m.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error { + return fn(ctx, session) + }) + +} + +func (m *defaultTianyuanapiCallLogModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultTianyuanapiCallLogModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + yccTianyuanapiCallLogIdKey := fmt.Sprintf("%s%v", cacheYccTianyuanapiCallLogIdPrefix, id) + _, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + if session != nil { + return session.ExecCtx(ctx, query, id) + } + return conn.ExecCtx(ctx, query, id) + }, yccTianyuanapiCallLogIdKey) + return err +} +func (m *defaultTianyuanapiCallLogModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheYccTianyuanapiCallLogIdPrefix, primary) +} +func (m *defaultTianyuanapiCallLogModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", tianyuanapiCallLogRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultTianyuanapiCallLogModel) tableName() string { + return m.table +} diff --git a/deploy/script/gen_models.ps1 b/deploy/script/gen_models.ps1 index 90dde89..7a807c0 100644 --- a/deploy/script/gen_models.ps1 +++ b/deploy/script/gen_models.ps1 @@ -45,7 +45,7 @@ $tables = @( # "agent_withdrawal_tax", # "authorization_document", # "example", - # "feature" + "feature" # "alipay_from_callback" # "global_notifications", # "order", @@ -61,10 +61,11 @@ $tables = @( # "whitelist_order", # "whitelist_order_item", # "user_feature_whitelist" - "complaint_main", - "complaint_alipay", - "complaint_alipay_trade", - "complaint_manual" + # "complaint_main", + # "complaint_alipay", + # "complaint_alipay_trade", + # "complaint_manual" + # "tianyuanapi_call_log" ) # 为每个表生成模型 diff --git a/deploy/sql/Context参数优化说明.md b/deploy/sql/Context参数优化说明.md new file mode 100644 index 0000000..8d2c861 --- /dev/null +++ b/deploy/sql/Context参数优化说明.md @@ -0,0 +1,158 @@ +# Context参数优化说明 + +## 优化内容 + +将 `ctx context.Context` 从 `ProcessRequests` 方法作为参数传入,而不是在每个 `ProcessXXXRequest` 方法内部创建。 + +## 优化原因 + +1. **更好的上下文管理**:可以从上层传递 context,支持超时、取消等操作 +2. **避免重复代码**:不需要在每个方法里都写 `ctx := context.Background()` +3. **更好的可追踪性**:可以通过 context 传递请求ID、用户ID等元数据 +4. **符合Go最佳实践**:context 应该作为第一个参数传递 + +## 修改内容 + +### 1. ProcessRequests 方法签名修改 + +**修改前:** +```go +func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([]byte, error) { + var ctx, cancel = context.WithCancel(context.Background()) + defer cancel() +``` + +**修改后:** +```go +func (a *ApiRequestService) ProcessRequests(ctx context.Context, params []byte, productID string) ([]byte, error) { + var cancel context.CancelFunc + ctx, cancel = context.WithCancel(ctx) + defer cancel() +``` + +### 2. PreprocessRequestApi 方法修改 + +**修改前:** +```go +func (a *ApiRequestService) PreprocessRequestApi(params []byte, apiID string) ([]byte, error) { + if processor, exists := requestProcessors[apiID]; exists { + return processor(a, params) + } + return nil, errors.New("api请求, 未找到相应的处理程序") +} +``` + +**修改后:** +```go +func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, params []byte, apiID string) ([]byte, error) { + if processor, exists := requestProcessors[apiID]; exists { + return processor(a, ctx, params) + } + return nil, errors.New("api请求, 未找到相应的处理程序") +} +``` + +### 3. requestProcessors map 类型定义修改 + +**修改前:** +```go +var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, error){ +``` + +**修改后:** +```go +var requestProcessors = map[string]func(*ApiRequestService, context.Context, []byte) ([]byte, error){ +``` + +### 4. 所有 ProcessXXXRequest 方法签名修改 + +**修改前:** +```go +func (a *ApiRequestService) ProcessYYSYBE08Request(params []byte) ([]byte, error) { + ctx := context.Background() + // ... +} +``` + +**修改后:** +```go +func (a *ApiRequestService) ProcessYYSYBE08Request(ctx context.Context, params []byte) ([]byte, error) { + // 移除了 ctx := context.Background() + // ... +} +``` + +### 5. 错误码提取优化 + +**修改前:** +```go +if tianyuanErr, ok := err.(*tianyuanapi.Error); ok { + errorCode = fmt.Sprintf("%d", tianyuanErr.Code) +} +``` + +**修改后:** +```go +if code := tianyuanapi.GetCodeByError(err); code != -1 { + errorCode = fmt.Sprintf("%d", code) +} +``` + +## 修改的方法列表(32个) + +所有以下方法都已添加 `ctx context.Context` 作为第一个参数,并移除了内部的 `ctx := context.Background()` 声明: + +1. ProcessPersonEnterpriseProRequest +2. ProcessBehaviorRiskScanRequest +3. ProcessYYSYBE08Request +4. ProcessYYSY09CDRequest +5. ProcessFLXG0687Request +6. ProcessFLXG3D56Request +7. ProcessFLXG0V4BRequest +8. ProcessQYGL8271Request +9. ProcessIVYZ5733Request +10. ProcessIVYZ9A2BRequest +11. ProcessJRZQ0A03Request +12. ProcessQYGL6F2DRequest +13. ProcessJRZQ8203Request +14. ProcessJRZQ4AA8Request +15. ProcessQCXG7A2BRequest +16. ProcessDWBG8B4DRequest +17. ProcessDWBG6A2CRequest +18. ProcessJRZQ4B6CRequest +19. ProcessJRZQ09J8Request +20. ProcessJRZQ5E9FRequest +21. ProcessQYGL3F8ERequest +22. ProcessIVYZ81NCRequest +23. ProcessIVYZ7F3ARequest +24. ProcessDWBG7F3ARequest +25. ProcessJRZQ8A2DRequest +26. ProcessYYSY8B1CRequest +27. ProcessYYSY7D3ERequest +28. ProcessFLXG7E8FRequest +29. ProcessIVYZ8I9JRequest +30. ProcessJRZQ7F1ARequest +31. ProcessIVYZ3P9MRequest +32. ProcessJRZQ6F2ARequest + +## 调用方验证 + +调用方 `paySuccessNotify.go` 已经正确传递了 `ctx`: + +```go +combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(ctx, decryptData, product.Id) +``` + +## 优势 + +1. **统一的上下文管理**:所有API调用共享同一个 context,可以统一控制超时和取消 +2. **更好的错误追踪**:可以在 context 中传递请求ID、用户ID等信息,方便日志追踪 +3. **代码更简洁**:不需要在每个方法中重复创建 context +4. **符合Go规范**:context 作为第一个参数是 Go 的标准做法 + +## 注意事项 + +- 所有方法现在都依赖外部传入的 `ctx`,不再内部创建 +- `ProcessRequests` 方法内部仍然会创建一个带 cancel 的 context,用于控制并发请求的取消 +- 如果将来需要在 context 中传递元数据(如请求ID、用户ID),可以在调用 `ProcessRequests` 之前设置 + diff --git a/deploy/sql/generate_tianyuanapi_models.sh b/deploy/sql/generate_tianyuanapi_models.sh new file mode 100644 index 0000000..6237b95 --- /dev/null +++ b/deploy/sql/generate_tianyuanapi_models.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# 天元API调用记录表 - Model生成脚本 + +# 数据库配置(从main.yaml中提取) +DB_HOST="ycc_mysql:3306" +DB_USER="ycc" +DB_PASS="5vg67b3UNHu8" +DB_NAME="ycc" +MODEL_DIR="../../app/main/model" +TEMPLATE_DIR="./template" + +# 生成feature表(已添加cost_price字段) +echo "正在生成 feature 表Model..." +goctl model mysql datasource -url="$DB_USER:$DB_PASS@tcp($DB_HOST)/$DB_NAME" -table="feature" -dir="$MODEL_DIR" -cache=true --style=goZero --home="$TEMPLATE_DIR" + +# 生成tianyuanapi_call_log表 +echo "正在生成 tianyuanapi_call_log 表Model..." +goctl model mysql datasource -url="$DB_USER:$DB_PASS@tcp($DB_HOST)/$DB_NAME" -table="tianyuanapi_call_log" -dir="$MODEL_DIR" -cache=true --style=goZero --home="$TEMPLATE_DIR" + +echo "=========================================" +echo "Model生成完成!" +echo "=========================================" +echo "生成的文件:" +echo " 1. featureModel_gen.go (已更新)" +echo " 2. featureModel.go (已更新)" +echo " 3. tianyuanapiCallLogModel_gen.go (新建)" +echo " 4. tianyuanapiCallLogModel.go (新建)" +echo "=========================================" +echo "下一步:" +echo " 1. 检查生成的Model文件" +echo " 2. 更新svc/servicecontext.go,添加新Model的初始化" +echo " 3. 在apirequestService.go中添加调用记录逻辑" +echo "=========================================" + diff --git a/deploy/sql/tianyuanapi_cost_migration.sql b/deploy/sql/tianyuanapi_cost_migration.sql new file mode 100644 index 0000000..d4d3cdf --- /dev/null +++ b/deploy/sql/tianyuanapi_cost_migration.sql @@ -0,0 +1,64 @@ +-- ============================================ +-- Feature表 - 添加成本价字段 +-- 说明:为 feature 表添加天远API调用成本价字段 +-- 执行时间:2025-01-13 +-- ============================================ + +-- 添加成本价字段 +ALTER TABLE `feature` +ADD COLUMN `cost_price` decimal(10, 2) DEFAULT 0.00 COMMENT '天远API调用成本价(单位:元)' AFTER `whitelist_price`; + +-- ============================================ +-- 说明: +-- 1. cost_price 默认值为 0.00,表示该feature的成本价为0元 +-- 2. 后台可以在"功能管理"页面配置每个feature的调用成本 +-- 3. 成本价用于统计天远API的调用成本 +-- ============================================ + +-- ============================================ +-- 天远API调用记录表 +-- 说明:记录每次调用天远API的详细信息 +-- ============================================ + +CREATE TABLE `tianyuanapi_call_log` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `feature_id` varchar(36) NOT NULL COMMENT '功能ID(关联feature表)', + `api_id` varchar(50) NOT NULL COMMENT 'API标识(如:YYSYBE08)', + `order_id` varchar(36) DEFAULT NULL COMMENT '订单ID(关联order表)', + `query_id` varchar(36) DEFAULT NULL COMMENT '查询ID(关联query表)', + `call_status` tinyint NOT NULL DEFAULT 0 COMMENT '调用状态:0=失败,1=成功', + `call_time` datetime NOT NULL COMMENT '调用时间', + `response_time` int DEFAULT NULL COMMENT '响应耗时(毫秒)', + `cost_price` decimal(10, 2) DEFAULT 0.00 COMMENT '本次调用成本(成功时从feature.cost_price获取,失败时为0)', + `error_code` varchar(50) DEFAULT NULL COMMENT '错误码(失败时记录)', + `error_message` varchar(500) DEFAULT NULL COMMENT '错误信息(失败时记录)', + `request_params` text DEFAULT NULL COMMENT '请求参数(JSON格式)', + `response_data` text DEFAULT NULL COMMENT '响应数据(JSON格式,仅记录关键信息)', + `transaction_id` varchar(100) DEFAULT NULL COMMENT '天远API流水号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT 0 COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT 0 COMMENT '版本号(乐观锁)', + PRIMARY KEY (`id`), + KEY `idx_feature_id` (`feature_id`), + KEY `idx_api_id` (`api_id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_query_id` (`query_id`), + KEY `idx_call_status` (`call_status`), + KEY `idx_call_time` (`call_time`), + KEY `idx_feature_time` (`feature_id`, `call_time`) COMMENT '复合索引:查询某个功能在某段时间的调用记录' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '天远API调用记录表'; + +-- ============================================ +-- 说明: +-- 1. feature_id: 关联到feature表,记录是哪个接口的调用 +-- 2. api_id: 天远API的接口标识(如:YYSYBE08) +-- 3. order_id/query_id: 关联订单或查询,方便追溯 +-- 4. call_status: 调用状态,成功=1,失败=0 +-- 5. cost_price: 成本价,成功时为feature.cost_price,失败时为0 +-- 6. error_code/error_message: 失败时记录错误信息 +-- 7. request_params/response_data: 请求和响应数据(可选择性记录) +-- 8. transaction_id: 天远API返回的流水号 +-- 9. 无论成功失败都会记录,方便统计分析 +-- ============================================ \ No newline at end of file diff --git a/deploy/sql/优化后的批量修改说明.md b/deploy/sql/优化后的批量修改说明.md new file mode 100644 index 0000000..d3efabb --- /dev/null +++ b/deploy/sql/优化后的批量修改说明.md @@ -0,0 +1,183 @@ +# 优化后的批量修改说明 - 使用ctx参数 + +## 优化说明 + +根据建议,我们通过 `ProcessRequests` 方法传入 `ctx context.Context` 参数,而不是在每个方法内部创建 `context.Background()`。 + +这样的好处: +1. **符合Go最佳实践**:Context应该从上层传入 +2. **更好的控制**:可以统一控制请求超时、取消等 +3. **代码更简洁**:不需要在每个方法中重复创建context +4. **更好的追踪**:可以统一传递追踪信息 + +## 已完成的修改 + +### 1. 修改了 ProcessRequests 方法签名 +```go +func (a *ApiRequestService) ProcessRequests(ctx context.Context, params []byte, productID string) ([]byte, error) +``` +- 移除了内部的 `ctx, cancel = context.WithCancel(context.Background())` +- 直接使用外部传入的 `ctx` + +### 2. 定义了 RequestProcessor 类型 +```go +type RequestProcessor func(ctx context.Context, a *ApiRequestService, params []byte) ([]byte, error) +``` + +### 3. 更新了 requestProcessors 映射 +所有方法签名的类型都更新为接收 `context.Context` 参数 + +### 4. 修改了 PreprocessRequestApi 方法 +```go +func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, params []byte, apiID string) ([]byte, error) { + if processor, exists := requestProcessors[apiID]; exists { + return processor(ctx, a, params) // 传递ctx + } + return nil, errors.New("api请求, 未找到相应的处理程序") +} +``` + +### 5. 修改了示例方法 +- `ProcessYYSYBE08Request` ✅ - 已添加 `ctx context.Context` 参数 +- `ProcessFLXG0V4BRequest` ✅ - 已添加 `ctx context.Context` 参数 + +## 需要完成的修改 + +现在还需要修改剩余30个方法的签名,在每个方法签名的 `params` 参数前添加 `ctx context.Context` 参数。 + +### 方法1:使用批量修改脚本(推荐) + +#### Linux/Mac 环境 +```bash +cd ycc-proxy-server +chmod +x deploy/sql/批量修改方法签名.sh +./deploy/sql/批量修改方法签名.sh +``` + +这个脚本会: +1. 备份 `apirequestService.go` 文件 +2. 批量修改31个方法的签名 +3. 显示修改统计 + +### 方法2:使用IDE查找替换(Windows环境推荐) + +#### 步骤1:在IDE中打开查找替换 +打开 `apirequestService.go`,按 `Ctrl+H` 打开查找替换 + +#### 步骤2:查找并替换 +**查找内容:** +```text +func (a *ApiRequestService) Process(params []byte) ([]byte, error) +``` + +**替换为:** +```text +func (a *ApiRequestService) Process(ctx context.Context, params []byte) ([]byte, error) +``` + +**注意:** +- 这个模式会匹配所有 `Process*Request` 方法 +- 点击"全部替换"即可 + +#### 步骤3:验证替换 +替换后,检查是否所有31个方法都成功修改。如果没有修改成功,可能是因为: +1. 某些方法已经修改过了 +2. 方法签名格式有差异 + +### 方法3:逐个手动修改 + +如果批量方法不适用,可以逐个修改。需要修改的方法列表: + +#### 已修改 ✅ +1. ProcessYYSYBE08Request +2. ProcessFLXG0V4BRequest + +#### 待修改 ⏳ +1. ProcessPersonEnterpriseProRequest (第312行) +2. ProcessFLXG0687Request (第671行) +3. ProcessFLXG3D56Request (第699行) +4. ProcessIVYZ5733Request (第754行) +5. ProcessIVYZ9A2BRequest (第810行) +6. ProcessJRZQ0A03Request (第922行) +7. ProcessJRZQ8203Request (第979行) +8. ProcessJRZQ4AA8Request (第1036行) +9. ProcessQYGL8271Request (第1085行) +10. ProcessQYGL6F2DRequest (第1150行) +11. ProcessQCXG7A2BRequest (第1191行) +12. ProcessYYSY09CDRequest (第1211行) +13. ProcessBehaviorRiskScanRequest (第1247行) +14. ProcessDWBG8B4DRequest (第1314行) +15. ProcessDWBG6A2CRequest (第1339行) +16. ProcessJRZQ4B6CRequest (第1363行) +17. ProcessJRZQ09J8Request (第1387行) +18. ProcessJRZQ5E9FRequest (第1411行) +19. ProcessQYGL3F8ERequest (第1433行) +20. ProcessIVYZ81NCRequest (第1461行) +21. ProcessIVYZ7F3ARequest (第1482行) +22. ProcessDWBG7F3ARequest (第1505行) +23. ProcessJRZQ8A2DRequest (第1528行) +24. ProcessYYSY8B1CRequest (第1550行) +25. ProcessYYSY7D3ERequest (第1569行) +26. ProcessFLXG7E8FRequest (第1590行) +27. ProcessIVYZ8I9JRequest (第1613行) +28. ProcessJRZQ7F1ARequest (第1636行) +29. ProcessIVYZ3P9MRequest (第1659行) +30. ProcessJRZQ6F2ARequest (第1680行) + +每个方法需要做的修改: + +**修改前:** +```go +func (a *ApiRequestService) ProcessMethodName(params []byte) ([]byte, error) { + // 方法体 +} +``` + +**修改后:** +```go +func (a *ApiRequestService) ProcessMethodName(ctx context.Context, params []byte) ([]byte, error) { + // 方法体 +} +``` + +## 注意事项 + +1. **不需要在每个方法中添加 `ctx := context.Background()`** + - 因为现在通过参数传入 + - 使用 `ctx context.Context` 参数即可 + +2. **`callTianyuanApiWithLog` 方法已经支持传入 ctx** + - 如果第一个参数为空字符串 `""`,会自动从缓存获取featureID + - 否则使用传入的featureID + +3. **不需要修改方法内部的 API 调用** + - 因为我们已经修改了部分方法使用 `callTianyuanApiWithLog` + - 批量替换后,记得将所有 `a.tianyuanapi.CallInterface` 替换为 `a.callTianyuanApiWithLog(ctx, "",` + +## 完成后验证 + +1. **编译项目** +```bash +cd ycc-proxy-server +go build ./... +``` + +2. **检查编译错误** + - 主要是方法签名不匹配的错误 + - 如果有错误,检查哪些方法没有正确修改 + +3. **运行测试** + - 发起几个API请求 + - 检查 `tianyuanapi_call_log` 表 + - 验证日志是否正确记录 + +## 优势总结 + +使用 `ctx` 参数的方式: + +✅ **代码更简洁** - 不需要重复创建context +✅ **更好的控制** - 可以统一管理超时、取消 +✅ **符合最佳实践** - Context应该从上层传入 +✅ **便于追踪** - 可以在ctx中传递追踪信息 +✅ **便于测试** - 测试时可以传入自定义的ctx + diff --git a/deploy/sql/利润统计API成本计入说明.md b/deploy/sql/利润统计API成本计入说明.md new file mode 100644 index 0000000..31e3c8f --- /dev/null +++ b/deploy/sql/利润统计API成本计入说明.md @@ -0,0 +1,135 @@ +# 利润统计API成本计入说明 + +## 修改内容 + +在后台统计面板的利润统计部分,将API调用成本计入成本计算中。 + +## 修改位置 + +文件:`ycc-proxy-server/app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go` + +方法:`calculateProfitStatistics` + +## 修改详情 + +### 1. 今日利润计算 + +**修改前:** +```go +// 今日利润 = 营收 - 佣金 - 返利 - 税务成本 + 提现收税 +stats.TodayProfit = todayRevenue - todayCommission - todayRebate - todayCompanyTax + todayTaxIncome +``` + +**修改后:** +```go +// 今日API调用成本 +todayApiCost := 0.0 +if l.svcCtx.TianyuanapiCallLogService != nil { + todayApiStats, err := l.svcCtx.TianyuanapiCallLogService.GetStatistics(l.ctx, service.StatisticsFilter{ + StartDate: todayStart, + EndDate: todayEnd, + }) + if err != nil { + logx.Errorf("获取今日API调用成本失败: %v", err) + } else { + todayApiCost = todayApiStats.TotalCost + } +} +// 今日利润 = 营收 - 佣金 - 返利 - 税务成本 - API调用成本 + 提现收税 +stats.TodayProfit = todayRevenue - todayCommission - todayRebate - todayCompanyTax - todayApiCost + todayTaxIncome +``` + +### 2. 当月利润计算 + +**修改前:** +```go +// 当月利润 +stats.MonthProfit = monthRevenue - monthCommission - monthRebate - monthCompanyTax + monthTaxIncome +``` + +**修改后:** +```go +// 当月API调用成本 +monthApiCost := 0.0 +if l.svcCtx.TianyuanapiCallLogService != nil { + monthApiStats, err := l.svcCtx.TianyuanapiCallLogService.GetStatistics(l.ctx, service.StatisticsFilter{ + StartDate: monthStart, + EndDate: monthEnd, + }) + if err != nil { + logx.Errorf("获取当月API调用成本失败: %v", err) + } else { + monthApiCost = monthApiStats.TotalCost + } +} +// 当月利润 +stats.MonthProfit = monthRevenue - monthCommission - monthRebate - monthCompanyTax - monthApiCost + monthTaxIncome +``` + +### 3. 总利润计算 + +**修改前:** +```go +// 总利润 +stats.TotalProfit = totalRevenue - totalCommission - totalRebate - totalCompanyTax + totalTaxIncome +``` + +**修改后:** +```go +// 总API调用成本 +totalApiCost := 0.0 +if l.svcCtx.TianyuanapiCallLogService != nil { + totalApiStats, err := l.svcCtx.TianyuanapiCallLogService.GetStatistics(l.ctx, service.StatisticsFilter{}) + if err != nil { + logx.Errorf("获取总API调用成本失败: %v", err) + } else { + totalApiCost = totalApiStats.TotalCost + } +} +// 总利润 +stats.TotalProfit = totalRevenue - totalCommission - totalRebate - totalCompanyTax - totalApiCost + totalTaxIncome +``` + +## 利润计算公式 + +修改后的利润计算公式为: + +``` +利润 = 营收 - 佣金 - 返利 - 税务成本 - API调用成本 + 提现收税 +``` + +其中: +- **营收**:订单金额总和(status = 'paid') +- **佣金**:代理佣金总和(status != 3) +- **返利**:代理返利总和(status != 3) +- **税务成本**:订单金额的 6% +- **API调用成本**:天元API成功调用的成本总和(从 `tianyuanapi_call_log` 表统计) +- **提现收税**:代理提现税总和(tax_status = 2) + +## API调用成本统计说明 + +API调用成本通过 `TianyuanapiCallLogService.GetStatistics` 方法获取: + +1. **今日API调用成本**:统计 `call_time >= todayStart AND call_time < todayEnd` 的成功调用成本 +2. **当月API调用成本**:统计 `call_time >= monthStart AND call_time < monthEnd` 的成功调用成本 +3. **总API调用成本**:统计所有成功调用的成本 + +**注意**:只有成功调用(`call_status = 1`)才会计入成本,失败调用不计成本。 + +## 错误处理 + +如果获取API调用成本失败,会记录错误日志,但不会影响利润计算(API成本默认为0),确保统计功能的稳定性。 + +## 依赖项 + +- `TianyuanapiCallLogService`:需要在 `servicecontext.go` 中初始化 +- `tianyuanapi_call_log` 表:需要已创建并包含调用记录数据 + +## 验证 + +修改完成后,可以通过以下方式验证: + +1. 查看后台统计面板的利润统计数据 +2. 检查日志,确认API调用成本是否正确获取 +3. 对比修改前后的利润数据,确认API成本已正确计入 + diff --git a/deploy/sql/利润统计面板改造说明.md b/deploy/sql/利润统计面板改造说明.md new file mode 100644 index 0000000..304ec66 --- /dev/null +++ b/deploy/sql/利润统计面板改造说明.md @@ -0,0 +1,149 @@ +# 利润统计面板改造说明 + +## 改造内容 + +将利润统计从原来的卡片布局改为右侧栏面板,并展示详细的成本和收入明细。 + +## 修改文件 + +### 1. 后端API定义 + +**文件:** `ycc-proxy-server/app/main/api/desc/admin/dashboard.api` + +**修改内容:** +- 在 `AdminProfitStatistics` 中添加了三个明细字段: + - `TodayDetail AdminProfitDetail` - 今日明细 + - `MonthDetail AdminProfitDetail` - 当月明细 + - `TotalDetail AdminProfitDetail` - 总计明细 +- 新增 `AdminProfitDetail` 类型,包含: + - `Revenue` - 营收 + - `Commission` - 佣金 + - `Rebate` - 返利 + - `CompanyTax` - 税务成本 + - `ApiCost` - API调用成本 + - `TaxIncome` - 提现收税 + - `Profit` - 利润 + - `ProfitRate` - 利润率 + +### 2. 后端逻辑 + +**文件:** `ycc-proxy-server/app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go` + +**修改内容:** +- 在 `calculateProfitStatistics` 方法中,为今日、当月、总计三个时间范围都填充了明细数据 +- 明细数据包含所有成本和收入的详细金额 + +### 3. 前端类型定义 + +**文件:** `ycc-proxy-admin/apps/web-antd/src/api/dashboard/dashboard.ts` + +**修改内容:** +- 添加了 `ProfitDetail` 接口 +- 更新了 `ProfitStatistics` 接口,添加三个明细字段 + +### 4. 前端主页面 + +**文件:** `ycc-proxy-admin/apps/web-antd/src/views/dashboard/analytics/index.vue` + +**修改内容:** +- 移除了利润统计卡片(从4列布局改为3列布局) +- 改为左右布局:左侧是统计卡片和图表,右侧是利润统计面板 +- 添加了响应式布局,小屏幕下右侧面板会移到下方 + +### 5. 前端利润面板组件(新建) + +**文件:** `ycc-proxy-admin/apps/web-antd/src/views/dashboard/analytics/analytics-profit-panel.vue` + +**功能:** +- 展示利润总额和利润率 +- 通过Tab切换今日/当月/总计 +- 详细展示收入明细(营收、提现收税) +- 详细展示成本明细(代理佣金、代理返利、税务成本、API调用成本) +- 分别计算收入合计和成本合计 + +## 布局说明 + +### 桌面端(>1200px) +``` +┌─────────────────────────────────┬──────────────┐ +│ │ │ +│ 统计卡片(3列) │ 利润统计 │ +│ │ 面板 │ +│ 趋势图表 │ (固定) │ +│ │ │ +└─────────────────────────────────┴──────────────┘ +``` + +### 移动端(≤1200px) +``` +┌─────────────────────────────────┐ +│ 统计卡片(3列) │ +│ │ +│ 趋势图表 │ +│ │ +│ 利润统计面板 │ +│ (移到下方) │ +└─────────────────────────────────┘ +``` + +## 明细数据说明 + +### 收入项 +1. **营收**:订单金额总和(status = 'paid') +2. **提现收税**:代理提现税总和(tax_status = 2) + +### 成本项 +1. **代理佣金**:代理佣金总和(status != 3) +2. **代理返利**:代理返利总和(status != 3) +3. **税务成本**:订单金额的 6% +4. **API调用成本**:天元API成功调用的成本总和 + +### 利润计算 +``` +利润 = 营收 + 提现收税 - 代理佣金 - 代理返利 - 税务成本 - API调用成本 +利润率 = (利润 / 营收) × 100% +``` + +## 使用步骤 + +### 1. 生成API类型(后端) + +运行API生成脚本: +```bash +cd ycc-proxy-server +# 运行gen_api脚本生成types +``` + +### 2. 编译验证 + +```bash +# 后端编译 +cd ycc-proxy-server +go build + +# 前端编译 +cd ycc-proxy-admin +npm run build +``` + +### 3. 测试验证 + +1. 打开后台统计面板 +2. 查看右侧利润统计面板 +3. 切换今日/当月/总计Tab +4. 验证各项成本和收入明细是否正确显示 +5. 验证收入合计和成本合计计算是否正确 + +## 注意事项 + +1. **必须运行gen_api脚本**:修改了API定义后,需要运行gen_api脚本生成新的types.go文件 +2. **数据准确性**:确保API调用成本数据已正确记录在 `tianyuanapi_call_log` 表中 +3. **响应式布局**:在小屏幕设备上,右侧面板会自动移到下方,确保良好的用户体验 + +## UI特性 + +- **固定定位**:右侧面板在桌面端使用sticky定位,滚动时保持可见 +- **颜色区分**:收入用绿色(+),成本用红色(-),利润根据正负显示不同颜色 +- **Tab切换**:可以快速切换查看不同时间范围的明细 +- **合计显示**:分别显示收入合计和成本合计,方便查看 + diff --git a/deploy/sql/天元API调用记录修改说明.md b/deploy/sql/天元API调用记录修改说明.md new file mode 100644 index 0000000..16d3ab4 --- /dev/null +++ b/deploy/sql/天元API调用记录修改说明.md @@ -0,0 +1,159 @@ +# 天元API调用记录功能 - 代码修改说明 + +## 概述 +本文档说明如何修改 `apirequestService.go`,为每次天元API调用添加记录功能。 + +## 已完成的修改 + +### 1. 数据库层面 +- ✅ 创建 `tianyuanapi_cost_migration.sql` 脚本 + - 在 `feature` 表添加 `cost_price` 字段 + - 创建 `tianyuanapi_call_log` 表 + +### 2. Service层面 +- ✅ 创建 `tianyuanapiCallLogService.go` 服务 + - `RecordCall()` - 记录API调用 + - `GetStatistics()` - 获取统计信息 + +### 3. Model层面 +- ⏳ 需要执行 `generate_tianyuanapi_models.sh` 生成Model代码 + +### 4. ApiRequestService修改 +- ✅ 添加 `tianyuanapiCallLogService` 字段 +- ✅ 修改构造函数,添加 `tianyuanapiCallLogService` 参数 +- ✅ 创建 `callTianyuanApiWithLog()` 辅助方法 + +## 待完成的修改 + +### 1. 执行SQL脚本 +```bash +# 在数据库中执行迁移脚本 +mysql -u root -p ycc < ycc-proxy-server/deploy/sql/tianyuanapi_cost_migration.sql +``` + +### 2. 生成Model代码 +```bash +cd ycc-proxy-server/deploy/sql +bash generate_tianyuanapi_models.sh +``` + +### 3. 更新 servicecontext.go +在 `svc/servicecontext.go` 中添加: +```go +// 初始化Model +tianyuanapiCallLogModel := model.NewTianyuanapiCallLogModel(db, cacheConf) + +// 初始化Service +tianyuanapiCallLogService := service.NewTianyuanapiCallLogService( + tianyuanapiCallLogModel, + featureModel, +) + +// 修改ApiRequestService初始化 +apiRequestService := service.NewApiRequestService( + c, + featureModel, + productFeatureModel, + userFeatureWhitelistModel, + tianyuanapi, + tianyuanapiCallLogService, // 新增参数 +) + +// 在ServiceContext结构体中添加字段 +TianyuanapiCallLogModel model.TianyuanapiCallLogModel +TianyuanapiCallLogService *service.TianyuanapiCallLogService + +// 在返回的ServiceContext中添加 +TianyuanapiCallLogModel: tianyuanapiCallLogModel, +TianyuanapiCallLogService: tianyuanapiCallLogService, +``` + +### 4. 修改API调用方法示例 + +#### 原代码: +```go +resp, err := a.callTianyuanApiWithLog(ctx, "", "QYGLB4C0", map[string]interface{}{ + "id_card": idCard.String(), +}) +``` + +#### 修改后: +```go +resp, err := a.callTianyuanApiWithLog(ctx, featureID, "QYGLB4C0", map[string]interface{}{ + "id_card": idCard.String(), +}) +``` + +### 需要修改的方法列表(共32个) + +1. `ProcessPersonEnterpriseProRequest` - 需要修改2处 + - 第253行:`QYGLB4C0` - 主接口 + - 第455行:`QYGL8271` - 涉诉信息查询 + +2. `ProcessFLXG0V4BRequest` - 第586行:`FLXG0V4B` +3. `ProcessFLXG0687Request` - 第608行:`FLXG0687` +4. `ProcessFLXG3D56Request` - 第638行:`FLXG3D56` +5. `ProcessIVYZ5733Request` - 第692行:`IVYZ5733` +6. `ProcessIVYZ9A2BRequest` - 第748行:`IVYZ9A2B` +7. `ProcessYYSYBE08Request` - 第819行:`YYSYBE08` +8. `ProcessJRZQ0A03Request` - 第861行:`JRZQ0A03` +9. `ProcessJRZQ8203Request` - 第918行:`JRZQ8203` +10. `ProcessJRZQ4AA8Request` - 第975行:`JRZQ4AA8` +11. `ProcessQYGL8271Request` - 第1024行:`QYGL8271` +12. `ProcessQYGL6F2DRequest` - 第1082行:`QYGL6F2D` +13. `ProcessQCXG7A2BRequest` - 第1123行:`QCXG7A2B` +14. `ProcessYYSY09CDRequest` - 第1143行:`YYSY09CD` +15. `ProcessDWBG8B4DRequest` - 第1254行:`DWBG8B4D` +16. `ProcessDWBG6A2CRequest` - 第1279行:`DWBG6A2C` +17. `ProcessJRZQ4B6CRequest` - 第1303行:`JRZQ4B6C` +18. `ProcessJRZQ09J8Request` - 第1327行:`JRZQ09J8` +19. `ProcessJRZQ5E9FRequest` - 第1351行:`JRZQ5E9F` +20. `ProcessQYGL3F8ERequest` - 第1373行:`QYGL3F8E` +21. `ProcessIVYZ81NCRequest` - 第1393行:`IVYZ81NC` +22. `ProcessIVYZ7F3ARequest` - 第1414行:`IVYZ7F3A` +23. `ProcessDWBG7F3ARequest` - 第1437行:`DWBG7F3A` +24. `ProcessJRZQ8A2DRequest` - 第1460行:`JRZQ8A2D` +25. `ProcessYYSY8B1CRequest` - 第1482行:`YYSY8B1C` +26. `ProcessYYSY7D3ERequest` - 第1501行:`YYSY7D3E` +27. `ProcessFLXG7E8FRequest` - 第1522行:`FLXG7E8F` +28. `ProcessIVYZ8I9JRequest` - 第1545行:`IVYZ8I9J` +29. `ProcessJRZQ7F1ARequest` - 第1568行:`JRZQ7F1A` +30. `ProcessIVYZ3P9MRequest` - 第1591行:`IVYZ3P9M` +31. `ProcessJRZQ6F2ARequest` - 第1612行:`JRZQ6F2A` + +### 注意事项 + +1. **featureID获取**:每个方法中已经有了 `feature` 对象,直接使用 `feature.Id` 即可 +2. **context传递**:需要从方法签名中获取 `ctx context.Context` 参数 +3. **错误处理**:`callTianyuanApiWithLog` 已经包含了错误处理,无需额外处理 +4. **批量修改建议**:可以使用查找替换功能批量修改 + - 查找:`a.callTianyuanApiWithLog(ctx, "", "` + - 替换为:`a.callTianyuanApiWithLog(ctx, feature.Id, "` + +## 统计功能使用示例 + +```go +// 获取某个功能在某个时间段的调用统计 +filter := StatisticsFilter{ + FeatureID: "feature-id-123", + StartDate: time.Now().AddDate(0, 0, -30), // 30天前 + EndDate: time.Now(), +} +stats, err := ctx.TianyuanapiCallLogService.GetStatistics(context.Background(), filter) +if err == nil { + fmt.Printf("总调用次数: %d\n", stats.TotalCalls) + fmt.Printf("成功次数: %d\n", stats.SuccessCalls) + fmt.Printf("失败次数: %d\n", stats.FailedCalls) + fmt.Printf("总成本: %.2f元\n", stats.TotalCost) +} +``` + +## 验证步骤 + +1. 执行SQL脚本,检查表是否创建成功 +2. 运行Model生成脚本,检查Model文件是否生成 +3. 编译项目,检查是否有编译错误 +4. 运行项目,发起一个API请求 +5. 检查 `tianyuanapi_call_log` 表,验证记录是否正确插入 +6. 检查成本价是否正确计算(成功调用有成本,失败调用成本为0) + diff --git a/deploy/sql/天元API调用记录功能完成总结.md b/deploy/sql/天元API调用记录功能完成总结.md new file mode 100644 index 0000000..29b6909 --- /dev/null +++ b/deploy/sql/天元API调用记录功能完成总结.md @@ -0,0 +1,297 @@ +# 天元API调用记录功能 - 实施总结 + +## 功能概述 + +本功能实现了天元API调用的成本记录,包括: +1. 在 `feature` 表添加 `cost_price` 字段,存储每个接口的成本价 +2. 创建 `tianyuanapi_call_log` 表,记录每次API调用的详细信息 +3. 无论成功失败都记录,但只有成功调用才计算成本 +4. 提供统计功能,可查询时间段内的调用次数和总成本 + +## 已完成的工作 + +### 1. 数据库层面 ✅ + +**文件:** `ycc-proxy-server/deploy/sql/tianyuanapi_cost_migration.sql` + +**修改内容:** +- 在 `feature` 表添加 `cost_price` 字段(decimal(10, 2),默认0.00) +- 创建 `tianyuanapi_call_log` 表,包含以下字段: + - `feature_id`: 关联feature表 + - `api_id`: 天元API标识(如:YYSYBE08) + - `order_id`: 关联订单(可选) + - `query_id`: 关联查询(可选) + - `call_status`: 调用状态(0=失败,1=成功) + - `call_time`: 调用时间 + - `response_time`: 响应耗时(毫秒) + - `cost_price`: 本次调用成本(成功时有值,失败为0) + - `error_code`: 错误码(失败时) + - `error_message`: 错误信息(失败时) + - `request_params`: 请求参数(JSON) + - `response_data`: 响应数据(JSON,截取前1000字符) + - `transaction_id`: 天元API流水号 + +**索引设计:** +- `idx_feature_id`: 按feature_id查询 +- `idx_api_id`: 按api_id查询 +- `idx_order_id`: 按order_id查询 +- `idx_query_id`: 按query_id查询 +- `idx_call_status`: 按调用状态查询 +- `idx_call_time`: 按调用时间查询 +- `idx_feature_time`: 复合索引(feature_id + call_time) + +### 2. Service层面 ✅ + +**文件:** `ycc-proxy-server/app/main/api/internal/service/tianyuanapiCallLogService.go` + +**新增方法:** +1. `RecordCall(ctx context.Context, opts CallLogOptions) error` + - 记录API调用 + - 自动获取feature的成本价 + - 成功时记录成本,失败时成本为0 + - 异步记录,不影响主流程 + +2. `GetStatistics(ctx context.Context, filter StatisticsFilter) (*Statistics, error)` + - 获取统计信息 + - 支持按feature_id、api_id、时间范围过滤 + - 返回:总调用次数、成功次数、失败次数、总成本 + +### 3. ApiRequestService修改 ✅ + +**文件:** `ycc-proxy-server/app/main/api/internal/service/apirequestService.go` + +**新增字段:** +- `tianyuanapiCallLogService *TianyuanapiCallLogService` +- `apiFeatureMapCache map[string]string` - apiID到featureID的缓存 +- `apiFeatureMapMutex sync.RWMutex` - 缓存读写锁 + +**新增方法:** +1. `callTianyuanApiWithLog(ctx context.Context, featureID, apiID string, params map[string]interface{}) (*tianyuanapi.Response, error)` + - 调用天元API + - 记录调用开始时间 + - 计算响应耗时 + - 异步记录调用日志 + - 返回响应和错误 + +2. 在 `ProcessRequests` 方法中构建 `apiID -> featureID` 映射缓存 + +**修改的方法(示例):** +- `ProcessYYSYBE08Request` - 已修改 +- `ProcessFLXG0V4BRequest` - 已修改 + +**待修改的方法(30个):** +剩余30个方法需要将 `a.callTianyuanApiWithLog(ctx, "", "API名称", ...)` +替换为 `a.callTianyuanApiWithLog(ctx, "", "API名称", ...)` + +详细列表见:`批量替换API调用说明.md` + +### 4. ServiceContext修改 ✅ + +**文件:** `ycc-proxy-server/app/main/api/internal/svc/servicecontext.go` + +**新增模型:** +```go +TianyuanapiCallLogModel model.TianyuanapiCallLogModel +TianyuanapiCallLogService *service.TianyuanapiCallLogService +``` + +**新增初始化:** +```go +tianyuanapiCallLogModel := model.NewTianyuanapiCallLogModel(db, cacheConf) +tianyuanapiCallLogService := service.NewTianyuanapiCallLogService(tianyuanapiCallLogModel, featureModel) +``` + +**修改ApiRequestService初始化:** +```go +apiRequestService := service.NewApiRequestService( + c, + featureModel, + productFeatureModel, + userFeatureWhitelistModel, + tianyuanapi, + tianyuanapiCallLogService, // 新增参数 +) +``` + +### 5. Model生成脚本 ✅ + +**文件:** `ycc-proxy-server/deploy/sql/generate_tianyuanapi_models.sh` + +**功能:** +- 生成 `feature` 表的Model(已更新,包含cost_price字段) +- 生成 `tianyuanapi_call_log` 表的Model + +### 6. 文档 ✅ + +**已创建的文档:** +1. `天元API调用记录修改说明.md` - 详细的修改步骤和示例 +2. `批量替换API调用说明.md` - 批量修改API调用方法的指南 + +## 待完成的工作 + +### 1. 执行SQL脚本 ⏳ + +```bash +# 在数据库中执行迁移脚本 +mysql -u root -p ycc < ycc-proxy-server/deploy/sql/tianyuanapi_cost_migration.sql +``` + +### 2. 生成Model代码 ⏳ + +```bash +cd ycc-proxy-server/deploy/sql +bash generate_tianyuanapi_models.sh +``` + +这将生成以下文件: +- `app/main/model/featureModel_gen.go` (已更新,包含cost_price字段) +- `app/main/model/featureModel.go` (已更新) +- `app/main/model/tianyuanapiCallLogModel_gen.go` (新建) +- `app/main/model/tianyuanapiCallLogModel.go` (新建) + +### 3. 批量修改API调用方法 ⏳ + +**需要修改30个方法,将:** +```go +resp, err := a.callTianyuanApiWithLog(ctx, "", "API名称", map[string]interface{}{ +``` + +**替换为:** +```go +ctx := context.Background() +resp, err := a.callTianyuanApiWithLog(ctx, "", "API名称", map[string]interface{}{ +``` + +**参考文档:** `批量替换API调用说明.md` + +**建议使用IDE批量替换功能:** +1. 打开 `apirequestService.go` +2. 按 `Ctrl+H` 打开查找替换 +3. 查找:`a.callTianyuanApiWithLog(ctx, "", "` +4. 替换:`a.callTianyuanApiWithLog(ctx, "", "` +5. 全部替换 + +**然后手动在每个方法开头添加:** +```go +ctx := context.Background() +``` + +### 4. 编译验证 ⏳ + +```bash +cd ycc-proxy-server +go build ./... +``` + +检查是否有编译错误。 + +### 5. 测试验证 ⏳ + +1. 运行项目 +2. 发起几个API请求(成功和失败各几次) +3. 检查 `tianyuanapi_call_log` 表,验证: + - 记录是否正确插入 + - 成功调用有正确的成本价 + - 失败调用成本为0 + - 响应耗时是否记录 + - 错误信息是否正确 + +### 6. 后台配置 ⏳ + +在后台"功能管理"页面,为每个feature配置成本价: +1. 进入"功能管理" +2. 编辑某个功能 +3. 设置"天元API调用成本价"字段 +4. 保存 + +## 统计功能使用示例 + +```go +// 获取某个功能在某个时间段的调用统计 +filter := StatisticsFilter{ + FeatureID: "feature-id-123", + StartDate: time.Now().AddDate(0, 0, -30), // 30天前 + EndDate: time.Now(), +} +stats, err := ctx.TianyuanapiCallLogService.GetStatistics(context.Background(), filter) +if err == nil { + fmt.Printf("总调用次数: %d\n", stats.TotalCalls) + fmt.Printf("成功次数: %d\n", stats.SuccessCalls) + fmt.Printf("失败次数: %d\n", stats.FailedCalls) + fmt.Printf("总成本: %.2f元\n", stats.TotalCost) +} +``` + +## SQL查询示例 + +### 查询某个接口的调用记录 +```sql +SELECT * FROM tianyuanapi_call_log +WHERE api_id = 'YYSYBE08' +ORDER BY call_time DESC +LIMIT 100; +``` + +### 统计某天所有接口的调用情况 +```sql +SELECT + api_id, + COUNT(*) as total_calls, + SUM(CASE WHEN call_status = 1 THEN 1 ELSE 0 END) as success_calls, + SUM(CASE WHEN call_status = 0 THEN 1 ELSE 0 END) as failed_calls, + SUM(cost_price) as total_cost +FROM tianyuanapi_call_log +WHERE DATE(call_time) = CURDATE() +GROUP BY api_id; +``` + +### 查询成功率最低的接口 +```sql +SELECT + api_id, + COUNT(*) as total_calls, + SUM(CASE WHEN call_status = 1 THEN 1 ELSE 0 END) as success_calls, + (SUM(CASE WHEN call_status = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(*)) as success_rate +FROM tianyuanapi_call_log +WHERE call_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) +GROUP BY api_id +ORDER BY success_rate ASC +LIMIT 10; +``` + +## 注意事项 + +1. **成本价配置**:需要在后台为每个feature配置成本价,否则所有调用的成本都是0 +2. **性能考虑**:调用日志采用异步记录,不影响API响应速度 +3. **数据量控制**:response_data只记录前1000字符,避免存储过大 +4. **缓存机制**:apiID到featureID的映射在 `ProcessRequests` 时构建,避免频繁查询数据库 +5. **失败不收费**:根据需求,调用失败时天元API不收费,所以cost_price为0 + +## 后续优化建议 + +1. **定时清理**:可以添加定时任务,定期清理超过一定时间的日志数据(如90天) +2. **成本预警**:可以添加监控,当某个时间段的总成本超过阈值时发送告警 +3. **成功率监控**:监控各接口的成功率,低于阈值时告警 +4. **性能分析**:分析响应时间,找出性能瓶颈 +5. **数据归档**:将历史日志归档到其他存储,避免主表过大 + +## 文件清单 + +### 新增文件 +1. `deploy/sql/tianyuanapi_cost_migration.sql` - 数据库迁移脚本 +2. `deploy/sql/generate_tianyuanapi_models.sh` - Model生成脚本 +3. `app/main/api/internal/service/tianyuanapiCallLogService.go` - 调用记录服务 +4. `deploy/sql/天元API调用记录修改说明.md` - 详细修改说明 +5. `deploy/sql/批量替换API调用说明.md` - 批量替换指南 +6. `deploy/sql/天元API调用记录功能完成总结.md` - 本文档 + +### 修改文件 +1. `app/main/api/internal/service/apirequestService.go` - 添加调用记录逻辑 +2. `app/main/api/internal/svc/servicecontext.go` - 添加新Model和Service初始化 + +### 待生成文件(执行脚本后) +1. `app/main/model/featureModel_gen.go` - 已更新 +2. `app/main/model/featureModel.go` - 已更新 +3. `app/main/model/tianyuanapiCallLogModel_gen.go` - 新建 +4. `app/main/model/tianyuanapiCallLogModel.go` - 新建 + diff --git a/deploy/sql/批量修改方法签名.sh b/deploy/sql/批量修改方法签名.sh new file mode 100644 index 0000000..1659a7c --- /dev/null +++ b/deploy/sql/批量修改方法签名.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# 批量修改API请求方法签名,添加ctx参数 + +FILE="app/main/api/internal/service/apirequestService.go" + +# 需要修改的方法列表(不包括ProcessYYSYBE08Request和ProcessFLXG0V4BRequest,因为已经修改过了) +METHODS=( + "ProcessPersonEnterpriseProRequest" + "ProcessFLXG0687Request" + "ProcessFLXG3D56Request" + "ProcessIVYZ5733Request" + "ProcessIVYZ9A2BRequest" + "ProcessJRZQ0A03Request" + "ProcessJRZQ8203Request" + "ProcessJRZQ4AA8Request" + "ProcessQYGL8271Request" + "ProcessQYGL6F2DRequest" + "ProcessQCXG7A2BRequest" + "ProcessYYSY09CDRequest" + "ProcessBehaviorRiskScanRequest" + "ProcessDWBG8B4DRequest" + "ProcessDWBG6A2CRequest" + "ProcessJRZQ4B6CRequest" + "ProcessJRZQ09J8Request" + "ProcessJRZQ5E9FRequest" + "ProcessQYGL3F8ERequest" + "ProcessIVYZ81NCRequest" + "ProcessIVYZ7F3ARequest" + "ProcessDWBG7F3ARequest" + "ProcessJRZQ8A2DRequest" + "ProcessYYSY8B1CRequest" + "ProcessYYSY7D3ERequest" + "ProcessFLXG7E8FRequest" + "ProcessIVYZ8I9JRequest" + "ProcessJRZQ7F1ARequest" + "ProcessIVYZ3P9MRequest" + "ProcessJRZQ6F2ARequest" +) + +echo "开始批量修改方法签名..." +echo "" + +# 备份文件 +if [ ! -f "$FILE.backup" ]; then + cp "$FILE" "$FILE.backup" + echo "✓ 已备份文件到: $FILE.backup" + echo "" +else + echo "⚠ 备份文件已存在,跳过备份" + echo "" +fi + +# 逐个修改方法 +count=0 +for method in "${METHODS[@]}"; do + # 查找方法定义 + old_pattern="func (a \*ApiRequestService) $method(params \[\]byte\) \(\[\]byte, error\)" + new_pattern="func (a *ApiRequestService) $method(ctx context.Context, params \[\]byte\) \(\[\]byte, error\)" + + # 检查文件中是否包含这个方法签名 + if grep -q "$method" "$FILE"; then + # 使用sed替换 + sed -i "s/$old_pattern/$new_pattern/g" "$FILE" + echo "✓ 已修改: $method" + ((count++)) + else + echo "✗ 未找到: $method" + fi +done + +echo "" +echo "=========================================" +echo "修改完成!" +echo "=========================================" +echo "共修改了 $count 个方法" +echo "" +echo "下一步:" +echo "1. 检查修改后的文件" +echo "2. 编译项目验证是否有错误" +echo "3. 运行测试" +echo "=========================================" + diff --git a/deploy/sql/批量替换API调用方法.ps1 b/deploy/sql/批量替换API调用方法.ps1 new file mode 100644 index 0000000..4079fb8 --- /dev/null +++ b/deploy/sql/批量替换API调用方法.ps1 @@ -0,0 +1,86 @@ +# 批量替换API调用方法的PowerShell脚本 + +# 说明:此脚本用于批量修改apirequestService.go中的API调用方法 +# 使用前请先备份文件! + +$filePath = "app\main\api\internal\service\apirequestService.go" + +# 检查文件是否存在 +if (-not (Test-Path $filePath)) { + Write-Host "错误:文件不存在 - $filePath" -ForegroundColor Red + exit 1 +} + +# 备份文件 +$backupPath = "$filePath.backup" +Copy-Item $filePath $backupPath +Write-Host "已备份文件到: $backupPath" -ForegroundColor Green + +# 读取文件内容 +$content = Get-Content $filePath -Raw + +# 需要修改的方法列表(已完成的除外) +$methodsToSkip = @( + "ProcessYYSYBE08Request", + "ProcessFLXG0V4BRequest" +) + +# 需要添加ctx声明的模式 +$pattern1 = '(?m)(func \(a \*ApiRequestService\) Process(\w+)Request\(params \[\]byte\) \(\[\]byte, error\) \{)' + +# 替换API调用 +$pattern2 = 'a\.tianyuanapi\.CallInterface\("' +$replacement2 = 'a.callTianyuanApiWithLog(ctx, "", "' + +# 执行替换 +$count = 0 +$content -replace $pattern2, $replacement2 | Out-File $filePath -Encoding UTF8 + +# 统计替换次数 +$matches = [regex]::Matches($content, $pattern2) +$count = $matches.Count + +Write-Host "已替换 $count 处 a.tianyuanapi.CallInterface 调用" -ForegroundColor Green + +Write-Host "`n现在需要手动为每个方法添加 ctx 声明:" -ForegroundColor Yellow +Write-Host "在每个方法的 func 声明后,第一行添加:" -ForegroundColor Yellow +Write-Host 'ctx := context.Background()' -ForegroundColor Cyan + +Write-Host "`n需要修改的方法列表(已完成的除外):" -ForegroundColor Yellow +Write-Host "ProcessPersonEnterpriseProRequest - 主接口和涉诉查询" -ForegroundColor White +Write-Host "ProcessFLXG0687Request" -ForegroundColor White +Write-Host "ProcessFLXG3D56Request" -ForegroundColor White +Write-Host "ProcessIVYZ5733Request" -ForegroundColor White +Write-Host "ProcessIVYZ9A2BRequest" -ForegroundColor White +Write-Host "ProcessJRZQ0A03Request" -ForegroundColor White +Write-Host "ProcessJRZQ8203Request" -ForegroundColor White +Write-Host "ProcessJRZQ4AA8Request" -ForegroundColor White +Write-Host "ProcessQYGL8271Request" -ForegroundColor White +Write-Host "ProcessQYGL6F2DRequest" -ForegroundColor White +Write-Host "ProcessQCXG7A2BRequest" -ForegroundColor White +Write-Host "ProcessYYSY09CDRequest" -ForegroundColor White +Write-Host "ProcessDWBG8B4DRequest" -ForegroundColor White +Write-Host "ProcessDWBG6A2CRequest" -ForegroundColor White +Write-Host "ProcessJRZQ4B6CRequest" -ForegroundColor White +Write-Host "ProcessJRZQ09J8Request" -ForegroundColor White +Write-Host "ProcessJRZQ5E9FRequest" -ForegroundColor White +Write-Host "ProcessQYGL3F8ERequest" -ForegroundColor White +Write-Host "ProcessIVYZ81NCRequest" -ForegroundColor White +Write-Host "ProcessIVYZ7F3ARequest" -ForegroundColor White +Write-Host "ProcessDWBG7F3ARequest" -ForegroundColor White +Write-Host "ProcessJRZQ8A2DRequest" -ForegroundColor White +Write-Host "ProcessYYSY8B1CRequest" -ForegroundColor White +Write-Host "ProcessYYSY7D3ERequest" -ForegroundColor White +Write-Host "ProcessFLXG7E8FRequest" -ForegroundColor White +Write-Host "ProcessIVYZ8I9JRequest" -ForegroundColor White +Write-Host "ProcessJRZQ7F1ARequest" -ForegroundColor White +Write-Host "ProcessIVYZ3P9MRequest" -ForegroundColor White +Write-Host "ProcessJRZQ6F2ARequest" -ForegroundColor White + +Write-Host "`n总计需要修改 30 个方法" -ForegroundColor Yellow +Write-Host "`n提示:在IDE中使用查找替换功能会更方便" -ForegroundColor Cyan +Write-Host "查找:a.tianyuanapi.CallInterface(`(" -ForegroundColor Cyan +Write-Host "替换:a.callTianyuanApiWithLog(ctx, `", `(" -ForegroundColor Cyan + +Write-Host "`n`n操作完成!请检查文件并手动添加 ctx 声明" -ForegroundColor Green + diff --git a/deploy/sql/批量替换API调用说明.md b/deploy/sql/批量替换API调用说明.md new file mode 100644 index 0000000..dd2da1d --- /dev/null +++ b/deploy/sql/批量替换API调用说明.md @@ -0,0 +1,97 @@ +# 批量替换天元API调用方法 + +## 目的 +将所有 `a.tianyuanapi.CallInterface(` 替换为 `a.callTianyuanApiWithLog(ctx, "", ` + +## 需要修改的文件 +`ycc-proxy-server/app/main/api/internal/service/apirequestService.go` + +## 手动修改步骤 + +### 步骤1:在方法开头添加 ctx 声明 +在每个需要修改的方法开头,在 `func` 声明后添加: +```go +ctx := context.Background() +``` + +### 步骤2:替换调用 +查找并替换: +``` +a.callTianyuanApiWithLog(ctx, "", "API名称", map[string]interface{}{ +``` +替换为: +``` +a.callTianyuanApiWithLog(ctx, "", "API名称", map[string]interface{}{ +``` + +## 具体需要修改的方法列表(已完成部分) + +### ✅ 已完成 +1. ✅ ProcessYYSYBE08Request (第886行) +2. ✅ ProcessFLXG0V4BRequest (第653行) + +### ⏳ 待修改 +1. ProcessPersonEnterpriseProRequest (第320行) - 主接口 +2. ProcessPersonEnterpriseProRequest (第522行) - 涉诉查询 +3. ProcessFLXG0687Request (第675行) +4. ProcessFLXG3D56Request (第705行) +5. ProcessIVYZ5733Request (第759行) +6. ProcessIVYZ9A2BRequest (第815行) +7. ProcessJRZQ0A03Request (第929行) +8. ProcessJRZQ8203Request (第986行) +9. ProcessJRZQ4AA8Request (第1043行) +10. ProcessQYGL8271Request (第1092行) +11. ProcessQYGL6F2DRequest (第1150行) +12. ProcessQCXG7A2BRequest (第1191行) +13. ProcessYYSY09CDRequest (第1211行) +14. ProcessDWBG8B4DRequest (第1322行) +15. ProcessDWBG6A2CRequest (第1347行) +16. ProcessJRZQ4B6CRequest (第1371行) +17. ProcessJRZQ09J8Request (第1395行) +18. ProcessJRZQ5E9FRequest (第1419行) +19. ProcessQYGL3F8ERequest (第1441行) +20. ProcessIVYZ81NCRequest (第1461行) +21. ProcessIVYZ7F3ARequest (第1482行) +22. ProcessDWBG7F3ARequest (第1505行) +23. ProcessJRZQ8A2DRequest (第1528行) +24. ProcessYYSY8B1CRequest (第1550行) +25. ProcessYYSY7D3ERequest (第1569行) +26. ProcessFLXG7E8FRequest (第1590行) +27. ProcessIVYZ8I9JRequest (第1545行) +28. ProcessJRZQ7F1ARequest (第1568行) +29. ProcessIVYZ3P9MRequest (第1591行) +30. ProcessJRZQ6F2ARequest (第1612行) + +## 使用IDE批量替换功能 + +### 方式1:使用VSCode查找替换 +1. 打开 `apirequestService.go` +2. 按 `Ctrl+H` 打开查找替换 +3. 查找内容:`a.callTianyuanApiWithLog(ctx, "", "` +4. 替换内容:`a.callTianyuanApiWithLog(ctx, "", "` +5. 点击"全部替换" + +### 方式2:使用正则表达式批量替换 +查找:`a\.tianyuanapi\.CallInterface\("(.+?)",` +替换:`a.callTianyuanApiWithLog(ctx, "", "$1",` + +**注意:** 执行批量替换后,还需要在每个方法开头添加 `ctx := context.Background()` 声明 + +## 验证修改 + +修改完成后,执行以下步骤验证: + +1. 编译项目 +```bash +cd ycc-proxy-server +go build ./... +``` + +2. 检查是否有编译错误 + +3. 运行测试,发起几个API请求 + +4. 检查数据库 `tianyuanapi_call_log` 表,验证记录是否正确插入 + +5. 检查成本价是否正确计算(成功调用有成本,失败调用成本为0) +