fix: 增加我们的商品统计工作流
This commit is contained in:
parent
571b9a88f4
commit
b39d58280c
|
|
@ -209,6 +209,8 @@ type EinoToolsConfig struct {
|
|||
DaOfficialProductDecline ToolConfig `mapstructure:"daOfficialProductDecline"`
|
||||
// 我们的商品统计
|
||||
RechargeStatisticsOursProduct ToolConfig `mapstructure:"rechargeStatisticsOursProduct"`
|
||||
// Excel 转图片
|
||||
Excel2Pic ToolConfig `mapstructure:"excel2Pic"`
|
||||
}
|
||||
|
||||
// LoggingConfig 日志配置
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue