fix: 优化excel数据生成方法,支持动态title

This commit is contained in:
fuzhongyun 2025-12-31 15:44:13 +08:00
parent f497872d51
commit ad3c5bc4b1
4 changed files with 109 additions and 54 deletions

View File

@ -124,6 +124,13 @@ eino_tools:
# 货易通商品品牌查询
hytGoodsBrandSearch:
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list"
# == 电商充值系统 ==
# 我们的商品统计
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"

View File

@ -15,19 +15,15 @@ func New() *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
func (g *Client) Call(req *ExcelGeneratorRequest) ([]byte, error) {
if req.StartRow <= 0 {
req.StartRow = 2
}
if styleRow <= 0 {
styleRow = 2
if req.StyleRow <= 0 {
req.StyleRow = 2
}
f, err := excelize.OpenFile(templatePath)
f, err := excelize.OpenFile(req.TemplatePath)
if err != nil {
return nil, err
}
@ -35,20 +31,25 @@ func (g *Client) Call(templatePath string, data [][]string, startRow int, styleR
sheet := f.GetSheetName(0)
// 若提供标题,替换第一行表格标题
if len(req.Title) > 0 {
f.SetCellValue(sheet, "A1", req.Title)
}
// 获取样式和行高
styleID, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", styleRow))
styleID, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", req.StyleRow))
if err != nil {
log.Errorf("获取样式失败: %v", err)
styleID = 0
}
rowHeight, err := f.GetRowHeight(sheet, styleRow)
rowHeight, err := f.GetRowHeight(sheet, req.StyleRow)
if err != nil {
log.Errorf("获取行高失败: %v", err)
rowHeight = 31 // 默认高度
}
row := startRow
for i, item := range data {
row := req.StartRow
for i, item := range req.ExcelData {
currentRow := row + i
// 设置行高

View File

@ -0,0 +1,9 @@
package excel_generator
type ExcelGeneratorRequest struct {
TemplatePath string // 模板文件路径
ExcelData [][]string // 二维字符串数组
StartRow int // 数据填充起始行 (默认 2)
StyleRow int // 样式参考行 (默认 2)
Title string // 表格标题(仅替换)
}

View File

@ -4,16 +4,19 @@ import (
"ai_scheduler/internal/config"
errorcode "ai_scheduler/internal/data/error"
toolManager "ai_scheduler/internal/domain/tools"
"ai_scheduler/internal/domain/tools/common/excel_generator"
"ai_scheduler/internal/domain/tools/recharge/statistics_ours_product"
"ai_scheduler/internal/domain/workflow/runtime"
"ai_scheduler/internal/pkg/utils_oss"
"context"
"errors"
"fmt"
"math/rand"
"path/filepath"
"time"
"github.com/cloudwego/eino/compose"
"github.com/go-kratos/kratos/v2/log"
)
const WorkflowIDStatisticsOursProduct = "recharge.statisticsOursProduct"
@ -31,8 +34,7 @@ type statisticsOursProduct struct {
}
type StatisticsOursProductWorkflowInput struct {
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
Time time.Time `json:"time"`
}
type StatisticsOursProductWorkflowOutput struct {
@ -52,15 +54,10 @@ func (w *statisticsOursProduct) Invoke(ctx context.Context, args *runtime.Workfl
}
// 获取参数时间
now := args.Args["now"].(time.Time)
input := &StatisticsOursProductWorkflowInput{
// 默认值,具体应从 rec 解析
StartTime: now.Format("2006010200"),
EndTime: now.Format("2006010215"),
Time: args.Args["now"].(time.Time),
}
input.StartTime = "2025122300"
// 工作流过程调用
output, err := runnable.Invoke(ctx, input)
if err != nil {
@ -75,66 +72,107 @@ func (w *statisticsOursProduct) Invoke(ctx context.Context, args *runtime.Workfl
return output, nil
}
type StatisticsOursProductContext struct {
Time time.Time
StartTime string
EndTime string
Title string
ProductData []statistics_ours_product.StatisticsOursProductItem
ImgUrl string
ExcelData [][]string
}
func (w *statisticsOursProduct) buildWorkflow(ctx context.Context) (compose.Runnable[*StatisticsOursProductWorkflowInput, map[string]any], error) {
c := compose.NewChain[*StatisticsOursProductWorkflowInput, map[string]any]()
// 1. 调用工具统计我们的商品
// 1. 参数整理
c.AppendLambda(compose.InvokableLambda(w.formatContext))
// 2. 调用工具统计我们的商品
c.AppendLambda(compose.InvokableLambda(w.callStatisticsTool))
// 2. 生成 Excel 并转图片上传
// 3. 生成 Excel 并转图片上传
c.AppendLambda(compose.InvokableLambda(w.generateExcelAndUpload))
// 3. 转map输出
// 4. 转map输出
c.AppendLambda(compose.InvokableLambda(w.convertToMap))
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},
}
// formatContext 整理上下文参数
func (w *statisticsOursProduct) formatContext(ctx context.Context, input *StatisticsOursProductWorkflowInput) (*StatisticsOursProductContext, error) {
startTime := input.Time.Format("2006010200")
endTime := input.Time.Format("2006010215")
endTimeStr := input.Time.Format("1.2号15点")
return w.toolManager.Recharge.StatisticsOursProduct.Call(ctx, req)
return &StatisticsOursProductContext{
Time: time.Now(),
StartTime: startTime,
EndTime: endTime,
Title: fmt.Sprintf("截止 %s 我们的商品统计", endTimeStr),
}, nil
}
func (w *statisticsOursProduct) generateExcelAndUpload(ctx context.Context, data []statistics_ours_product.StatisticsOursProductItem) (*StatisticsOursProductWorkflowOutput, error) {
// 2. 获取模板路径 (假设在项目根目录的 assets/templates 下)
func (w *statisticsOursProduct) callStatisticsTool(ctx context.Context, state *StatisticsOursProductContext) (*StatisticsOursProductContext, error) {
req := statistics_ours_product.StatisticsOursProductRequest{
Page: 1,
Limit: 100, // 仅取前100条
Serial: []string{state.StartTime, state.EndTime},
}
dataList, err := w.toolManager.Recharge.StatisticsOursProduct.Call(ctx, req)
if err != nil {
log.Errorf("调用统计我们的商品工具失败: %v", err)
return nil, fmt.Errorf("获取统计我们的商品数据失败")
}
if len(dataList) == 0 {
return nil, fmt.Errorf("我们的商品数据为空")
}
state.ProductData = dataList
return state, nil
}
func (w *statisticsOursProduct) generateExcelAndUpload(ctx context.Context, state *StatisticsOursProductContext) (*StatisticsOursProductContext, error) {
// 1. 获取模板路径
cwd, _ := filepath.Abs(".")
templatePath := filepath.Join(cwd, "tmpl", "excel_temp", "recharge_statistics_ours_product.xlsx")
fileName := fmt.Sprintf("statistics_ours_product_%d", time.Now().Unix())
templatePath := filepath.Join(cwd, "../../tmpl", "excel_temp", "recharge_statistics_ours_product.xlsx")
fileName := fmt.Sprintf("statistics_ours_product_%d%d", time.Now().Unix(), rand.Intn(1000))
// 3. 转换数据为 [][]string
excelData := w.convertDataToExcelFormat(data)
// 2. 转换数据为 [][]string
excelData := w.convertDataToExcelFormat(state.ProductData)
// 4. 生成 Excel
excelBytes, err := w.toolManager.Common.ExcelGenerator.Call(templatePath, excelData, 4, 3)
// 3. 生成 Excel
req := &excel_generator.ExcelGeneratorRequest{
TemplatePath: templatePath,
ExcelData: excelData,
StartRow: 4,
StyleRow: 3,
Title: state.Title,
}
excelBytes, err := w.toolManager.Common.ExcelGenerator.Call(req)
if err != nil {
return nil, fmt.Errorf("生成 Excel 失败: %v", err)
}
// 5. Excel 转图片
// 4. Excel 转图片
picBytes, err := w.toolManager.Common.ImageConverter.Call(fileName+".xlsx", excelBytes, 2)
if err != nil {
return nil, fmt.Errorf("Excel 转图片失败: %v", err)
}
// 6. 上传 OSS
// 5. 上传 OSS
url, err := w.ossClient.UploadBytes(fileName+".png", picBytes)
if err != nil {
return nil, fmt.Errorf("上传 OSS 失败: %v", err)
}
res := &StatisticsOursProductWorkflowOutput{
Path: "",
Url: url,
Data: excelData,
Desc: "",
}
state.ImgUrl = url
state.ExcelData = excelData
return res, nil
return state, nil
}
// convertDataToExcelFormat 将业务数据转换为 Excel 生成器需要的二维字符串数组
@ -158,11 +196,11 @@ func (w *statisticsOursProduct) convertDataToExcelFormat(data []statistics_ours_
return result
}
func (w *statisticsOursProduct) convertToMap(ctx context.Context, output *StatisticsOursProductWorkflowOutput) (map[string]any, error) {
func (w *statisticsOursProduct) convertToMap(ctx context.Context, state *StatisticsOursProductContext) (map[string]any, error) {
return map[string]any{
"path": output.Path,
"url": output.Url,
"data": output.Data,
"desc": output.Desc,
"path": "",
"url": state.ImgUrl,
"data": state.ExcelData,
"desc": state.Title,
}, nil
}