feat: 1.新增多个货易通工具 2.新增货易通创建商品工作流
This commit is contained in:
parent
8d4f3c494e
commit
208f749483
|
|
@ -88,6 +88,23 @@ 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"
|
||||
# 货易通商品图片添加
|
||||
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:
|
||||
img_recognize:
|
||||
|
|
|
|||
|
|
@ -163,6 +163,16 @@ type EinoToolsConfig struct {
|
|||
HytSupplierSearch ToolConfig `mapstructure:"hytSupplierSearch"`
|
||||
// 货易通仓库查询
|
||||
HytWarehouseSearch ToolConfig `mapstructure:"hytWarehouseSearch"`
|
||||
// 货易通商品添加
|
||||
HytGoodsAdd ToolConfig `mapstructure:"hytGoodsAdd"`
|
||||
// 货易通商品图片添加
|
||||
HytGoodsMediaAdd ToolConfig `mapstructure:"hytGoodsMediaAdd"`
|
||||
// 货易通商品分类添加
|
||||
HytGoodsCategoryAdd ToolConfig `mapstructure:"hytGoodsCategoryAdd"`
|
||||
// 货易通商品分类查询
|
||||
HytGoodsCategorySearch ToolConfig `mapstructure:"hytGoodsCategorySearch"`
|
||||
// 货易通商品品牌查询
|
||||
HytGoodsBrandSearch ToolConfig `mapstructure:"hytGoodsBrandSearch"`
|
||||
}
|
||||
|
||||
// LoggingConfig 日志配置
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ const (
|
|||
|
||||
// 商品属性模板-中文
|
||||
const (
|
||||
// 货易通商品属性模板-中文
|
||||
HYTProductPropertyTemplateZH = `{
|
||||
// 货易通供应商商品属性模板-中文
|
||||
HYTSupplierProductPropertyTemplateZH = `{
|
||||
"货品编号": "string", // 商品编号
|
||||
"条码": "string", // 货品编号
|
||||
"分类名称": "string", // 商品分类
|
||||
|
|
@ -47,6 +47,33 @@ const (
|
|||
"默认供应商": "string", // 空
|
||||
"默认存放仓库": "string", // 空
|
||||
}`
|
||||
// 货易通商品属性模板-中文 Ps:手机端主图、详情图文、平台资质图 (暂时无需)
|
||||
HYTGoodsAddPropertyTemplateZH = `{
|
||||
"商品标题": "string", // 商品名称
|
||||
"商品编码": "string", // 商品编码
|
||||
"SPU名称": "string", // 商品SPU名称
|
||||
"SPU编码": "string", // 商品编码
|
||||
"商品货号": "string", // 商品货号
|
||||
"商品条形码": "string", // 商品编码
|
||||
"市场价": "string", // 商品市场价 decimal(10,2)
|
||||
"建议销售价": "string", // 商品建议销售价 decimal(10,2)
|
||||
"电商销售价格": "string", // 商品电商销售价格 decimal(10,2)
|
||||
"单位": "string", // 商品单位,若无则使用'个'
|
||||
"折扣(%)": "string", // 商品折扣(%),默认0%
|
||||
"税率(%)": "string", // 商品税率(%),默认13%
|
||||
"运费模版": "string", // 商品运费模版,默认空
|
||||
"保质期": "string", // 商品保质期,无则空
|
||||
"保质期单位": "string", // 商品保质期单位,无则空
|
||||
"品牌": "string", // 商品品牌,若无则空
|
||||
"是否热销主推": "string", // 填否
|
||||
"外部平台链接": "string", // 商品外部平台链接
|
||||
"商品卖点": "string", // 商品卖点
|
||||
"商品规格参数": "string", // 商品规格参数
|
||||
"商品说明": "string", // 商品说明
|
||||
"备注": "string", // 无则空
|
||||
"分类名称": "string", // 商品分类
|
||||
"电脑端主图": ["string"], // 商品电脑端主图
|
||||
}`
|
||||
)
|
||||
|
||||
// 缓存key
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package goods_add
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/pkg/l_request"
|
||||
"ai_scheduler/internal/pkg/util"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
cfg config.ToolConfig
|
||||
}
|
||||
|
||||
func New(cfg config.ToolConfig) *Client {
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Call(ctx context.Context, req *GoodsAddRequest) (int, error) {
|
||||
apiReq, _ := util.StructToMap(req)
|
||||
|
||||
r := l_request.Request{
|
||||
Method: "Post",
|
||||
Url: c.cfg.BaseURL,
|
||||
Json: apiReq,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
||||
res, err := r.Send()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("请求失败,err: %v", err)
|
||||
}
|
||||
|
||||
var resData GoodsAddResponse
|
||||
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||
return 0, fmt.Errorf("解析响应失败,err: %v", err)
|
||||
}
|
||||
|
||||
if resData.Code != 200 {
|
||||
return 0, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||
}
|
||||
|
||||
return resData.Data.Id, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package goods_add
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Call
|
||||
func Test_Call(t *testing.T) {
|
||||
req := &GoodsAddRequest{
|
||||
Unit: "元",
|
||||
IsComposeGoods: 2,
|
||||
GoodsAttributes: "<p><span style=\"color: rgb(96, 98, 102); background-color: rgb(255, 255, 255); font-size: 14px;\">商品规格参数</span></p>",
|
||||
Introduction: "<p><span style=\"color: rgb(96, 98, 102); background-color: rgb(255, 255, 255); font-size: 14px;\">商品卖点</span></p>",
|
||||
GoodsIllustration: "<p><span style=\"color: rgb(96, 98, 102); background-color: rgb(255, 255, 255); font-size: 14px;\">商品说明</span></p>",
|
||||
IsHot: 2,
|
||||
Title: "fu测试001",
|
||||
GoodsNum: "futest001sku",
|
||||
SpuCode: "futest001spu",
|
||||
SpuName: "fu测试001",
|
||||
Price: 100,
|
||||
SalesPrice: 80,
|
||||
Discount: 15,
|
||||
TaxRate: 13,
|
||||
FreightId: 3,
|
||||
Remark: "备注说明",
|
||||
SellByDate: 180,
|
||||
ExternalPrice: 120,
|
||||
GoodsBarCode: "futest001code2",
|
||||
GoodsCode: "futest001code1",
|
||||
SellByDateUnit: "天",
|
||||
BrandId: 3,
|
||||
ExternalUrl: "https://www.baidu.com",
|
||||
}
|
||||
|
||||
cfg := config.ToolConfig{
|
||||
BaseURL: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/add",
|
||||
}
|
||||
|
||||
client := New(cfg)
|
||||
toolResp, err := client.Call(context.Background(), req)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Call() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("toolResp: %v\n", toolResp)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package goods_add
|
||||
|
||||
type GoodsAddRequest struct {
|
||||
Title string `json:"title"` // 商品标题
|
||||
GoodsCode string `json:"goods_code"` // 商品编码
|
||||
SpuName string `json:"spu_name"` // SPU 名称
|
||||
SpuCode string `json:"spu_code"` // SPU 编码
|
||||
GoodsNum string `json:"goods_num"` // 商品货号
|
||||
GoodsBarCode string `json:"goods_bar_code"` // 商品条形码
|
||||
Price float64 `json:"price"` // 市场价
|
||||
SalesPrice float64 `json:"sales_price"` // 建议销售价
|
||||
ExternalPrice float64 `json:"external_price"` // 电商销售价格
|
||||
Unit string `json:"unit"` // 价格单位
|
||||
Discount int `json:"discount"` // 折扣
|
||||
TaxRate int `json:"tax_rate"` // 税率
|
||||
FreightId int `json:"freight_id"` // 运费模板 ID
|
||||
SellByDate int `json:"sell_by_date"` // 保质期
|
||||
SellByDateUnit string `json:"sell_by_date_unit"` // 保质期单位
|
||||
BrandId int `json:"brand_id"` // 品牌 ID
|
||||
IsHot int `json:"is_hot"` // 是否热销主推 1.是 2.否(默认)
|
||||
ExternalUrl string `json:"external_url"` // 外部平台链接
|
||||
Introduction string `json:"introduction"` // 商品卖点
|
||||
GoodsAttributes string `json:"goods_attributes"` // 商品规格参数
|
||||
GoodsIllustration string `json:"goods_illustration"` // 商品说明
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
IsComposeGoods int `json:"is_compose_goods"` // 是否组合商品 1.是 2.否(默认)
|
||||
}
|
||||
|
||||
type GoodsAddResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Id int `json:"id"` // 商品 ID
|
||||
} `json:"data"`
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package goods_brand_search
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/pkg/l_request"
|
||||
"ai_scheduler/internal/pkg/util"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
cfg config.ToolConfig
|
||||
}
|
||||
|
||||
func New(cfg config.ToolConfig) *Client {
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Call(ctx context.Context, name string) (int, error) {
|
||||
if name == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
reqBody := GoodsBrandSearchRequest{
|
||||
Page: 1,
|
||||
Limit: 1,
|
||||
Search: SearchCondition{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
|
||||
apiReq, _ := util.StructToMap(reqBody)
|
||||
|
||||
req := l_request.Request{
|
||||
Method: "Post",
|
||||
Url: c.cfg.BaseURL,
|
||||
Json: apiReq,
|
||||
Headers: map[string]string{
|
||||
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
||||
res, err := req.Send()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("请求失败,err: %v", err)
|
||||
}
|
||||
|
||||
var resData GoodsBrandSearchResponse
|
||||
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||
return 0, fmt.Errorf("解析响应失败,err: %v", err)
|
||||
}
|
||||
|
||||
if resData.Code != 200 {
|
||||
return 0, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||
}
|
||||
|
||||
if len(resData.Data.List) == 0 {
|
||||
return 0, fmt.Errorf("品牌不存在")
|
||||
}
|
||||
|
||||
// 返回第一个匹配的品牌ID
|
||||
return resData.Data.List[0].ID, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package goods_brand_search
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Call
|
||||
func Test_Call(t *testing.T) {
|
||||
// 使用示例中的查询条件
|
||||
name := "vivo"
|
||||
|
||||
cfg := config.ToolConfig{
|
||||
BaseURL: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list",
|
||||
}
|
||||
|
||||
client := New(cfg)
|
||||
toolResp, err := client.Call(context.Background(), name)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Call() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("toolResp (BrandID): %v\n", toolResp)
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package goods_brand_search
|
||||
|
||||
type GoodsBrandSearchRequest struct {
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
Search SearchCondition `json:"search"`
|
||||
}
|
||||
|
||||
type SearchCondition struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type GoodsBrandSearchResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
List []BrandInfo `json:"list"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type BrandInfo struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Logo string `json:"logo"`
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package goods_category_add
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/pkg/l_request"
|
||||
"ai_scheduler/internal/pkg/util"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
cfg config.ToolConfig
|
||||
}
|
||||
|
||||
func New(cfg config.ToolConfig) *Client {
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Call(ctx context.Context, req *GoodsCategoryAddRequest) (bool, error) {
|
||||
apiReq, _ := util.StructToMap(req)
|
||||
|
||||
r := l_request.Request{
|
||||
Method: "Post",
|
||||
Url: c.cfg.BaseURL,
|
||||
Json: apiReq,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
||||
res, err := r.Send()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("请求失败,err: %v", err)
|
||||
}
|
||||
|
||||
var resData GoodsCategoryAddResponse
|
||||
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||
return false, fmt.Errorf("解析响应失败,err: %v", err)
|
||||
}
|
||||
|
||||
if resData.Code != 200 {
|
||||
return false, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||
}
|
||||
|
||||
return resData.Data.IsSuccess, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package goods_category_add
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Call
|
||||
func Test_Call(t *testing.T) {
|
||||
req := &GoodsCategoryAddRequest{
|
||||
GoodsId: 8496,
|
||||
CategoryIds: []int{1667},
|
||||
IsCover: false,
|
||||
}
|
||||
|
||||
cfg := config.ToolConfig{
|
||||
BaseURL: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/good/category/relation/add",
|
||||
}
|
||||
|
||||
client := New(cfg)
|
||||
toolResp, err := client.Call(context.Background(), req)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Call() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("toolResp: %v\n", toolResp)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package goods_category_add
|
||||
|
||||
type GoodsCategoryAddRequest struct {
|
||||
GoodsId int `json:"goods_id"`
|
||||
CategoryIds []int `json:"category_ids"`
|
||||
IsCover bool `json:"is_cover"`
|
||||
}
|
||||
|
||||
type GoodsCategoryAddResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
IsSuccess bool `json:"is_success"` // 是否成功
|
||||
} `json:"data"`
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package goods_category_search
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/pkg/l_request"
|
||||
"ai_scheduler/internal/pkg/util"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
cfg config.ToolConfig
|
||||
}
|
||||
|
||||
func New(cfg config.ToolConfig) *Client {
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Call(ctx context.Context, name string) (int, error) {
|
||||
if name == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
reqBody := GoodsCategorySearchRequest{
|
||||
Page: 1,
|
||||
Limit: 1,
|
||||
Search: SearchCondition{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
|
||||
apiReq, _ := util.StructToMap(reqBody)
|
||||
|
||||
req := l_request.Request{
|
||||
Method: "Post",
|
||||
Url: c.cfg.BaseURL,
|
||||
Json: apiReq,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
||||
res, err := req.Send()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("请求失败,err: %v", err)
|
||||
}
|
||||
|
||||
var resData GoodsCategorySearchResponse
|
||||
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||
return 0, fmt.Errorf("解析响应失败,err: %v", err)
|
||||
}
|
||||
|
||||
if resData.Code != 200 {
|
||||
return 0, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||
}
|
||||
|
||||
if len(resData.Data.List) == 0 {
|
||||
return 0, fmt.Errorf("商品分类不存在")
|
||||
}
|
||||
|
||||
// 返回第一个匹配的分类ID
|
||||
return resData.Data.List[0].ID, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package goods_category_search
|
||||
|
||||
type GoodsCategorySearchRequest struct {
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
Search SearchCondition `json:"search"`
|
||||
}
|
||||
|
||||
type SearchCondition struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type GoodsCategorySearchResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
List []CategoryInfo `json:"list"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type CategoryInfo struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package goods_media_add
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/pkg/l_request"
|
||||
"ai_scheduler/internal/pkg/util"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
cfg config.ToolConfig
|
||||
}
|
||||
|
||||
func New(cfg config.ToolConfig) *Client {
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Call(ctx context.Context, req *GoodsMediaAddRequest) (bool, error) {
|
||||
apiReq, _ := util.StructToMap(req)
|
||||
|
||||
r := l_request.Request{
|
||||
Method: "Post",
|
||||
Url: c.cfg.BaseURL,
|
||||
Json: apiReq,
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
||||
res, err := r.Send()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("请求失败,err: %v", err)
|
||||
}
|
||||
|
||||
var resData GoodsMediaAddResponse
|
||||
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||
return false, fmt.Errorf("解析响应失败,err: %v", err)
|
||||
}
|
||||
|
||||
if resData.Code != 200 {
|
||||
return false, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||
}
|
||||
|
||||
return resData.Data.IsSuccess, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package goods_media_add
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Call
|
||||
func Test_Call(t *testing.T) {
|
||||
req := &GoodsMediaAddRequest{
|
||||
GoodsId: 8496,
|
||||
Data: []MediaItem{
|
||||
{
|
||||
Type: 1,
|
||||
Url: "https://lsxd-hz-store.oss-cn-hangzhou.aliyuncs.com/physicalGoodsSystems/images/goodsimages/goods/22f03d91-3cb7-45b4-ab92-07aad78a1633-screenshot_2025-12-17_17-46-00.png",
|
||||
Sort: 1,
|
||||
},
|
||||
},
|
||||
IsCover: true,
|
||||
}
|
||||
|
||||
cfg := config.ToolConfig{
|
||||
BaseURL: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/media/add/batch",
|
||||
}
|
||||
|
||||
client := New(cfg)
|
||||
toolResp, err := client.Call(context.Background(), req)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Call() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("toolResp: %v\n", toolResp)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package goods_media_add
|
||||
|
||||
type GoodsMediaAddRequest struct {
|
||||
GoodsId int `json:"goods_id"`
|
||||
Data []MediaItem `json:"data"`
|
||||
IsCover bool `json:"is_cover"`
|
||||
}
|
||||
|
||||
type MediaItem struct {
|
||||
Type int `json:"type"`
|
||||
Url string `json:"url"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
type GoodsMediaAddResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
IsSuccess bool `json:"is_success"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ func (c *Client) Call(ctx context.Context, toolReq *ProductUploadRequest) (toolR
|
|||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Ids []int `json:"ids"` // 预览URL
|
||||
Ids []int `json:"ids"` // 商品 IDs
|
||||
} `json:"data"`
|
||||
}
|
||||
var resMap resType
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
package product_upload
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Call
|
||||
func Test_Call(t *testing.T) {
|
||||
req := &ProductUploadRequest{
|
||||
SupplierId: 261,
|
||||
WarehouseId: 257,
|
||||
IsDefaultWarehouse: 1,
|
||||
Sort: 1,
|
||||
Profit: 40,
|
||||
TaxRate: 13,
|
||||
GoodsList: []Goods{
|
||||
{
|
||||
GoodsInfo: GoodsInfo{
|
||||
Title: "Apple iPhone 17 Pro Max 星宇橙色 256GB",
|
||||
Brand: "Apple/苹果",
|
||||
Category: "手机",
|
||||
// CostPrice: 9999.00,
|
||||
GoodsAttributes: "CPU型号:A19 Pro;操作系统:iOS;机身存储:256GB;屏幕尺寸:6.86英寸;屏幕材质:OLED直屏;屏幕技术:视网膜XDR;后置摄像头:4800万像素三主摄系统(主摄4800万+超广角4800万+长焦4800万);前置摄像头:1800万像素;网络支持:5G双卡双待(移动/联通/电信);生物识别:人脸识别;防水等级:IP68;充电功率:40W;无线充电:支持;机身尺寸:163.4mm×78.0mm×8.75mm;机身重量:231g;机身颜色:星宇橙色;特征特质:轻薄、防水防尘、无线充电、NFC、磁吸无线充",
|
||||
GoodsBarCode: "10181383848993",
|
||||
GoodsIllustration: "Apple/苹果 iPhone 17 Pro Max 【需当面激活】支持移动联通电信 5G 双卡双待手机 星宇橙色 256GB 官方标配。搭载A19 Pro芯片,6.86英寸OLED视网膜XDR直屏,4800万像素三主摄系统,支持5G双卡双待,IP68防水防尘,40W有线充电,支持无线充电和磁吸充电。",
|
||||
GoodsNum: "10181383848993",
|
||||
Introduction: "Apple/苹果 iPhone 17 Pro Max 【需当面激活】支持移动联通电信 5G 双卡双待手机 星宇橙色 256GB 官方标配。搭载A19 Pro芯片,6.86英寸OLED视网膜XDR直屏,4800万像素三主摄系统,支持5G双卡双待,IP68防水防尘,40W有线充电,支持无线充电和磁吸充电。",
|
||||
IsBind: 1,
|
||||
SpuName: "Apple iPhone 17 Pro Max",
|
||||
SpuNum: "jd_1766038130329_8721",
|
||||
TaxRate: 13,
|
||||
Unit: "台",
|
||||
Weight: "0.231", // 单位:kg
|
||||
Price: 9999.00,
|
||||
SalesPrice: 9999.00,
|
||||
Stock: 0, // JSON 中未提供库存信息
|
||||
Discount: 10, // JSON 中未提供折扣信息
|
||||
IsComposeGoods: 2,
|
||||
IsHot: 2,
|
||||
},
|
||||
GoodsMediaList: []GoodsMedia{
|
||||
{
|
||||
Type: 1,
|
||||
Url: "https://img10.360buyimg.com/pcpubliccms/s228x228_jfs/t1/363919/12/2409/45712/691d9970F84b99d32/9f9a5d5d16efeb79.jpg.avif",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
client := New(config.ToolConfig{})
|
||||
toolResp, err := client.Call(context.Background(), req)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Call() error = %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("toolResp: %v\n", toolResp)
|
||||
}
|
||||
|
|
@ -41,7 +41,6 @@ func (c *Client) Call(ctx context.Context, name string) (int, error) {
|
|||
Url: c.cfg.BaseURL,
|
||||
Json: apiReq,
|
||||
Headers: map[string]string{
|
||||
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ func (c *Client) Call(ctx context.Context, name string) (int, error) {
|
|||
Url: c.cfg.BaseURL,
|
||||
Params: params,
|
||||
Headers: map[string]string{
|
||||
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ package tools
|
|||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"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"
|
||||
"ai_scheduler/internal/domain/tools/hyt/goods_category_search"
|
||||
"ai_scheduler/internal/domain/tools/hyt/goods_media_add"
|
||||
"ai_scheduler/internal/domain/tools/hyt/product_upload"
|
||||
"ai_scheduler/internal/domain/tools/hyt/supplier_search"
|
||||
"ai_scheduler/internal/domain/tools/hyt/warehouse_search"
|
||||
|
|
@ -13,17 +18,27 @@ type Manager struct {
|
|||
}
|
||||
|
||||
type HytTools struct {
|
||||
ProductUpload *product_upload.Client
|
||||
SupplierSearch *supplier_search.Client
|
||||
WarehouseSearch *warehouse_search.Client
|
||||
ProductUpload *product_upload.Client
|
||||
SupplierSearch *supplier_search.Client
|
||||
WarehouseSearch *warehouse_search.Client
|
||||
GoodsAdd *goods_add.Client
|
||||
GoodsMediaAdd *goods_media_add.Client
|
||||
GoodsCategoryAdd *goods_category_add.Client
|
||||
GoodsCategorySearch *goods_category_search.Client
|
||||
GoodsBrandSearch *goods_brand_search.Client
|
||||
}
|
||||
|
||||
func NewManager(cfg *config.Config) *Manager {
|
||||
return &Manager{
|
||||
Hyt: &HytTools{
|
||||
ProductUpload: product_upload.New(cfg.EinoTools.HytProductUpload),
|
||||
SupplierSearch: supplier_search.New(cfg.EinoTools.HytSupplierSearch),
|
||||
WarehouseSearch: warehouse_search.New(cfg.EinoTools.HytWarehouseSearch),
|
||||
ProductUpload: product_upload.New(cfg.EinoTools.HytProductUpload),
|
||||
SupplierSearch: supplier_search.New(cfg.EinoTools.HytSupplierSearch),
|
||||
WarehouseSearch: warehouse_search.New(cfg.EinoTools.HytWarehouseSearch),
|
||||
GoodsAdd: goods_add.New(cfg.EinoTools.HytGoodsAdd),
|
||||
GoodsMediaAdd: goods_media_add.New(cfg.EinoTools.HytGoodsMediaAdd),
|
||||
GoodsCategoryAdd: goods_category_add.New(cfg.EinoTools.HytGoodsCategoryAdd),
|
||||
GoodsCategorySearch: goods_category_search.New(cfg.EinoTools.HytGoodsCategorySearch),
|
||||
GoodsBrandSearch: goods_brand_search.New(cfg.EinoTools.HytGoodsBrandSearch),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,387 @@
|
|||
package hyt
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
errorcode "ai_scheduler/internal/data/error"
|
||||
toolManager "ai_scheduler/internal/domain/tools"
|
||||
"ai_scheduler/internal/domain/tools/hyt/goods_add"
|
||||
"ai_scheduler/internal/domain/tools/hyt/goods_category_add"
|
||||
"ai_scheduler/internal/domain/tools/hyt/goods_media_add"
|
||||
"ai_scheduler/internal/domain/workflow/runtime"
|
||||
"ai_scheduler/internal/entitys"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudwego/eino/compose"
|
||||
)
|
||||
|
||||
const WorkflowIDGoodsAdd = "hyt.goodsAdd"
|
||||
|
||||
func init() {
|
||||
runtime.Register(WorkflowIDGoodsAdd, func(d *runtime.Deps) (runtime.Workflow, error) {
|
||||
return &goodsAdd{cfg: d.Conf, toolManager: d.ToolManager}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type goodsAdd struct {
|
||||
cfg *config.Config
|
||||
toolManager *toolManager.Manager
|
||||
data *GoodsAddWorkflowInput
|
||||
}
|
||||
|
||||
type GoodsAddWorkflowInput struct {
|
||||
Text string `mapstructure:"text"`
|
||||
}
|
||||
|
||||
func (o *goodsAdd) ID() string { return WorkflowIDGoodsAdd }
|
||||
|
||||
func (o *goodsAdd) Invoke(ctx context.Context, rec *entitys.Recognize) (map[string]any, error) {
|
||||
// 构建工作流
|
||||
runnable, err := o.buildWorkflow(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o.data = &GoodsAddWorkflowInput{
|
||||
Text: rec.UserContent.Text,
|
||||
}
|
||||
// 工作流过程调用
|
||||
output, err := runnable.Invoke(ctx, o.data)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
if u := errors.Unwrap(err); u != nil {
|
||||
errStr = u.Error()
|
||||
}
|
||||
return nil, errorcode.WorkflowErr(errStr)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// ProductIngestData 对应 HYTGoodsAddPropertyTemplateZH 的结构
|
||||
type GoodsAddProductIngestData struct {
|
||||
Title string `json:"商品标题"`
|
||||
GoodsCode string `json:"商品编码"`
|
||||
SpuName string `json:"SPU名称"`
|
||||
SpuCode string `json:"SPU编码"`
|
||||
GoodsNum string `json:"商品货号"`
|
||||
GoodsBarCode string `json:"商品条形码"`
|
||||
Price string `json:"市场价"`
|
||||
SalesPrice string `json:"建议销售价"`
|
||||
ExternalPrice string `json:"电商销售价格"`
|
||||
Unit string `json:"单位"`
|
||||
Discount string `json:"折扣(%)"`
|
||||
TaxRate string `json:"税率(%)"`
|
||||
FreightTemplate string `json:"运费模版"`
|
||||
SellByDate string `json:"保质期"`
|
||||
SellByDateUnit string `json:"保质期单位"`
|
||||
Brand string `json:"品牌"`
|
||||
IsHot string `json:"是否热销主推"`
|
||||
ExternalUrl string `json:"外部平台链接"`
|
||||
Introduction string `json:"商品卖点"`
|
||||
GoodsAttributes string `json:"商品规格参数"`
|
||||
GoodsIllustration string `json:"商品说明"`
|
||||
Remark string `json:"备注"`
|
||||
CategoryName string `json:"分类名称"`
|
||||
Images []string `json:"电脑端主图"`
|
||||
}
|
||||
|
||||
// GoodsAddContext Graph 执行上下文状态
|
||||
type GoodsAddContext struct {
|
||||
mu *sync.Mutex
|
||||
InputText string
|
||||
IngestData *GoodsAddProductIngestData
|
||||
|
||||
// 核心请求体
|
||||
AddGoodsReq *goods_add.GoodsAddRequest
|
||||
|
||||
// 中间态数据
|
||||
BrandId int
|
||||
CategoryId int
|
||||
BrandName string
|
||||
CategoryName string
|
||||
|
||||
// 运行结果
|
||||
GoodsId int
|
||||
Result map[string]any
|
||||
}
|
||||
|
||||
// buildWorkflow 构建基于 Graph 的并行工作流
|
||||
func (o *goodsAdd) buildWorkflow(ctx context.Context) (compose.Runnable[*GoodsAddWorkflowInput, map[string]any], error) {
|
||||
g := compose.NewGraph[*GoodsAddWorkflowInput, map[string]any]()
|
||||
|
||||
// 1. DataMapping 节点: 解析 JSON -> 填充基础 Request
|
||||
g.AddLambdaNode("data_mapping", compose.InvokableLambda(func(ctx context.Context, in *GoodsAddWorkflowInput) (*GoodsAddContext, error) {
|
||||
state := &GoodsAddContext{
|
||||
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)
|
||||
}
|
||||
|
||||
// 必填校验
|
||||
if ingestData.Title == "" {
|
||||
return nil, errors.New("商品标题不能为空")
|
||||
}
|
||||
if ingestData.GoodsCode == "" {
|
||||
return nil, errors.New("商品编码不能为空")
|
||||
}
|
||||
if ingestData.SpuName == "" {
|
||||
return nil, errors.New("SPU名称不能为空")
|
||||
}
|
||||
if ingestData.SpuCode == "" {
|
||||
return nil, errors.New("SPU编码不能为空")
|
||||
}
|
||||
if ingestData.Price == "" {
|
||||
return nil, errors.New("市场价不能为空")
|
||||
}
|
||||
if ingestData.SalesPrice == "" {
|
||||
return nil, errors.New("建议销售价不能为空")
|
||||
}
|
||||
if ingestData.Unit == "" {
|
||||
return nil, errors.New("价格单位不能为空")
|
||||
}
|
||||
if ingestData.Discount == "" {
|
||||
return nil, errors.New("折扣不能为空")
|
||||
}
|
||||
if ingestData.TaxRate == "" {
|
||||
return nil, errors.New("税率不能为空")
|
||||
}
|
||||
|
||||
state.IngestData = &ingestData
|
||||
state.BrandName = ingestData.Brand
|
||||
state.CategoryName = ingestData.CategoryName
|
||||
|
||||
// 映射字段到 AddGoodsReq
|
||||
state.AddGoodsReq.Title = ingestData.Title
|
||||
state.AddGoodsReq.GoodsCode = ingestData.GoodsCode
|
||||
state.AddGoodsReq.SpuName = ingestData.SpuName
|
||||
state.AddGoodsReq.SpuCode = ingestData.SpuCode
|
||||
state.AddGoodsReq.GoodsNum = ingestData.GoodsNum
|
||||
state.AddGoodsReq.GoodsBarCode = ingestData.GoodsBarCode
|
||||
|
||||
// 价格处理
|
||||
if val, err := strconv.ParseFloat(strings.TrimSuffix(ingestData.Price, "元"), 64); err == nil {
|
||||
state.AddGoodsReq.Price = val
|
||||
}
|
||||
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 {
|
||||
state.AddGoodsReq.ExternalPrice = val
|
||||
}
|
||||
|
||||
state.AddGoodsReq.Unit = ingestData.Unit
|
||||
|
||||
// 折扣处理 "80%" -> 80
|
||||
discountStr := strings.TrimSuffix(ingestData.Discount, "%")
|
||||
if val, err := strconv.Atoi(discountStr); err == nil {
|
||||
state.AddGoodsReq.Discount = val
|
||||
}
|
||||
// 税率处理 "13%" -> 13
|
||||
taxStr := strings.TrimSuffix(strings.TrimSuffix(ingestData.TaxRate, "%"), " ")
|
||||
if val, err := strconv.Atoi(taxStr); err == nil {
|
||||
state.AddGoodsReq.TaxRate = val
|
||||
}
|
||||
|
||||
// 运费模板先不给 state.AddGoodsReq.FreightId = 3
|
||||
|
||||
// 保质期处理 "180天" -> 180
|
||||
sellByDateStr := strings.TrimSuffix(ingestData.SellByDate, "天")
|
||||
if val, err := strconv.Atoi(sellByDateStr); err == nil {
|
||||
state.AddGoodsReq.SellByDate = val
|
||||
}
|
||||
state.AddGoodsReq.SellByDateUnit = ingestData.SellByDateUnit
|
||||
|
||||
// state.AddGoodsReq.BrandId 品牌ID后续赋值
|
||||
|
||||
state.AddGoodsReq.IsHot = 2
|
||||
if ingestData.IsHot == "是" {
|
||||
state.AddGoodsReq.IsHot = 1
|
||||
}
|
||||
|
||||
state.AddGoodsReq.ExternalUrl = ingestData.ExternalUrl
|
||||
state.AddGoodsReq.Introduction = ingestData.Introduction
|
||||
state.AddGoodsReq.GoodsAttributes = ingestData.GoodsAttributes
|
||||
state.AddGoodsReq.GoodsIllustration = ingestData.GoodsIllustration
|
||||
state.AddGoodsReq.Remark = ingestData.Remark
|
||||
state.AddGoodsReq.IsComposeGoods = 2 // 非组合商品
|
||||
|
||||
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("品牌名称不能为空")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
state.mu.Lock()
|
||||
defer state.mu.Unlock()
|
||||
state.BrandId = brandId
|
||||
state.AddGoodsReq.BrandId = brandId
|
||||
|
||||
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)
|
||||
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")
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
req := &goods_media_add.GoodsMediaAddRequest{
|
||||
GoodsId: state.GoodsId,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
_, 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.mu.Unlock()
|
||||
} else {
|
||||
state.mu.Lock()
|
||||
state.Result["media_added"] = true
|
||||
state.mu.Unlock()
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}))
|
||||
|
||||
// 7. 结果格式化节点
|
||||
g.AddLambdaNode("format_output", compose.InvokableLambda(func(ctx context.Context, state *GoodsAddContext) (map[string]any, error) {
|
||||
return state.Result, 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("format_output", compose.END)
|
||||
|
||||
return g.Compile(ctx)
|
||||
}
|
||||
|
|
@ -19,10 +19,10 @@ import (
|
|||
"github.com/cloudwego/eino/compose"
|
||||
)
|
||||
|
||||
const WorkflowID = "hyt.productUpload"
|
||||
const WorkflowIDProductUpload = "hyt.productUpload"
|
||||
|
||||
func init() {
|
||||
runtime.Register(WorkflowID, func(d *runtime.Deps) (runtime.Workflow, error) {
|
||||
runtime.Register(WorkflowIDProductUpload, func(d *runtime.Deps) (runtime.Workflow, error) {
|
||||
return &productUpload{cfg: d.Conf, toolManager: d.ToolManager}, nil
|
||||
})
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ type ProductUploadWorkflowInput struct {
|
|||
Text string `mapstructure:"text"`
|
||||
}
|
||||
|
||||
func (o *productUpload) ID() string { return WorkflowID }
|
||||
func (o *productUpload) ID() string { return WorkflowIDProductUpload }
|
||||
|
||||
func (o *productUpload) Invoke(ctx context.Context, rec *entitys.Recognize) (map[string]any, error) {
|
||||
// 构建工作流
|
||||
|
|
@ -64,8 +64,8 @@ func (o *productUpload) Invoke(ctx context.Context, rec *entitys.Recognize) (map
|
|||
return output, nil
|
||||
}
|
||||
|
||||
// ProductIngestData 对应 HYTProductPropertyTemplateZH 的结构
|
||||
type ProductIngestData struct {
|
||||
// ProductIngestData 对应 HYTSupplierProductPropertyTemplateZH 的结构
|
||||
type SupplierProductIngestData struct {
|
||||
BarCode string `json:"条码"`
|
||||
CategoryName string `json:"分类名称"`
|
||||
GoodsName string `json:"货品名称"`
|
||||
|
|
@ -99,7 +99,7 @@ type ProductIngestData struct {
|
|||
type ProductUploadContext struct {
|
||||
mu *sync.Mutex
|
||||
InputText string
|
||||
IngestData *ProductIngestData
|
||||
IngestData *SupplierProductIngestData
|
||||
UploadReq *toolPu.ProductUploadRequest
|
||||
SupplierName string
|
||||
WarehouseName string
|
||||
|
|
@ -121,7 +121,7 @@ func (o *productUpload) buildWorkflow(ctx context.Context) (compose.Runnable[*Pr
|
|||
}
|
||||
|
||||
// 解析用户输入的中文 JSON
|
||||
var ingestData ProductIngestData
|
||||
var ingestData SupplierProductIngestData
|
||||
if err := json.Unmarshal([]byte(in.Text), &ingestData); err != nil {
|
||||
return nil, fmt.Errorf("解析商品数据失败: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ func (s *CapabilityService) ProductIngest(c *fiber.Ctx) error {
|
|||
var sysProductPropertyTemplateZH string
|
||||
switch req.SysId {
|
||||
case "hyt": // 货易通
|
||||
sysProductPropertyTemplateZH = constants.HYTProductPropertyTemplateZH
|
||||
sysProductPropertyTemplateZH = constants.HYTGoodsAddPropertyTemplateZH
|
||||
default:
|
||||
return errorcode.ParamErrf("invalid sys_id")
|
||||
}
|
||||
|
|
@ -191,7 +191,7 @@ func (s *CapabilityService) ProductIngestConfirm(c *fiber.Ctx) error {
|
|||
switch respData.SysId {
|
||||
// 货易通
|
||||
case "hyt":
|
||||
workflowId = hytWorkflow.WorkflowID
|
||||
workflowId = hytWorkflow.WorkflowIDGoodsAdd
|
||||
default:
|
||||
return errorcode.ParamErr("invalid sys_id")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue