Merge branch 'analysis' into v4
This commit is contained in:
commit
212b7cd860
|
|
@ -84,6 +84,7 @@ tools:
|
||||||
|
|
||||||
# eino tool 配置
|
# eino tool 配置
|
||||||
eino_tools:
|
eino_tools:
|
||||||
|
# == 货易通 hyt ==
|
||||||
# 货易通商品上传
|
# 货易通商品上传
|
||||||
hytProductUpload:
|
hytProductUpload:
|
||||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/supplier/batch/add/complete"
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/supplier/batch/add/complete"
|
||||||
|
|
@ -110,6 +111,23 @@ eino_tools:
|
||||||
# 货易通商品品牌查询
|
# 货易通商品品牌查询
|
||||||
hytGoodsBrandSearch:
|
hytGoodsBrandSearch:
|
||||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list"
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list"
|
||||||
|
# == 报表分析 data analytics ==
|
||||||
|
# 负利润分析列表
|
||||||
|
daOursProductLoss:
|
||||||
|
base_url: "https://reportapi.1688sup.com/api/dataanalytics/statisOursProductLossSum"
|
||||||
|
# 利润同比排行榜
|
||||||
|
daProfitRanking:
|
||||||
|
base_url: "https://reportapi.1688sup.com/api/dataanalytics/profitRankingSum"
|
||||||
|
# 销售同比分析列表
|
||||||
|
daOfficialProduct:
|
||||||
|
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"
|
||||||
|
excel2pic:
|
||||||
|
base_url: "http://192.168.6.109:8010/api/v1/convert"
|
||||||
|
|
||||||
dingtalk:
|
dingtalk:
|
||||||
api_key: "dingsbbntrkeiyazcfdg"
|
api_key: "dingsbbntrkeiyazcfdg"
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,18 @@ type EinoToolsConfig struct {
|
||||||
HytGoodsCategorySearch ToolConfig `mapstructure:"hytGoodsCategorySearch"`
|
HytGoodsCategorySearch ToolConfig `mapstructure:"hytGoodsCategorySearch"`
|
||||||
// 货易通商品品牌查询
|
// 货易通商品品牌查询
|
||||||
HytGoodsBrandSearch ToolConfig `mapstructure:"hytGoodsBrandSearch"`
|
HytGoodsBrandSearch ToolConfig `mapstructure:"hytGoodsBrandSearch"`
|
||||||
|
// 负利润分析列表、 详情
|
||||||
|
DaOursProductLoss ToolConfig `mapstructure:"daOursProductLoss"`
|
||||||
|
// 利润同比排行榜
|
||||||
|
DaProfitRanking ToolConfig `mapstructure:"daProfitRanking"`
|
||||||
|
// 销售同比分析列表
|
||||||
|
DaOfficialProduct ToolConfig `mapstructure:"daOfficialProduct"`
|
||||||
|
// 销售同比下滑详情
|
||||||
|
DaOfficialProductDecline ToolConfig `mapstructure:"daOfficialProductDecline"`
|
||||||
|
// 我们的商品统计
|
||||||
|
RechargeStatisticsOursProduct ToolConfig `mapstructure:"rechargeStatisticsOursProduct"`
|
||||||
|
// Excel 转图片
|
||||||
|
Excel2Pic ToolConfig `mapstructure:"excel2Pic"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoggingConfig 日志配置
|
// LoggingConfig 日志配置
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,21 @@
|
||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
"ai_scheduler/internal/data/impl"
|
"ai_scheduler/internal/data/impl"
|
||||||
"ai_scheduler/utils"
|
"ai_scheduler/internal/pkg/oss"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repos 聚合所有 Repository
|
// Repos 聚合所有 Repository
|
||||||
type Repos struct {
|
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{
|
return &Repos{
|
||||||
Session: NewSessionAdapter(sessionImpl),
|
Session: NewSessionAdapter(sessionImpl),
|
||||||
|
OssClient: ossClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@ package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
"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_add"
|
||||||
"ai_scheduler/internal/domain/tools/hyt/goods_brand_search"
|
"ai_scheduler/internal/domain/tools/hyt/goods_brand_search"
|
||||||
"ai_scheduler/internal/domain/tools/hyt/goods_category_add"
|
"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/product_upload"
|
||||||
"ai_scheduler/internal/domain/tools/hyt/supplier_search"
|
"ai_scheduler/internal/domain/tools/hyt/supplier_search"
|
||||||
"ai_scheduler/internal/domain/tools/hyt/warehouse_search"
|
"ai_scheduler/internal/domain/tools/hyt/warehouse_search"
|
||||||
|
"ai_scheduler/internal/domain/tools/recharge/statistics_ours_product"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
Hyt *HytTools
|
Hyt *HytTools
|
||||||
|
Recharge *RechargeTools
|
||||||
|
Common *CommonTools
|
||||||
// Zltx *ZltxTools
|
// Zltx *ZltxTools
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommonTools struct {
|
||||||
|
ExcelGenerator *excel_generator.Client
|
||||||
|
ImageConverter *image_converter.Client
|
||||||
|
}
|
||||||
|
|
||||||
type HytTools struct {
|
type HytTools struct {
|
||||||
ProductUpload *product_upload.Client
|
ProductUpload *product_upload.Client
|
||||||
SupplierSearch *supplier_search.Client
|
SupplierSearch *supplier_search.Client
|
||||||
|
|
@ -28,6 +38,10 @@ type HytTools struct {
|
||||||
GoodsBrandSearch *goods_brand_search.Client
|
GoodsBrandSearch *goods_brand_search.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RechargeTools struct {
|
||||||
|
StatisticsOursProduct *statistics_ours_product.Client
|
||||||
|
}
|
||||||
|
|
||||||
func NewManager(cfg *config.Config) *Manager {
|
func NewManager(cfg *config.Config) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
Hyt: &HytTools{
|
Hyt: &HytTools{
|
||||||
|
|
@ -40,5 +54,12 @@ func NewManager(cfg *config.Config) *Manager {
|
||||||
GoodsCategorySearch: goods_category_search.New(cfg.EinoTools.HytGoodsCategorySearch),
|
GoodsCategorySearch: goods_category_search.New(cfg.EinoTools.HytGoodsCategorySearch),
|
||||||
GoodsBrandSearch: goods_brand_search.New(cfg.EinoTools.HytGoodsBrandSearch),
|
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),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
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, 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) {
|
||||||
|
// 构建工作流
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ func run() {
|
||||||
// 初始化Redis数据库连接
|
// 初始化Redis数据库连接
|
||||||
rdb := utils.NewRdb(configConfig)
|
rdb := utils.NewRdb(configConfig)
|
||||||
// 初始化仓库层
|
// 初始化仓库层
|
||||||
repos := repo.NewRepos(sessionImpl, rdb)
|
repos := repo.NewRepos(sessionImpl, configConfig)
|
||||||
// 初始化包级别的Redis连接
|
// 初始化包级别的Redis连接
|
||||||
pkgRdb := pkg.NewRdb(configConfig)
|
pkgRdb := pkg.NewRdb(configConfig)
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue