From 0f0da660bad89a7985d98c25513e3900b0e49500 Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Mon, 29 Dec 2025 14:59:34 +0800 Subject: [PATCH 1/4] =?UTF-8?q?chore=EF=BC=9A=20=E6=9A=82=E5=AD=98?= =?UTF-8?q?=E4=B8=80=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config_env.yaml | 14 ++++ internal/config/config.go | 8 ++ .../data_analytics/official_product/client.go | 70 ++++++++++++++++ .../data_analytics/official_product/types.go | 31 ++++++++ .../official_product_decline/client.go | 79 +++++++++++++++++++ .../official_product_decline/types.go | 35 ++++++++ .../ours_product_loss/client.go | 78 ++++++++++++++++++ .../data_analytics/ours_product_loss/types.go | 29 +++++++ .../data_analytics/profit_ranking/client.go | 63 +++++++++++++++ .../profit_ranking/client_test.go | 27 +++++++ .../data_analytics/profit_ranking/types.go | 29 +++++++ 11 files changed, 463 insertions(+) create mode 100644 internal/domain/tools/data_analytics/official_product/client.go create mode 100644 internal/domain/tools/data_analytics/official_product/types.go create mode 100644 internal/domain/tools/data_analytics/official_product_decline/client.go create mode 100644 internal/domain/tools/data_analytics/official_product_decline/types.go create mode 100644 internal/domain/tools/data_analytics/ours_product_loss/client.go create mode 100644 internal/domain/tools/data_analytics/ours_product_loss/types.go create mode 100644 internal/domain/tools/data_analytics/profit_ranking/client.go create mode 100644 internal/domain/tools/data_analytics/profit_ranking/client_test.go create mode 100644 internal/domain/tools/data_analytics/profit_ranking/types.go diff --git a/config/config_env.yaml b/config/config_env.yaml index 21c1faa..9757b80 100644 --- a/config/config_env.yaml +++ b/config/config_env.yaml @@ -78,6 +78,7 @@ tools: # eino tool 配置 eino_tools: + # == 货易通 hyt == # 货易通商品上传 hytProductUpload: base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/supplier/batch/add/complete" @@ -104,6 +105,19 @@ eino_tools: # 货易通商品品牌查询 hytGoodsBrandSearch: base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list" + # == 报表分析 data analytics == + # 负利润分析列表、 负利润分析详情 + daOursProductLoss: + base_url: "http://test.analysis.com/api/dataanalytics/statisOursProductLossSum" + api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY2OTc4MTQwLCJuYmYiOjE3NjY5NzYzNDAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.nyQw7_JZd7IfnIkPlfySGJMkFAHy_RtIL6gRqOYdku3ikp_w3x9bt7rrSBDQMi4Z8jC4jL_qxwhJ1D1DV4uLZ_cZBmuuG-gymIDN1TSJKa8hpu4E8L3tPdbT1H6hehXvg10-_ugKAYNBgnvFUbCRnpMl-sNuqCLWZKa_v63L1v3lGZOQiPPyzEGmIulNwqSF8rlzZggJneVTWEQac2BNK4181mpbz0S_m84xuFFO3Qwen2sEntBaPKeKbJ9BAfdSAQPcIlnG9FHF3mttZfxQQF5nbxU7CAGYsu_2kjpcmP-u5x3tT3gCWsF1t84lSZ8mMxIax7rh0-Y55abRoBnrwQ" + # 利润同比排行榜 + daProfitRanking: + base_url: "http://test.analysis.com/api/dataanalytics/profitRankingSum" + api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY2OTc4MTQwLCJuYmYiOjE3NjY5NzYzNDAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.nyQw7_JZd7IfnIkPlfySGJMkFAHy_RtIL6gRqOYdku3ikp_w3x9bt7rrSBDQMi4Z8jC4jL_qxwhJ1D1DV4uLZ_cZBmuuG-gymIDN1TSJKa8hpu4E8L3tPdbT1H6hehXvg10-_ugKAYNBgnvFUbCRnpMl-sNuqCLWZKa_v63L1v3lGZOQiPPyzEGmIulNwqSF8rlzZggJneVTWEQac2BNK4181mpbz0S_m84xuFFO3Qwen2sEntBaPKeKbJ9BAfdSAQPcIlnG9FHF3mttZfxQQF5nbxU7CAGYsu_2kjpcmP-u5x3tT3gCWsF1t84lSZ8mMxIax7rh0-Y55abRoBnrwQ" + # 销售同比分析列表 + daOfficialProduct: + base_url: "http://test.analysis.com/api/dataanalytics/statisOfficialProduct" + api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY2OTc4MTQwLCJuYmYiOjE3NjY5NzYzNDAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.nyQw7_JZd7IfnIkPlfySGJMkFAHy_RtIL6gRqOYdku3ikp_w3x9bt7rrSBDQMi4Z8jC4jL_qxwhJ1D1DV4uLZ_cZBmuuG-gymIDN1TSJKa8hpu4E8L3tPdbT1H6hehXvg10-_ugKAYNBgnvFUbCRnpMl-sNuqCLWZKa_v63L1v3lGZOQiPPyzEGmIulNwqSF8rlzZggJneVTWEQac2BNK4181mpbz0S_m84xuFFO3Qwen2sEntBaPKeKbJ9BAfdSAQPcIlnG9FHF3mttZfxQQF5nbxU7CAGYsu_2kjpcmP-u5x3tT3gCWsF1t84lSZ8mMxIax7rh0-Y55abRoBnrwQ" dingtalk: api_key: "dingsbbntrkeiyazcfdg" diff --git a/internal/config/config.go b/internal/config/config.go index 441d09d..713663f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -188,6 +188,14 @@ type EinoToolsConfig struct { HytGoodsCategorySearch ToolConfig `mapstructure:"hytGoodsCategorySearch"` // 货易通商品品牌查询 HytGoodsBrandSearch ToolConfig `mapstructure:"hytGoodsBrandSearch"` + // 负利润分析列表、 详情 + DaOursProductLoss ToolConfig `mapstructure:"daOursProductLoss"` + // 利润同比排行榜 + DaProfitRanking ToolConfig `mapstructure:"daProfitRanking"` + // 销售同比分析列表 + DaOfficialProduct ToolConfig `mapstructure:"daOfficialProduct"` + // 销售同比下滑详情 + DaOfficialProductDecline ToolConfig `mapstructure:"daOfficialProductDecline"` } // LoggingConfig 日志配置 diff --git a/internal/domain/tools/data_analytics/official_product/client.go b/internal/domain/tools/data_analytics/official_product/client.go new file mode 100644 index 0000000..bffb6be --- /dev/null +++ b/internal/domain/tools/data_analytics/official_product/client.go @@ -0,0 +1,70 @@ +package official_product + +import ( + "ai_scheduler/internal/config" + "ai_scheduler/internal/pkg/l_request" + "context" + "encoding/json" + "fmt" + "strings" +) + +type Client struct { + cfg config.ToolConfig +} + +func New(cfg config.ToolConfig) *Client { + return &Client{ + cfg: cfg, + } +} + +// Call 调用销售同比分析接口 +func (c *Client) Call(ctx context.Context, req OfficialProductRequest) (*OfficialProductData, error) { + // 构建 URL 参数 + var queryParams []string + + if req.Page > 0 { + queryParams = append(queryParams, fmt.Sprintf("page=%d", req.Page)) + } + if req.Limit > 0 { + queryParams = append(queryParams, fmt.Sprintf("limit=%d", req.Limit)) + } + + for _, pid := range req.OfficialProductIds { + queryParams = append(queryParams, fmt.Sprintf("official_product_id[]=%s", pid)) + } + + for _, t := range req.Ct { + queryParams = append(queryParams, fmt.Sprintf("ct[]=%s", strings.ReplaceAll(t, " ", "+"))) + } + + queryString := strings.Join(queryParams, "&") + fullURL := fmt.Sprintf("%s?%s", c.cfg.BaseURL, queryString) + + headers := map[string]string{ + "Authorization": fmt.Sprintf("Bearer %s", c.cfg.APIKey), + } + + reqObj := l_request.Request{ + Method: "GET", + Url: fullURL, + Headers: headers, + } + + res, err := reqObj.Send() + if err != nil { + return nil, fmt.Errorf("请求失败,err: %v", err) + } + + var resData OfficialProductResponse + if err := json.Unmarshal([]byte(res.Text), &resData); err != nil { + return nil, fmt.Errorf("解析响应失败,err: %v, resp: %s", err, res.Text) + } + + if resData.Code != 200 { + return nil, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg) + } + + return &resData.Data, nil +} diff --git a/internal/domain/tools/data_analytics/official_product/types.go b/internal/domain/tools/data_analytics/official_product/types.go new file mode 100644 index 0000000..a0890b5 --- /dev/null +++ b/internal/domain/tools/data_analytics/official_product/types.go @@ -0,0 +1,31 @@ +package official_product + +// OfficialProductRequest 销售同比分析请求参数 +type OfficialProductRequest struct { + Page int `json:"page"` // 页码 + Limit int `json:"limit"` // 每页条数 + OfficialProductIds []string `json:"official_product_ids"` // 官方产品ID列表 + Ct []string `json:"ct"` // 时间范围 [开始时间, 结束时间] +} + +// OfficialProductResponse 销售同比分析响应结构 +type OfficialProductResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data OfficialProductData `json:"data"` +} + +type OfficialProductData struct { + OfficialProductSum []OfficialProductItem `json:"officialProductSum"` + DataCount int `json:"dataCount"` +} + +type OfficialProductItem struct { + OfficialProductId int `json:"officialProductId"` + OfficialProductName string `json:"officialProductName"` + CurrentNum int `json:"currentNum"` + HistoryOneNum int `json:"historyOneNum"` + HistoryTwoNum int `json:"historyTwoNum"` + HistoryOneDiff int `json:"historyOneDiff"` + HistoryTwoDiff int `json:"historyTwoDiff"` +} diff --git a/internal/domain/tools/data_analytics/official_product_decline/client.go b/internal/domain/tools/data_analytics/official_product_decline/client.go new file mode 100644 index 0000000..9ce9499 --- /dev/null +++ b/internal/domain/tools/data_analytics/official_product_decline/client.go @@ -0,0 +1,79 @@ +package official_product_decline + +import ( + "ai_scheduler/internal/config" + "ai_scheduler/internal/pkg/l_request" + "context" + "encoding/json" + "fmt" + "strings" +) + +type Client struct { + cfg config.ToolConfig +} + +func New(cfg config.ToolConfig) *Client { + return &Client{ + cfg: cfg, + } +} + +// Call 调用销售同比下滑详情接口 +func (c *Client) Call(ctx context.Context, req OfficialProductDeclineRequest) (*OfficialProductDeclineData, error) { + // 构建 URL 参数 + var queryParams []string + + if req.Page > 0 { + queryParams = append(queryParams, fmt.Sprintf("page=%d", req.Page)) + } + if req.Limit > 0 { + queryParams = append(queryParams, fmt.Sprintf("limit=%d", req.Limit)) + } + if req.DownwardValue > 0 { + queryParams = append(queryParams, fmt.Sprintf("downwardValue=%d", req.DownwardValue)) + } + // showTime 可能是 0,所以这里不做 > 0 判断,如果业务默认是 0 可以忽略,或者根据实际需求 + // 假设始终传递该参数如果已设置 + queryParams = append(queryParams, fmt.Sprintf("showTime=%d", req.ShowTime)) + + for _, pid := range req.OfficialProductIds { + queryParams = append(queryParams, fmt.Sprintf("official_product_id[]=%s", pid)) + } + + for _, t := range req.Ct { + queryParams = append(queryParams, fmt.Sprintf("ct[]=%s", strings.ReplaceAll(t, " ", "+"))) + } + + queryString := strings.Join(queryParams, "&") + fullURL := fmt.Sprintf("%s?%s", c.cfg.BaseURL, queryString) + + headers := map[string]string{ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36", + "Accept": "application/json, text/plain, */*", + "Authorization": fmt.Sprintf("Bearer %s", c.cfg.APIKey), + "Accept-Language": "zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7", + } + + reqObj := l_request.Request{ + Method: "GET", + Url: fullURL, + Headers: headers, + } + + res, err := reqObj.Send() + if err != nil { + return nil, fmt.Errorf("请求失败,err: %v", err) + } + + var resData OfficialProductDeclineResponse + if err := json.Unmarshal([]byte(res.Text), &resData); err != nil { + return nil, fmt.Errorf("解析响应失败,err: %v, resp: %s", err, res.Text) + } + + if resData.Code != 200 { + return nil, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg) + } + + return &resData.Data, nil +} diff --git a/internal/domain/tools/data_analytics/official_product_decline/types.go b/internal/domain/tools/data_analytics/official_product_decline/types.go new file mode 100644 index 0000000..3fa598d --- /dev/null +++ b/internal/domain/tools/data_analytics/official_product_decline/types.go @@ -0,0 +1,35 @@ +package official_product_decline + +// OfficialProductDeclineRequest 销售同比下滑详情请求参数 +type OfficialProductDeclineRequest struct { + Page int `json:"page"` // 页码 + Limit int `json:"limit"` // 每页条数 + Ct []string `json:"ct"` // 时间范围 [开始时间, 结束时间] + OfficialProductIds []string `json:"official_product_ids"` // 官方产品ID列表 + DownwardValue int `json:"downward_value"` // 下滑值 + ShowTime int `json:"show_time"` // 是否显示时间 (0:不显示, 1:显示) +} + +// OfficialProductDeclineResponse 销售同比下滑详情响应结构 +type OfficialProductDeclineResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data OfficialProductDeclineData `json:"data"` +} + +type OfficialProductDeclineData struct { + OfficialProductSumDecline []OfficialProductDeclineItem `json:"officialProductSumDecline"` + DataCount int `json:"dataCount"` +} + +type OfficialProductDeclineItem struct { + ResellerId int `json:"resellerId"` + OfficialProductId int `json:"officialProductId"` + OfficialProductName string `json:"officialProductName"` + ResellerName string `json:"resellerName"` + CurrentNum int `json:"currentNum"` + HistoryOneNum int `json:"historyOneNum"` + HistoryTwoNum int `json:"historyTwoNum"` + HistoryOneDiff int `json:"historyOneDiff"` + HistoryTwoDiff int `json:"historyTwoDiff"` +} diff --git a/internal/domain/tools/data_analytics/ours_product_loss/client.go b/internal/domain/tools/data_analytics/ours_product_loss/client.go new file mode 100644 index 0000000..9e145b3 --- /dev/null +++ b/internal/domain/tools/data_analytics/ours_product_loss/client.go @@ -0,0 +1,78 @@ +package ours_product_loss + +import ( + "ai_scheduler/internal/config" + "ai_scheduler/internal/pkg/l_request" + "context" + "encoding/json" + "fmt" + "strings" +) + +type Client struct { + cfg config.ToolConfig +} + +func New(cfg config.ToolConfig) *Client { + return &Client{ + cfg: cfg, + } +} + +// Call 调用负利润分析接口 +// 支持列表查询和详情查询 +// 列表查询:提供 page, limit, ct[] +// 详情查询:提供 ct[], resellerId +func (c *Client) Call(ctx context.Context, req OursProductLossRequest) (*OursProductLossData, error) { + // 处理数组参数 ct[] + // util.StructToMap 通常不支持数组到 url query array 的转换,这里手动处理查询字符串 + // 或者如果 l_request 支持 map 中的 slice 自动转换最好,假设不支持需手动拼接 + + // 构建 URL 参数 + var queryParams []string + + if req.Page > 0 { + queryParams = append(queryParams, fmt.Sprintf("page=%d", req.Page)) + } + if req.Limit > 0 { + queryParams = append(queryParams, fmt.Sprintf("limit=%d", req.Limit)) + } + if req.ResellerId != "" { + queryParams = append(queryParams, fmt.Sprintf("resellerId=%s", req.ResellerId)) + } + + for _, t := range req.Ct { + // URL 编码处理,这里简单处理,实际应使用 url.QueryEscape + // 假设输入已经是合法的格式 + queryParams = append(queryParams, fmt.Sprintf("ct[]=%s", strings.ReplaceAll(t, " ", "+"))) + } + + queryString := strings.Join(queryParams, "&") + fullURL := fmt.Sprintf("%s?%s", c.cfg.BaseURL, queryString) + + headers := map[string]string{ + "Authorization": fmt.Sprintf("Bearer %s", c.cfg.APIKey), + } + + reqObj := l_request.Request{ + Method: "GET", + Url: fullURL, + Headers: headers, + } + + res, err := reqObj.Send() + if err != nil { + return nil, fmt.Errorf("请求失败,err: %v", err) + } + + var resData OursProductLossResponse + if err := json.Unmarshal([]byte(res.Text), &resData); err != nil { + return nil, fmt.Errorf("解析响应失败,err: %v, resp: %s", err, res.Text) + } + + if resData.Code != 200 { + return nil, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg) + } + + return &resData.Data, nil +} diff --git a/internal/domain/tools/data_analytics/ours_product_loss/types.go b/internal/domain/tools/data_analytics/ours_product_loss/types.go new file mode 100644 index 0000000..ceb006a --- /dev/null +++ b/internal/domain/tools/data_analytics/ours_product_loss/types.go @@ -0,0 +1,29 @@ +package ours_product_loss + +// OursProductLossRequest 负利润分析请求参数 +type OursProductLossRequest struct { + Page int `json:"page"` // 页码 + Limit int `json:"limit"` // 每页条数 + Ct []string `json:"ct"` // 时间范围 [开始时间, 结束时间] + ResellerId string `json:"reseller_id"` // 经销商ID (详情查询时使用) +} + +// OursProductLossResponse 负利润分析响应结构 +type OursProductLossResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data OursProductLossData `json:"data"` +} + +type OursProductLossData struct { + List []OursProductLossItem `json:"list"` + DataCount int `json:"dataCount"` +} + +type OursProductLossItem struct { + OursProductId int `json:"oursProductId"` + OursProductName string `json:"oursProductName"` + ResellerName string `json:"resellerName"` + ResellerId int `json:"resellerId"` + Loss float64 `json:"loss"` +} diff --git a/internal/domain/tools/data_analytics/profit_ranking/client.go b/internal/domain/tools/data_analytics/profit_ranking/client.go new file mode 100644 index 0000000..251c59e --- /dev/null +++ b/internal/domain/tools/data_analytics/profit_ranking/client.go @@ -0,0 +1,63 @@ +package profit_ranking + +import ( + "ai_scheduler/internal/config" + "ai_scheduler/internal/pkg/l_request" + "context" + "encoding/json" + "fmt" + "strings" +) + +type Client struct { + cfg config.ToolConfig +} + +func New(cfg config.ToolConfig) *Client { + return &Client{ + cfg: cfg, + } +} + +// Call 调用利润同比排行榜接口 +func (c *Client) Call(ctx context.Context, req ProfitRankingRequest) (*ProfitRankingData, error) { + // 构建 URL 参数 + var queryParams []string + + for _, t := range req.Ct { + queryParams = append(queryParams, fmt.Sprintf("ct[]=%s", strings.ReplaceAll(t, " ", "+"))) + } + + for _, rid := range req.ResellerIds { + queryParams = append(queryParams, fmt.Sprintf("resellerIds[]=%s", rid)) + } + + queryString := strings.Join(queryParams, "&") + fullURL := fmt.Sprintf("%s?%s", c.cfg.BaseURL, queryString) + + headers := map[string]string{ + "Authorization": fmt.Sprintf("Bearer %s", c.cfg.APIKey), + } + + reqObj := l_request.Request{ + Method: "GET", + Url: fullURL, + Headers: headers, + } + + res, err := reqObj.Send() + if err != nil { + return nil, fmt.Errorf("请求失败,err: %v", err) + } + + var resData ProfitRankingResponse + if err := json.Unmarshal([]byte(res.Text), &resData); err != nil { + return nil, fmt.Errorf("解析响应失败,err: %v, resp: %s", err, res.Text) + } + + if resData.Code != 200 { + return nil, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg) + } + + return &resData.Data, nil +} diff --git a/internal/domain/tools/data_analytics/profit_ranking/client_test.go b/internal/domain/tools/data_analytics/profit_ranking/client_test.go new file mode 100644 index 0000000..ad5bc29 --- /dev/null +++ b/internal/domain/tools/data_analytics/profit_ranking/client_test.go @@ -0,0 +1,27 @@ +package profit_ranking + +import ( + "ai_scheduler/internal/config" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestClient_Call(t *testing.T) { + cfg := config.ToolConfig{ + BaseURL: "http://test.analysis.com/api/dataanalytics/profitRankingSum", + APIKey: "test_jwt_token", + } + + client := New(cfg) + assert.NotNil(t, client) + + req := ProfitRankingRequest{ + Ct: []string{"2025-01-01 00:00:00", "2025-01-01 23:59:59"}, + ResellerIds: []string{"1001", "1002"}, + } + + t.Logf("Testing Call with req: %+v", req) + // _, err := client.Call(context.Background(), req) + // assert.Error(t, err) +} diff --git a/internal/domain/tools/data_analytics/profit_ranking/types.go b/internal/domain/tools/data_analytics/profit_ranking/types.go new file mode 100644 index 0000000..d0d8552 --- /dev/null +++ b/internal/domain/tools/data_analytics/profit_ranking/types.go @@ -0,0 +1,29 @@ +package profit_ranking + +// ProfitRankingRequest 利润同比排行请求参数 +type ProfitRankingRequest struct { + Ct []string `json:"ct"` // 时间范围 [开始时间, 结束时间] + ResellerIds []string `json:"reseller_ids"` // 经销商ID列表 +} + +// ProfitRankingResponse 利润同比排行响应结构 +type ProfitRankingResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data ProfitRankingData `json:"data"` +} + +type ProfitRankingData struct { + List []ProfitRankingItem `json:"list"` + DataCount int `json:"dataCount"` +} + +type ProfitRankingItem struct { + ResellerId string `json:"resellerId"` + ResellerName string `json:"resellerName"` + CurrentProfit float64 `json:"currentProfit"` + HistoryOneProfit float64 `json:"historyOneProfit"` + HistoryTwoProfit float64 `json:"historyTwoProfit"` + HistoryOneDiff float64 `json:"historyOneDiff"` + HistoryTwoDiff float64 `json:"historyTwoDiff"` +} From 571b9a88f43e3469ea03c4a5b799337797ec39b1 Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Tue, 30 Dec 2025 18:31:20 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=94=B5?= =?UTF-8?q?=E5=95=86=E5=85=85=E5=80=BC=E7=B3=BB=E7=BB=9F-=E6=88=91?= =?UTF-8?q?=E4=BB=AC=E7=9A=84=E5=95=86=E5=93=81=E7=BB=9F=E8=AE=A1=E5=B7=A5?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config_env.yaml | 16 ++-- internal/config/config.go | 2 + .../statistics_ours_product/client.go | 73 +++++++++++++++++++ .../statistics_ours_product/client_test.go | 33 +++++++++ .../recharge/statistics_ours_product/types.go | 30 ++++++++ .../recharge/statistics_ours_product.go | 33 +++++++++ 6 files changed, 180 insertions(+), 7 deletions(-) create mode 100644 internal/domain/tools/recharge/statistics_ours_product/client.go create mode 100644 internal/domain/tools/recharge/statistics_ours_product/client_test.go create mode 100644 internal/domain/tools/recharge/statistics_ours_product/types.go create mode 100644 internal/domain/workflow/recharge/statistics_ours_product.go diff --git a/config/config_env.yaml b/config/config_env.yaml index 83bbfd6..0467cbc 100644 --- a/config/config_env.yaml +++ b/config/config_env.yaml @@ -112,18 +112,20 @@ eino_tools: hytGoodsBrandSearch: base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list" # == 报表分析 data analytics == - # 负利润分析列表、 负利润分析详情 + # 负利润分析列表 daOursProductLoss: - base_url: "http://test.analysis.com/api/dataanalytics/statisOursProductLossSum" - api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY2OTc4MTQwLCJuYmYiOjE3NjY5NzYzNDAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.nyQw7_JZd7IfnIkPlfySGJMkFAHy_RtIL6gRqOYdku3ikp_w3x9bt7rrSBDQMi4Z8jC4jL_qxwhJ1D1DV4uLZ_cZBmuuG-gymIDN1TSJKa8hpu4E8L3tPdbT1H6hehXvg10-_ugKAYNBgnvFUbCRnpMl-sNuqCLWZKa_v63L1v3lGZOQiPPyzEGmIulNwqSF8rlzZggJneVTWEQac2BNK4181mpbz0S_m84xuFFO3Qwen2sEntBaPKeKbJ9BAfdSAQPcIlnG9FHF3mttZfxQQF5nbxU7CAGYsu_2kjpcmP-u5x3tT3gCWsF1t84lSZ8mMxIax7rh0-Y55abRoBnrwQ" + base_url: "https://reportapi.1688sup.com/api/dataanalytics/statisOursProductLossSum" # 利润同比排行榜 daProfitRanking: - base_url: "http://test.analysis.com/api/dataanalytics/profitRankingSum" - api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY2OTc4MTQwLCJuYmYiOjE3NjY5NzYzNDAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.nyQw7_JZd7IfnIkPlfySGJMkFAHy_RtIL6gRqOYdku3ikp_w3x9bt7rrSBDQMi4Z8jC4jL_qxwhJ1D1DV4uLZ_cZBmuuG-gymIDN1TSJKa8hpu4E8L3tPdbT1H6hehXvg10-_ugKAYNBgnvFUbCRnpMl-sNuqCLWZKa_v63L1v3lGZOQiPPyzEGmIulNwqSF8rlzZggJneVTWEQac2BNK4181mpbz0S_m84xuFFO3Qwen2sEntBaPKeKbJ9BAfdSAQPcIlnG9FHF3mttZfxQQF5nbxU7CAGYsu_2kjpcmP-u5x3tT3gCWsF1t84lSZ8mMxIax7rh0-Y55abRoBnrwQ" + base_url: "https://reportapi.1688sup.com/api/dataanalytics/profitRankingSum" # 销售同比分析列表 daOfficialProduct: - base_url: "http://test.analysis.com/api/dataanalytics/statisOfficialProduct" - api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY2OTc4MTQwLCJuYmYiOjE3NjY5NzYzNDAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.nyQw7_JZd7IfnIkPlfySGJMkFAHy_RtIL6gRqOYdku3ikp_w3x9bt7rrSBDQMi4Z8jC4jL_qxwhJ1D1DV4uLZ_cZBmuuG-gymIDN1TSJKa8hpu4E8L3tPdbT1H6hehXvg10-_ugKAYNBgnvFUbCRnpMl-sNuqCLWZKa_v63L1v3lGZOQiPPyzEGmIulNwqSF8rlzZggJneVTWEQac2BNK4181mpbz0S_m84xuFFO3Qwen2sEntBaPKeKbJ9BAfdSAQPcIlnG9FHF3mttZfxQQF5nbxU7CAGYsu_2kjpcmP-u5x3tT3gCWsF1t84lSZ8mMxIax7rh0-Y55abRoBnrwQ" + base_url: "https://reportapi.1688sup.com/api/dataanalytics/statisOfficialProduct" + # == 电商充值系统 == + # 我们的商品统计 + rechargeStatisticsOursProduct: + base_url: "http://admin.1688sup.cn:8001/admin/statistics/oursProduct" + api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY3MDc3NzcwLCJuYmYiOjE3NjcwNzU5NzAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.Nuw_aR6iSPmhhh9E5rhyTxHBsgWtaTZvbnc7SFTnUBJXTQvYahnk0LyZaVpsVw6FB3cU0F5xOdX3rmGyWyaiszWO6yi-o1oxGMXwhf39fMiWT2xUI6pAn9Ync8DmZ4tOMCNUTdEk4CaQFzrTwJs0c-VR4yW6LgoPmNPvUVZ-KwmusUpnPz5j9RsJItzIWE3bpGGsfB54e2UERcZdbo9BXxCZIBbpAYKBKdl73KuI8SNaXgKvGTrJ6hEN4ESpnbisJVwT5pp_kuChJlcfjHTHFsEf4RJDjN9gTrtDbBWZyY3OmO2ukqYAM7tZPs6TXJwvQGJQsFRVZUBGxS1nD_6DzQ" dingtalk: api_key: "dingsbbntrkeiyazcfdg" diff --git a/internal/config/config.go b/internal/config/config.go index 83115a3..7e94bfd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -207,6 +207,8 @@ type EinoToolsConfig struct { DaOfficialProduct ToolConfig `mapstructure:"daOfficialProduct"` // 销售同比下滑详情 DaOfficialProductDecline ToolConfig `mapstructure:"daOfficialProductDecline"` + // 我们的商品统计 + RechargeStatisticsOursProduct ToolConfig `mapstructure:"rechargeStatisticsOursProduct"` } // LoggingConfig 日志配置 diff --git a/internal/domain/tools/recharge/statistics_ours_product/client.go b/internal/domain/tools/recharge/statistics_ours_product/client.go new file mode 100644 index 0000000..a9706b9 --- /dev/null +++ b/internal/domain/tools/recharge/statistics_ours_product/client.go @@ -0,0 +1,73 @@ +package statistics_ours_product + +import ( + "ai_scheduler/internal/config" + "ai_scheduler/internal/pkg/l_request" + "context" + "encoding/json" + "fmt" + "strings" + "time" +) + +type Client struct { + cfg config.ToolConfig +} + +func New(cfg config.ToolConfig) *Client { + return &Client{ + cfg: cfg, + } +} + +// Call 调用我们的商品统计接口 +func (c *Client) Call(ctx context.Context, req StatisticsOursProductRequest) ([]StatisticsOursProductItem, error) { + // 构建 URL 参数 + var queryParams []string + + if req.Page > 0 { + queryParams = append(queryParams, fmt.Sprintf("page=%d", req.Page)) + } + if req.Limit > 0 { + queryParams = append(queryParams, fmt.Sprintf("limit=%d", req.Limit)) + } + if req.OursProductId != "" { + queryParams = append(queryParams, fmt.Sprintf("ours_product_id=%s", req.OursProductId)) + } + + for _, s := range req.Serial { + queryParams = append(queryParams, fmt.Sprintf("serial[]=%s", s)) + } + + // 添加 timestamp + queryParams = append(queryParams, fmt.Sprintf("timestamp=%d", time.Now().UnixMilli())) + + queryString := strings.Join(queryParams, "&") + fullURL := fmt.Sprintf("%s?%s", c.cfg.BaseURL, queryString) + + headers := map[string]string{ + "Authorization": fmt.Sprintf("Bearer %s", c.cfg.APIKey), + } + + reqObj := l_request.Request{ + Method: "GET", + Url: fullURL, + Headers: headers, + } + + res, err := reqObj.Send() + if err != nil { + return nil, fmt.Errorf("请求失败,err: %v", err) + } + + var resData StatisticsOursProductResponse + if err := json.Unmarshal([]byte(res.Text), &resData); err != nil { + return nil, fmt.Errorf("解析响应失败,err: %v, resp: %s", err, res.Text) + } + + if resData.Code != 200 { + return nil, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg) + } + + return resData.Data, nil +} diff --git a/internal/domain/tools/recharge/statistics_ours_product/client_test.go b/internal/domain/tools/recharge/statistics_ours_product/client_test.go new file mode 100644 index 0000000..4789fba --- /dev/null +++ b/internal/domain/tools/recharge/statistics_ours_product/client_test.go @@ -0,0 +1,33 @@ +package statistics_ours_product + +import ( + "ai_scheduler/internal/config" + "context" + "testing" +) + +func TestClient_Call(t *testing.T) { + cfg := config.ToolConfig{ + BaseURL: "http://admin.1688sup.cn:8001/admin/statistics/oursProduct", + APIKey: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY3MDc3NzcwLCJuYmYiOjE3NjcwNzU5NzAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.Nuw_aR6iSPmhhh9E5rhyTxHBsgWtaTZvbnc7SFTnUBJXTQvYahnk0LyZaVpsVw6FB3cU0F5xOdX3rmGyWyaiszWO6yi-o1oxGMXwhf39fMiWT2xUI6pAn9Ync8DmZ4tOMCNUTdEk4CaQFzrTwJs0c-VR4yW6LgoPmNPvUVZ-KwmusUpnPz5j9RsJItzIWE3bpGGsfB54e2UERcZdbo9BXxCZIBbpAYKBKdl73KuI8SNaXgKvGTrJ6hEN4ESpnbisJVwT5pp_kuChJlcfjHTHFsEf4RJDjN9gTrtDbBWZyY3OmO2ukqYAM7tZPs6TXJwvQGJQsFRVZUBGxS1nD_6DzQ", + } + + client := New(cfg) + + req := StatisticsOursProductRequest{ + Page: 1, + Limit: 100, + Serial: []string{"2025122300", "2025123123"}, + } + + t.Logf("Testing Call with req: %+v", req) + + // 由于没有真实的后端环境和 Token,这里注释掉实际调用 + // 在开发环境中,你可以取消注释并填入有效的 Token 进行测试 + res, err := client.Call(context.Background(), req) + if err != nil { + t.Logf("Call failed: %v", err) + } else { + t.Logf("Call success, resp: %+v", res) + } +} diff --git a/internal/domain/tools/recharge/statistics_ours_product/types.go b/internal/domain/tools/recharge/statistics_ours_product/types.go new file mode 100644 index 0000000..624406b --- /dev/null +++ b/internal/domain/tools/recharge/statistics_ours_product/types.go @@ -0,0 +1,30 @@ +package statistics_ours_product + +// StatisticsOursProductRequest 我们的商品统计请求参数 +type StatisticsOursProductRequest struct { + Page int `json:"page"` // 页码 + Limit int `json:"limit"` // 每页条数 + Serial []string `json:"serial"` // 流水号范围 (通常是日期格式,如 YYYYMMDDHH) + OursProductId string `json:"ours_product_id"` // 我们的商品ID (可选) +} + +// StatisticsOursProductResponse 我们的商品统计响应结构 +// 注意:接口直接返回数组,而不是包含在 data 字段中的对象 +type StatisticsOursProductResponse struct { + Code int `json:"code"` + Msg string `json:"error"` // 接口返回字段名为 error + Data []StatisticsOursProductItem `json:"data"` // data 是一个数组 +} + +type StatisticsOursProductItem struct { + OursProductId int `json:"ours_product_id"` + ResellerId int `json:"reseller_id"` + TotalPrice string `json:"total_price"` + Count string `json:"count"` + SuccessCount string `json:"success_count"` + SuccessPrice string `json:"success_price"` + FailCount string `json:"fail_count"` + FailPrice string `json:"fail_price"` + Profit string `json:"profit"` + OursProductName string `json:"ours_product_name"` +} diff --git a/internal/domain/workflow/recharge/statistics_ours_product.go b/internal/domain/workflow/recharge/statistics_ours_product.go new file mode 100644 index 0000000..0a7649d --- /dev/null +++ b/internal/domain/workflow/recharge/statistics_ours_product.go @@ -0,0 +1,33 @@ +package recharge + +import ( + "ai_scheduler/internal/config" + toolManager "ai_scheduler/internal/domain/tools" + "ai_scheduler/internal/domain/workflow/runtime" + "ai_scheduler/internal/entitys" + "context" +) + +const WorkflowIDStatisticsOursProduct = "recharge.statisticsOursProduct" + +func init() { + runtime.Register(WorkflowIDStatisticsOursProduct, func(d *runtime.Deps) (runtime.Workflow, error) { + return &statisticsOursProduct{cfg: d.Conf, toolManager: d.ToolManager}, nil + }) +} + +type statisticsOursProduct struct { + cfg *config.Config + toolManager *toolManager.Manager +} + +type StatisticsOursProductWorkflowInput struct { + // 预留字段 +} + +func (w *statisticsOursProduct) ID() string { return WorkflowIDStatisticsOursProduct } + +func (w *statisticsOursProduct) Invoke(ctx context.Context, rec *entitys.Recognize) (map[string]any, error) { + // 保持流程为空,仅返回 nil + return nil, nil +} From b39d58280cdbd583f31ff1aa19e9ae07880c4359 Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Wed, 31 Dec 2025 10:17:46 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix=EF=BC=9A=20=E5=A2=9E=E5=8A=A0=E6=88=91?= =?UTF-8?q?=E4=BB=AC=E7=9A=84=E5=95=86=E5=93=81=E7=BB=9F=E8=AE=A1=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/config/config.go | 2 + internal/domain/repo/repos.go | 12 +- .../tools/common/excel_generator/client.go | 76 +++++++++++ .../tools/common/image_converter/client.go | 58 +++++++++ internal/domain/tools/registry.go | 23 +++- .../recharge/statistics_ours_product.go | 121 +++++++++++++++++- internal/services/dtalk_bot_test.go | 2 +- .../recharge_statistics_ours_product.xlsx | Bin 0 -> 9787 bytes 8 files changed, 284 insertions(+), 10 deletions(-) create mode 100644 internal/domain/tools/common/excel_generator/client.go create mode 100644 internal/domain/tools/common/image_converter/client.go create mode 100755 tmpl/excel_temp/recharge_statistics_ours_product.xlsx diff --git a/internal/config/config.go b/internal/config/config.go index 7e94bfd..add63a1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -209,6 +209,8 @@ type EinoToolsConfig struct { DaOfficialProductDecline ToolConfig `mapstructure:"daOfficialProductDecline"` // 我们的商品统计 RechargeStatisticsOursProduct ToolConfig `mapstructure:"rechargeStatisticsOursProduct"` + // Excel 转图片 + Excel2Pic ToolConfig `mapstructure:"excel2Pic"` } // LoggingConfig 日志配置 diff --git a/internal/domain/repo/repos.go b/internal/domain/repo/repos.go index 40ba3de..a3250ba 100644 --- a/internal/domain/repo/repos.go +++ b/internal/domain/repo/repos.go @@ -1,17 +1,21 @@ package repo import ( + "ai_scheduler/internal/config" "ai_scheduler/internal/data/impl" - "ai_scheduler/utils" + "ai_scheduler/internal/pkg/oss" ) // Repos 聚合所有 Repository type Repos struct { - Session SessionRepo + Session SessionRepo + OssClient *oss.Client } -func NewRepos(sessionImpl *impl.SessionImpl, rdb *utils.Rdb) *Repos { +func NewRepos(sessionImpl *impl.SessionImpl, cfg *config.Config) *Repos { + ossClient, _ := oss.NewClient(cfg.Oss) return &Repos{ - Session: NewSessionAdapter(sessionImpl), + Session: NewSessionAdapter(sessionImpl), + OssClient: ossClient, } } diff --git a/internal/domain/tools/common/excel_generator/client.go b/internal/domain/tools/common/excel_generator/client.go new file mode 100644 index 0000000..eae9451 --- /dev/null +++ b/internal/domain/tools/common/excel_generator/client.go @@ -0,0 +1,76 @@ +package excel_generator + +import ( + "fmt" + + "github.com/go-kratos/kratos/v2/log" + "github.com/xuri/excelize/v2" +) + +// Client Excel 生成器 +type Client struct{} + +func New() *Client { + return &Client{} +} + +// Call 根据模板和数据生成 Excel 字节流 +// templatePath: 模板文件路径 +// data: 二维字符串数组,不再使用反射 +// startRow: 数据填充起始行 (默认 2) +// styleRow: 样式参考行 (默认 2) +func (g *Client) Call(templatePath string, data [][]string, startRow int, styleRow int) ([]byte, error) { + if startRow <= 0 { + startRow = 2 + } + if styleRow <= 0 { + styleRow = 2 + } + + f, err := excelize.OpenFile(templatePath) + if err != nil { + return nil, err + } + defer f.Close() + + sheet := f.GetSheetName(0) + + // 获取样式和行高 + styleID, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", styleRow)) + if err != nil { + log.Errorf("获取样式失败: %v", err) + styleID = 0 + } + rowHeight, err := f.GetRowHeight(sheet, styleRow) + if err != nil { + log.Errorf("获取行高失败: %v", err) + rowHeight = 31 // 默认高度 + } + + row := startRow + for i, item := range data { + currentRow := row + i + + // 设置行高 + f.SetRowHeight(sheet, currentRow, rowHeight) + + // 填充数据 + for col, value := range item { + cell := fmt.Sprintf("%c%d", 'A'+col, currentRow) + f.SetCellValue(sheet, cell, value) + } + + // 设置样式 + if styleID != 0 { + endCol := 'A' + len(item) - 1 + f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("%c%d", endCol, currentRow), styleID) + } + } + + buf, err := f.WriteToBuffer() + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/domain/tools/common/image_converter/client.go b/internal/domain/tools/common/image_converter/client.go new file mode 100644 index 0000000..5eba199 --- /dev/null +++ b/internal/domain/tools/common/image_converter/client.go @@ -0,0 +1,58 @@ +package image_converter + +import ( + "ai_scheduler/internal/config" + "bytes" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +// Client 图片转换器 +type Client struct { + cfg config.ToolConfig +} + +func New(cfg config.ToolConfig) *Client { + return &Client{ + cfg: cfg, + } +} + +// Call 将 Excel 文件转换为图片 +func (c *Client) Call(filename string, fileBytes []byte) ([]byte, error) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + part, err := writer.CreateFormFile("file", filename) + if err != nil { + return nil, err + } + if _, err = io.Copy(part, bytes.NewReader(fileBytes)); err != nil { + return nil, err + } + + if err = writer.Close(); err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", c.cfg.BaseURL, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("excel2pic service returned status: %s", resp.Status) + } + + return io.ReadAll(resp.Body) +} diff --git a/internal/domain/tools/registry.go b/internal/domain/tools/registry.go index ad9439d..f6b2193 100644 --- a/internal/domain/tools/registry.go +++ b/internal/domain/tools/registry.go @@ -2,6 +2,8 @@ package tools import ( "ai_scheduler/internal/config" + "ai_scheduler/internal/domain/tools/common/excel_generator" + "ai_scheduler/internal/domain/tools/common/image_converter" "ai_scheduler/internal/domain/tools/hyt/goods_add" "ai_scheduler/internal/domain/tools/hyt/goods_brand_search" "ai_scheduler/internal/domain/tools/hyt/goods_category_add" @@ -10,13 +12,21 @@ import ( "ai_scheduler/internal/domain/tools/hyt/product_upload" "ai_scheduler/internal/domain/tools/hyt/supplier_search" "ai_scheduler/internal/domain/tools/hyt/warehouse_search" + "ai_scheduler/internal/domain/tools/recharge/statistics_ours_product" ) type Manager struct { - Hyt *HytTools + Hyt *HytTools + Recharge *RechargeTools + Common *CommonTools // Zltx *ZltxTools } +type CommonTools struct { + ExcelGenerator *excel_generator.Client + ImageConverter *image_converter.Client +} + type HytTools struct { ProductUpload *product_upload.Client SupplierSearch *supplier_search.Client @@ -28,6 +38,10 @@ type HytTools struct { GoodsBrandSearch *goods_brand_search.Client } +type RechargeTools struct { + StatisticsOursProduct *statistics_ours_product.Client +} + func NewManager(cfg *config.Config) *Manager { return &Manager{ Hyt: &HytTools{ @@ -40,5 +54,12 @@ func NewManager(cfg *config.Config) *Manager { GoodsCategorySearch: goods_category_search.New(cfg.EinoTools.HytGoodsCategorySearch), GoodsBrandSearch: goods_brand_search.New(cfg.EinoTools.HytGoodsBrandSearch), }, + Recharge: &RechargeTools{ + StatisticsOursProduct: statistics_ours_product.New(cfg.EinoTools.RechargeStatisticsOursProduct), + }, + Common: &CommonTools{ + ExcelGenerator: excel_generator.New(), + ImageConverter: image_converter.New(cfg.EinoTools.Excel2Pic), + }, } } diff --git a/internal/domain/workflow/recharge/statistics_ours_product.go b/internal/domain/workflow/recharge/statistics_ours_product.go index 0a7649d..91d5092 100644 --- a/internal/domain/workflow/recharge/statistics_ours_product.go +++ b/internal/domain/workflow/recharge/statistics_ours_product.go @@ -2,32 +2,145 @@ package recharge import ( "ai_scheduler/internal/config" + errorcode "ai_scheduler/internal/data/error" toolManager "ai_scheduler/internal/domain/tools" + "ai_scheduler/internal/domain/tools/recharge/statistics_ours_product" "ai_scheduler/internal/domain/workflow/runtime" "ai_scheduler/internal/entitys" + "ai_scheduler/internal/pkg/oss" "context" + "errors" + "fmt" + "path/filepath" + "time" + + "github.com/cloudwego/eino/compose" ) const WorkflowIDStatisticsOursProduct = "recharge.statisticsOursProduct" func init() { runtime.Register(WorkflowIDStatisticsOursProduct, func(d *runtime.Deps) (runtime.Workflow, error) { - return &statisticsOursProduct{cfg: d.Conf, toolManager: d.ToolManager}, nil + return &statisticsOursProduct{cfg: d.Conf, toolManager: d.ToolManager, ossClient: d.Repos.OssClient}, nil }) } type statisticsOursProduct struct { cfg *config.Config toolManager *toolManager.Manager + ossClient *oss.Client } type StatisticsOursProductWorkflowInput struct { - // 预留字段 + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` +} + +type StatisticsOursProductWorkflowOutput struct { + ImgUrl string `json:"img_url"` } func (w *statisticsOursProduct) ID() string { return WorkflowIDStatisticsOursProduct } func (w *statisticsOursProduct) Invoke(ctx context.Context, rec *entitys.Recognize) (map[string]any, error) { - // 保持流程为空,仅返回 nil - return nil, nil + // 构建工作流 + runnable, err := w.buildWorkflow(ctx) + if err != nil { + return nil, err + } + + // 解析参数 (假设参数在 rec.Match.Parameters 中,或者根据实际情况解析) + // 这里简化处理,假设需要解析参数 + // 实际上这里应该根据 LLM 解析的结果来填充 Input + // 暂时假设 ParameterResult 是 JSON 字符串 + input := &StatisticsOursProductWorkflowInput{ + // 默认值,具体应从 rec 解析 + StartTime: time.Now().Format("2006010200"), + EndTime: time.Now().Format("2006010223"), + } + + // 工作流过程调用 + output, err := runnable.Invoke(ctx, input) + if err != nil { + fmt.Println("Invoke err:", err) + errStr := err.Error() + if u := errors.Unwrap(err); u != nil { + errStr = u.Error() + } + return nil, errorcode.WorkflowErr(errStr) + } + + return map[string]any{"img_url": output.ImgUrl}, nil +} + +func (w *statisticsOursProduct) buildWorkflow(ctx context.Context) (compose.Runnable[*StatisticsOursProductWorkflowInput, *StatisticsOursProductWorkflowOutput], error) { + c := compose.NewChain[*StatisticsOursProductWorkflowInput, *StatisticsOursProductWorkflowOutput]() + + // 1. 调用工具统计我们的商品 + c.AppendLambda(compose.InvokableLambda(w.callStatisticsTool)) + + // 2. 生成 Excel 并转图片上传 + c.AppendLambda(compose.InvokableLambda(w.generateExcelAndUpload)) + + return c.Compile(ctx) +} + +func (w *statisticsOursProduct) callStatisticsTool(ctx context.Context, input *StatisticsOursProductWorkflowInput) ([]statistics_ours_product.StatisticsOursProductItem, error) { + req := statistics_ours_product.StatisticsOursProductRequest{ + Page: 1, + Limit: 100, // 假设取前100条 + Serial: []string{input.StartTime, input.EndTime}, + } + + return w.toolManager.Recharge.StatisticsOursProduct.Call(ctx, req) +} + +func (w *statisticsOursProduct) generateExcelAndUpload(ctx context.Context, data []statistics_ours_product.StatisticsOursProductItem) (*StatisticsOursProductWorkflowOutput, error) { + // 2. 获取模板路径 (假设在项目根目录的 assets/templates 下) + cwd, _ := filepath.Abs(".") + templatePath := filepath.Join(cwd, "assets", "templates", "statistics_ours_product.xlsx") + fileName := fmt.Sprintf("statistics_ours_product_%d", time.Now().Unix()) + + // 3. 转换数据为 [][]string + excelData := w.convertDataToExcelFormat(data) + + // 4. 生成 Excel + excelBytes, err := w.toolManager.Common.ExcelGenerator.Call(templatePath, excelData, 2, 2) + if err != nil { + return nil, fmt.Errorf("生成 Excel 失败: %v", err) + } + + // 5. Excel 转图片 + picBytes, err := w.toolManager.Common.ImageConverter.Call(fileName+".xlsx", excelBytes) + if err != nil { + return nil, fmt.Errorf("Excel 转图片失败: %v", err) + } + + // 6. 上传 OSS + url, err := w.ossClient.UploadBytes(fileName+".png", picBytes) + if err != nil { + return nil, fmt.Errorf("上传 OSS 失败: %v", err) + } + + return &StatisticsOursProductWorkflowOutput{ImgUrl: url}, nil +} + +// convertDataToExcelFormat 将业务数据转换为 Excel 生成器需要的二维字符串数组 +func (w *statisticsOursProduct) convertDataToExcelFormat(data []statistics_ours_product.StatisticsOursProductItem) [][]string { + var result [][]string + for _, item := range data { + row := []string{ + item.OursProductName, + fmt.Sprintf("%d", item.OursProductId), + item.Count, + item.TotalPrice, + item.SuccessCount, + item.SuccessPrice, + item.FailCount, + item.FailPrice, + item.Profit, + } + result = append(result, row) + } + return result } diff --git a/internal/services/dtalk_bot_test.go b/internal/services/dtalk_bot_test.go index 2b8223f..6180016 100644 --- a/internal/services/dtalk_bot_test.go +++ b/internal/services/dtalk_bot_test.go @@ -60,7 +60,7 @@ func run() { // 初始化Redis数据库连接 rdb := utils.NewRdb(configConfig) // 初始化仓库层 - repos := repo.NewRepos(sessionImpl, rdb) + repos := repo.NewRepos(sessionImpl, configConfig) // 初始化包级别的Redis连接 pkgRdb := pkg.NewRdb(configConfig) diff --git a/tmpl/excel_temp/recharge_statistics_ours_product.xlsx b/tmpl/excel_temp/recharge_statistics_ours_product.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..8d5b5a301642c7c87f355460c49b3ecf5a7d3d09 GIT binary patch literal 9787 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 LFdYfS Date: Wed, 31 Dec 2025 10:18:12 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat=EF=BC=9A=E8=BF=BD=E5=8A=A0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config_env.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/config_env.yaml b/config/config_env.yaml index 0467cbc..4348d2d 100644 --- a/config/config_env.yaml +++ b/config/config_env.yaml @@ -126,6 +126,8 @@ eino_tools: rechargeStatisticsOursProduct: base_url: "http://admin.1688sup.cn:8001/admin/statistics/oursProduct" api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY3MDc3NzcwLCJuYmYiOjE3NjcwNzU5NzAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.Nuw_aR6iSPmhhh9E5rhyTxHBsgWtaTZvbnc7SFTnUBJXTQvYahnk0LyZaVpsVw6FB3cU0F5xOdX3rmGyWyaiszWO6yi-o1oxGMXwhf39fMiWT2xUI6pAn9Ync8DmZ4tOMCNUTdEk4CaQFzrTwJs0c-VR4yW6LgoPmNPvUVZ-KwmusUpnPz5j9RsJItzIWE3bpGGsfB54e2UERcZdbo9BXxCZIBbpAYKBKdl73KuI8SNaXgKvGTrJ6hEN4ESpnbisJVwT5pp_kuChJlcfjHTHFsEf4RJDjN9gTrtDbBWZyY3OmO2ukqYAM7tZPs6TXJwvQGJQsFRVZUBGxS1nD_6DzQ" + excel2pic: + base_url: "http://192.168.6.109:8010/api/v1/convert" dingtalk: api_key: "dingsbbntrkeiyazcfdg"