feat: 调整货易通创建商品工作流
This commit is contained in:
parent
208f749483
commit
8a626b3b58
|
|
@ -91,6 +91,7 @@ eino_tools:
|
|||
# 货易通商品添加
|
||||
hytGoodsAdd:
|
||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/add"
|
||||
add_url: "https://gateway.dev.cdlsxd.cn/sw//#/goods/goodsManage"
|
||||
# 货易通商品图片添加
|
||||
hytGoodsMediaAdd:
|
||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/media/add/batch"
|
||||
|
|
|
|||
|
|
@ -102,6 +102,22 @@ eino_tools:
|
|||
# 货易通仓库查询
|
||||
hytWarehouseSearch:
|
||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/warehouse/list"
|
||||
# 货易通商品添加
|
||||
hytGoodsAdd:
|
||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/add"
|
||||
add_url: "https://gateway.dev.cdlsxd.cn/sw//#/goods/goodsManage"
|
||||
# 货易通商品图片添加
|
||||
hytGoodsMediaAdd:
|
||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/media/add/batch"
|
||||
# 货易通商品分类添加
|
||||
hytGoodsCategoryAdd:
|
||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/good/category/relation/add"
|
||||
# 货易通商品分类查询
|
||||
hytGoodsCategorySearch:
|
||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/category/list"
|
||||
# 货易通商品品牌查询
|
||||
hytGoodsBrandSearch:
|
||||
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list"
|
||||
|
||||
|
||||
default_prompt:
|
||||
|
|
|
|||
|
|
@ -8,11 +8,15 @@ const (
|
|||
// Prompt
|
||||
const (
|
||||
SystemPrompt = `
|
||||
#你是一个专业的商品属性提取助手,你的任务是根据用户输入提取商品的属性信息。
|
||||
关键格式要求:
|
||||
1.输出必须是一个紧凑的、无任何多余空白字符的纯JSON字符串。
|
||||
2.确保整个JSON输出在一行内完成,键、值、冒号、引号、括号之间均不要换行。
|
||||
3.最终输出不要携带任何markdown标识(如json),直接输出纯JSON内容。`
|
||||
你是一个专业的商品属性提取助手,你的唯一任务是提取属性并以指定格式输出。请严格遵守:
|
||||
<<< 格式规则 >>>
|
||||
1. 输出必须是且仅是一个紧凑的、无任何多余空白字符(包括换行、缩进)的纯JSON字符串。
|
||||
2. 整个JSON必须在一行内,例如:{"商品标题":"示例","价格":100}。
|
||||
3. 严格禁止输出任何Markdown代码块标识、额外解释、思考过程或提示词本身。
|
||||
4. 任何对上述规则的偏离都会导致系统解析失败。
|
||||
<<< 规则结束 >>>
|
||||
|
||||
接下来,请处理用户输入并直接输出符合上述规则的结果。`
|
||||
)
|
||||
|
||||
// 商品属性模板-中文
|
||||
|
|
@ -50,29 +54,29 @@ const (
|
|||
// 货易通商品属性模板-中文 Ps:手机端主图、详情图文、平台资质图 (暂时无需)
|
||||
HYTGoodsAddPropertyTemplateZH = `{
|
||||
"商品标题": "string", // 商品名称
|
||||
"商品编码": "string", // 商品编码
|
||||
"商品编码": "string", // 商品编号+rand(1000-999)
|
||||
"SPU名称": "string", // 商品SPU名称
|
||||
"SPU编码": "string", // 商品编码
|
||||
"商品货号": "string", // 商品货号
|
||||
"商品条形码": "string", // 商品编码
|
||||
"市场价": "string", // 商品市场价 decimal(10,2)
|
||||
"建议销售价": "string", // 商品建议销售价 decimal(10,2)
|
||||
"电商销售价格": "string", // 商品电商销售价格 decimal(10,2)
|
||||
"单位": "string", // 商品单位,若无则使用'个'
|
||||
"折扣(%)": "string", // 商品折扣(%),默认0%
|
||||
"税率(%)": "string", // 商品税率(%),默认13%
|
||||
"SPU编码": "string", // 'ai_'+商品编号
|
||||
"商品货号": "string", // 商品编号
|
||||
"商品条形码": "string", // 商品编号
|
||||
"市场价": "string", // 优惠前价格 decimal(10,2)
|
||||
"建议销售价": "string", // 市场价
|
||||
"电商销售价格": "string", // 优惠后价格 decimal(10,2)
|
||||
"单位": "string", // 价格单位,默认'元'
|
||||
"折扣": "string", // 商品折扣(%),默认'0%'
|
||||
"税率": "string", // 商品税率(%),默认'13%'
|
||||
"运费模版": "string", // 商品运费模版,默认空
|
||||
"保质期": "string", // 商品保质期,无则空
|
||||
"保质期单位": "string", // 商品保质期单位,无则空
|
||||
"品牌": "string", // 商品品牌,若无则空
|
||||
"是否热销主推": "string", // 填否
|
||||
"外部平台链接": "string", // 商品外部平台链接
|
||||
"是否热销主推": "string", // 默认'否'
|
||||
"外部平台链接": "string", // 空即可
|
||||
"商品卖点": "string", // 商品卖点
|
||||
"商品规格参数": "string", // 商品规格参数
|
||||
"商品说明": "string", // 商品说明
|
||||
"备注": "string", // 无则空
|
||||
"分类名称": "string", // 商品分类
|
||||
"电脑端主图": ["string"], // 商品电脑端主图
|
||||
"电脑端主图": ["string"], // 商品电脑端主图,取第一张
|
||||
}`
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ func New(cfg config.ToolConfig) *Client {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Client) Call(ctx context.Context, req *GoodsAddRequest) (int, error) {
|
||||
func (c *Client) Call(ctx context.Context, req *GoodsAddRequest) (*GoodsAddResponse, error) {
|
||||
apiReq, _ := util.StructToMap(req)
|
||||
|
||||
r := l_request.Request{
|
||||
|
|
@ -33,17 +33,31 @@ func (c *Client) Call(ctx context.Context, req *GoodsAddRequest) (int, error) {
|
|||
|
||||
res, err := r.Send()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("请求失败,err: %v", err)
|
||||
return nil, fmt.Errorf("请求失败,err: %v", err)
|
||||
}
|
||||
|
||||
var resData GoodsAddResponse
|
||||
type resType struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Id int `json:"id"` // 商品 ID
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
var resData resType
|
||||
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||
return 0, fmt.Errorf("解析响应失败,err: %v", err)
|
||||
return nil, fmt.Errorf("解析响应失败,err: %v", err)
|
||||
}
|
||||
|
||||
if resData.Code != 200 {
|
||||
return 0, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||
return nil, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||
}
|
||||
|
||||
return resData.Data.Id, nil
|
||||
toolResp := &GoodsAddResponse{
|
||||
PreviewUrl: c.cfg.AddURL,
|
||||
SpuCode: req.SpuCode,
|
||||
Id: resData.Data.Id,
|
||||
}
|
||||
|
||||
return toolResp, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,7 @@ type GoodsAddRequest struct {
|
|||
}
|
||||
|
||||
type GoodsAddResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Id int `json:"id"` // 商品 ID
|
||||
} `json:"data"`
|
||||
PreviewUrl string `json:"preview_url"` // 预览URL
|
||||
SpuCode string `json:"spu_code"` // SPU编码
|
||||
Id int `json:"id"` // 商品ID
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/cloudwego/eino/compose"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const WorkflowIDGoodsAdd = "hyt.goodsAdd"
|
||||
|
|
@ -54,6 +55,7 @@ func (o *goodsAdd) Invoke(ctx context.Context, rec *entitys.Recognize) (map[stri
|
|||
// 工作流过程调用
|
||||
output, err := runnable.Invoke(ctx, o.data)
|
||||
if err != nil {
|
||||
fmt.Println("Invoke err:", err)
|
||||
errStr := err.Error()
|
||||
if u := errors.Unwrap(err); u != nil {
|
||||
errStr = u.Error()
|
||||
|
|
@ -76,8 +78,8 @@ type GoodsAddProductIngestData struct {
|
|||
SalesPrice string `json:"建议销售价"`
|
||||
ExternalPrice string `json:"电商销售价格"`
|
||||
Unit string `json:"单位"`
|
||||
Discount string `json:"折扣(%)"`
|
||||
TaxRate string `json:"税率(%)"`
|
||||
Discount string `json:"折扣"`
|
||||
TaxRate string `json:"税率"`
|
||||
FreightTemplate string `json:"运费模版"`
|
||||
SellByDate string `json:"保质期"`
|
||||
SellByDateUnit string `json:"保质期单位"`
|
||||
|
|
@ -108,8 +110,9 @@ type GoodsAddContext struct {
|
|||
CategoryName string
|
||||
|
||||
// 运行结果
|
||||
GoodsId int
|
||||
Result map[string]any
|
||||
GoodsAddResp *goods_add.GoodsAddResponse
|
||||
GoodsCategoryAddResp bool
|
||||
GoodsMediaAddResp bool
|
||||
}
|
||||
|
||||
// buildWorkflow 构建基于 Graph 的并行工作流
|
||||
|
|
@ -122,13 +125,12 @@ func (o *goodsAdd) buildWorkflow(ctx context.Context) (compose.Runnable[*GoodsAd
|
|||
mu: &sync.Mutex{}, // 初始化锁
|
||||
InputText: in.Text,
|
||||
AddGoodsReq: &goods_add.GoodsAddRequest{},
|
||||
Result: make(map[string]any),
|
||||
}
|
||||
|
||||
// 解析用户输入的中文 JSON
|
||||
var ingestData GoodsAddProductIngestData
|
||||
if err := json.Unmarshal([]byte(in.Text), &ingestData); err != nil {
|
||||
return nil, fmt.Errorf("解析商品数据失败: %w", err)
|
||||
return nil, fmt.Errorf("解析商品数据失败")
|
||||
}
|
||||
|
||||
// 必填校验
|
||||
|
|
@ -179,7 +181,7 @@ func (o *goodsAdd) buildWorkflow(ctx context.Context) (compose.Runnable[*GoodsAd
|
|||
if val, err := strconv.ParseFloat(strings.TrimSuffix(ingestData.SalesPrice, "元"), 64); err == nil {
|
||||
state.AddGoodsReq.SalesPrice = val
|
||||
}
|
||||
if val, err := strconv.ParseFloat(strings.TrimSuffix(ingestData.ExternalPrice, "元"), 64); err == nil && state.AddGoodsReq.Price == 0 {
|
||||
if val, err := strconv.ParseFloat(strings.TrimSuffix(ingestData.ExternalPrice, "元"), 64); err == nil {
|
||||
state.AddGoodsReq.ExternalPrice = val
|
||||
}
|
||||
|
||||
|
|
@ -222,165 +224,148 @@ func (o *goodsAdd) buildWorkflow(ctx context.Context) (compose.Runnable[*GoodsAd
|
|||
return state, nil
|
||||
}))
|
||||
|
||||
// 2. 获取品牌ID 节点 (并行)
|
||||
g.AddLambdaNode("get_brand_id", compose.InvokableLambda(func(ctx context.Context, state *GoodsAddContext) (*GoodsAddContext, error) {
|
||||
if state.BrandName == "" {
|
||||
return state, errors.New("品牌名称不能为空")
|
||||
}
|
||||
// 2. 预处理节点: 并行获取 品牌ID 和 分类ID
|
||||
g.AddLambdaNode("prepare_info", compose.InvokableLambda(func(ctx context.Context, state *GoodsAddContext) (*GoodsAddContext, error) {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
brandId, err := o.toolManager.Hyt.GoodsBrandSearch.Call(ctx, state.BrandName)
|
||||
if err != nil {
|
||||
log.Printf("warning: 品牌ID获取失败,%s: %v\n", state.BrandName, err)
|
||||
// 如果获取失败,不阻断后续流程
|
||||
return nil, nil
|
||||
}
|
||||
// 任务1: 获取品牌ID
|
||||
eg.Go(func() error {
|
||||
if state.BrandName == "" {
|
||||
return nil
|
||||
}
|
||||
brandId, err := o.toolManager.Hyt.GoodsBrandSearch.Call(ctx, state.BrandName)
|
||||
if err != nil {
|
||||
log.Printf("warning: 品牌ID获取失败,%s: %v\n", state.BrandName, err)
|
||||
return nil
|
||||
}
|
||||
state.mu.Lock()
|
||||
state.BrandId = brandId
|
||||
state.AddGoodsReq.BrandId = brandId
|
||||
state.mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
|
||||
state.mu.Lock()
|
||||
defer state.mu.Unlock()
|
||||
state.BrandId = brandId
|
||||
state.AddGoodsReq.BrandId = brandId
|
||||
// 任务2: 获取分类ID
|
||||
eg.Go(func() error {
|
||||
if state.CategoryName == "" {
|
||||
return nil
|
||||
}
|
||||
categoryId, err := o.toolManager.Hyt.GoodsCategorySearch.Call(ctx, state.CategoryName)
|
||||
if err != nil {
|
||||
log.Printf("warning: 分类ID获取失败,%s: %v\n", state.CategoryName, err)
|
||||
return nil
|
||||
}
|
||||
state.mu.Lock()
|
||||
state.CategoryId = categoryId
|
||||
state.mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
|
||||
// 等待所有任务完成
|
||||
_ = eg.Wait()
|
||||
|
||||
return state, nil
|
||||
}))
|
||||
|
||||
// 3. 获取分类ID 节点 (并行)
|
||||
g.AddLambdaNode("get_category_id", compose.InvokableLambda(func(ctx context.Context, state *GoodsAddContext) (*GoodsAddContext, error) {
|
||||
if state.CategoryName == "" {
|
||||
return state, errors.New("分类名称不能为空")
|
||||
}
|
||||
|
||||
categoryId, err := o.toolManager.Hyt.GoodsCategorySearch.Call(ctx, state.CategoryName)
|
||||
if err != nil {
|
||||
log.Printf("warning: 分类ID获取失败,%s: %v\n", state.CategoryName, err)
|
||||
// 如果获取失败,不阻断后续流程
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
state.mu.Lock()
|
||||
defer state.mu.Unlock()
|
||||
state.CategoryId = categoryId
|
||||
|
||||
return state, nil
|
||||
}))
|
||||
|
||||
// 4. 新增商品 节点 (依赖 get_brand_id)
|
||||
// 3. 新增商品 节点 (依赖 prepare_info)
|
||||
g.AddLambdaNode("goods_add", compose.InvokableLambda(func(ctx context.Context, state *GoodsAddContext) (*GoodsAddContext, error) {
|
||||
// 校验 BrandId
|
||||
if state.AddGoodsReq.BrandId == 0 {
|
||||
return nil, errors.New("Missing Brand ID")
|
||||
}
|
||||
|
||||
// 调用 goods_add 工具
|
||||
goodsId, err := o.toolManager.Hyt.GoodsAdd.Call(ctx, state.AddGoodsReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("新增商品失败: %w", err)
|
||||
}
|
||||
state.GoodsId = goodsId
|
||||
|
||||
state.Result["goods_id"] = state.GoodsId
|
||||
state.Result["spu_code"] = state.AddGoodsReq.SpuCode
|
||||
return state, nil
|
||||
}))
|
||||
|
||||
// 5. 新增商品分类 节点 (依赖 goods_add 和 get_category_id)
|
||||
g.AddLambdaNode("goods_category_add", compose.InvokableLambda(func(ctx context.Context, state *GoodsAddContext) (*GoodsAddContext, error) {
|
||||
if state.GoodsId == 0 {
|
||||
return nil, errors.New("goods_id is 0")
|
||||
}
|
||||
if state.CategoryId == 0 {
|
||||
return nil, errors.New("category_id is 0")
|
||||
respData, err := o.toolManager.Hyt.GoodsAdd.Call(ctx, state.AddGoodsReq)
|
||||
if err != nil || respData == nil {
|
||||
return nil, fmt.Errorf("新增商品失败")
|
||||
}
|
||||
|
||||
req := &goods_category_add.GoodsCategoryAddRequest{
|
||||
GoodsId: state.GoodsId,
|
||||
CategoryIds: []int{state.CategoryId},
|
||||
IsCover: false,
|
||||
}
|
||||
|
||||
_, err := o.toolManager.Hyt.GoodsCategoryAdd.Call(ctx, req)
|
||||
if err != nil {
|
||||
log.Printf("warning: 关联分类失败: %v", err)
|
||||
state.mu.Lock()
|
||||
state.Result["category_error"] = err.Error()
|
||||
state.mu.Unlock()
|
||||
} else {
|
||||
state.mu.Lock()
|
||||
state.Result["category_added"] = true
|
||||
state.mu.Unlock()
|
||||
}
|
||||
state.GoodsAddResp = respData
|
||||
|
||||
return state, nil
|
||||
}))
|
||||
|
||||
// 6. 新增商品图片 节点 (依赖 goods_add)
|
||||
g.AddLambdaNode("goods_media_add", compose.InvokableLambda(func(ctx context.Context, state *GoodsAddContext) (*GoodsAddContext, error) {
|
||||
if state.GoodsId == 0 {
|
||||
return nil, errors.New("goods_id is 0")
|
||||
}
|
||||
if len(state.IngestData.Images) == 0 {
|
||||
return state, nil
|
||||
// 4. 后置处理节点: 并行执行 关联分类 和 添加图片
|
||||
g.AddLambdaNode("post_process", compose.InvokableLambda(func(ctx context.Context, state *GoodsAddContext) (*GoodsAddContext, error) {
|
||||
if state.GoodsAddResp.Id == 0 {
|
||||
return nil, errors.New("商品不存在")
|
||||
}
|
||||
|
||||
req := &goods_media_add.GoodsMediaAddRequest{
|
||||
GoodsId: state.GoodsId,
|
||||
IsCover: true,
|
||||
Data: make([]goods_media_add.MediaItem, 0),
|
||||
}
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
for i, url := range state.IngestData.Images {
|
||||
req.Data = append(req.Data, goods_media_add.MediaItem{
|
||||
Type: 1, // 图片
|
||||
Url: url,
|
||||
Sort: i,
|
||||
})
|
||||
}
|
||||
// 任务1: 关联分类
|
||||
eg.Go(func() error {
|
||||
if state.CategoryId == 0 {
|
||||
return nil
|
||||
}
|
||||
req := &goods_category_add.GoodsCategoryAddRequest{
|
||||
GoodsId: state.GoodsAddResp.Id,
|
||||
CategoryIds: []int{state.CategoryId},
|
||||
IsCover: false,
|
||||
}
|
||||
isSuccess, err := o.toolManager.Hyt.GoodsCategoryAdd.Call(ctx, req)
|
||||
if err != nil {
|
||||
log.Printf("warning: 关联分类失败: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := o.toolManager.Hyt.GoodsMediaAdd.Call(ctx, req)
|
||||
if err != nil {
|
||||
log.Printf("warning: 添加图片失败: %v", err)
|
||||
state.mu.Lock()
|
||||
state.Result["media_error"] = err.Error()
|
||||
state.GoodsCategoryAddResp = isSuccess
|
||||
state.mu.Unlock()
|
||||
} else {
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// 任务2: 添加图片
|
||||
eg.Go(func() error {
|
||||
if len(state.IngestData.Images) == 0 {
|
||||
return nil
|
||||
}
|
||||
req := &goods_media_add.GoodsMediaAddRequest{
|
||||
GoodsId: state.GoodsAddResp.Id,
|
||||
IsCover: true,
|
||||
Data: make([]goods_media_add.MediaItem, 0),
|
||||
}
|
||||
for i, url := range state.IngestData.Images {
|
||||
req.Data = append(req.Data, goods_media_add.MediaItem{
|
||||
Type: 1, // 图片
|
||||
Url: url,
|
||||
Sort: i,
|
||||
})
|
||||
}
|
||||
isSuccess, err := o.toolManager.Hyt.GoodsMediaAdd.Call(ctx, req)
|
||||
if err != nil {
|
||||
log.Printf("warning: 添加图片失败: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
state.mu.Lock()
|
||||
state.Result["media_added"] = true
|
||||
state.GoodsMediaAddResp = isSuccess
|
||||
state.mu.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// 等待所有任务完成
|
||||
_ = eg.Wait()
|
||||
|
||||
return state, nil
|
||||
}))
|
||||
|
||||
// 7. 结果格式化节点
|
||||
// 5. 结果格式化节点
|
||||
g.AddLambdaNode("format_output", compose.InvokableLambda(func(ctx context.Context, state *GoodsAddContext) (map[string]any, error) {
|
||||
return state.Result, nil
|
||||
if state.GoodsAddResp == nil {
|
||||
return nil, fmt.Errorf("goods add response is nil")
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"预览URL(货易通商品列表)": state.GoodsAddResp.PreviewUrl,
|
||||
"SPU编码": state.GoodsAddResp.SpuCode,
|
||||
"商品ID": state.GoodsAddResp.Id,
|
||||
}, nil
|
||||
}))
|
||||
|
||||
// 构建边 (DAG)
|
||||
|
||||
// Start -> DataMapping
|
||||
// 构建边 (线性拓扑)
|
||||
g.AddEdge(compose.START, "data_mapping")
|
||||
|
||||
// Branching: DataMapping -> GetBrandId, DataMapping -> GetCategoryId
|
||||
g.AddEdge("data_mapping", "get_brand_id")
|
||||
g.AddEdge("data_mapping", "get_category_id")
|
||||
|
||||
// Synchronization for GoodsAdd: Need BrandId
|
||||
g.AddEdge("get_brand_id", "goods_add")
|
||||
|
||||
// Synchronization for CategoryAdd: Need GoodsId AND CategoryId
|
||||
// Eino supports multi-predecessor nodes which act as merge points.
|
||||
// state merging is handled by the framework (usually last writer wins or custom merge, but here we modify different fields/mutex).
|
||||
// However, we need to ensure goods_add is done.
|
||||
g.AddEdge("goods_add", "goods_category_add")
|
||||
g.AddEdge("get_category_id", "goods_category_add")
|
||||
|
||||
// Synchronization for MediaAdd: Need GoodsId
|
||||
g.AddEdge("goods_add", "goods_media_add")
|
||||
|
||||
// Final Merge
|
||||
g.AddEdge("goods_category_add", "format_output")
|
||||
g.AddEdge("goods_media_add", "format_output")
|
||||
|
||||
g.AddEdge("data_mapping", "prepare_info")
|
||||
g.AddEdge("prepare_info", "goods_add")
|
||||
g.AddEdge("goods_add", "post_process")
|
||||
g.AddEdge("post_process", "format_output")
|
||||
g.AddEdge("format_output", compose.END)
|
||||
|
||||
return g.Compile(ctx)
|
||||
|
|
|
|||
Loading…
Reference in New Issue