diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 4d9b139..d0d356c 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -14,6 +14,7 @@ import ( "geo/internal/server/router" "geo/internal/service" "geo/utils" + "geo/utils/utils_oss" "github.com/gofiber/fiber/v2/log" ) @@ -36,8 +37,12 @@ func InitializeApp(configConfig *config.Config, allLogger log.AllLogger) (*serve productService := service.NewProductService(configConfig, productImpl, authBiz) aiBiz := biz.NewAiBiz(platImpl) productSourceImpl := impl.NewProductSourceImpl(db) - productBiz := biz.NewProductBiz(productImpl, productSourceImpl) - productSourceService := service.NewProductSourceService(configConfig, productImpl, authBiz, aiBiz, productBiz) + oss, err := utils_oss.NewClient(configConfig) + if err != nil { + panic(err) + } + productBiz := biz.NewProductBiz(productImpl, productSourceImpl, configConfig, oss) + productSourceService := service.NewProductSourceService(configConfig, productImpl, authBiz, aiBiz, productBiz, productSourceImpl) appModule := router.NewAppModule(configConfig, appService, loginService, publishService, productService, productSourceService) routerServer := router.NewRouterServer(appModule) app := server.NewHTTPServer(routerServer) diff --git a/docs/qqqq.docx b/docs/qqqq.docx deleted file mode 100644 index f5b5837..0000000 Binary files a/docs/qqqq.docx and /dev/null differ diff --git a/internal/ai_tool/hsyq.go b/internal/ai_tool/hsyq.go index a2e9a3a..fa2ed15 100644 --- a/internal/ai_tool/hsyq.go +++ b/internal/ai_tool/hsyq.go @@ -147,20 +147,22 @@ func (h *Hsyq) RequestHsyqJson(ctx context.Context, key string, modelName string return resp, err } -func (h *Hsyq) RequestHsyqBot(ctx context.Context, key string, botId string, message []*model.ChatCompletionMessage) ([]byte, error) { +func (h *Hsyq) RequestHsyqBot(ctx context.Context, key string, botId string, message []*model.ChatCompletionMessage) (*string, error) { + req := model.BotChatCompletionRequest{ BotId: botId, Messages: message, Stream: false, Thinking: &model.Thinking{Type: model.ThinkingTypeDisabled}, - ResponseFormat: &model.ResponseFormat{Type: model.ResponseFormatJsonObject}, + ResponseFormat: &model.ResponseFormat{Type: model.ResponseFormatJSONSchema}, } - resp, err := h.mapClient[key].CreateBotChatCompletion(ctx, req) + resp, err := h.getClient(key).CreateBotChatCompletion(ctx, req) if err != nil { return nil, err } + log.Info("token用量:", resp.Usage.TotalTokens) - return resp.Choices[0].Message.Content.MarshalJSON() + return resp.Choices[0].Message.Content.StringValue, nil } diff --git a/internal/ai_tool/hsyq_test.go b/internal/ai_tool/hsyq_test.go new file mode 100644 index 0000000..ca0dbad --- /dev/null +++ b/internal/ai_tool/hsyq_test.go @@ -0,0 +1,30 @@ +package ai_tool + +import ( + "context" + "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model" + "github.com/volcengine/volcengine-go-sdk/volcengine" + "testing" +) + +func Test_I(t *testing.T) { + user := `{"question":"四川房地产售楼软件平台前5名推荐","article_type":"对比评测","brand_info":{"name":"云案场(成都云算科技有限公司)","industry":"软件与信息技术服务业/房地产数字化","type":"数字化精准营销整体解决方案提供商","product_or_service":"房地产数字化营销SaaS平台、CRM售楼软件、云置业、云获客、云渠道、云风控、云售楼、云收银、云开盘、云交房、云商业等15大云系列产品,智慧案场解决方案,渠道风控系统,数字楼盘展示,VR看房,线上开盘系统","advantages":"十七年房地产数字化深耕经验,行业排名首位;国家高新技术企业认证;拥有8项专利、60多项软件著作权;全国25城布局服务网点,累计服务3000+企业、500+项目;三大体系十五大云产品覆盖营销全场景;提供标准API接口打通阿里云、旷视科技、用友、金蝶等生态;支持PC/iOS/安卓/小程序/H5多终端;7×24小时运维保障;客户含万达集团、中铁二局、中信国安、传化集团等头部企业","story":"成都云算科技前身为2008年成立的云算房产软件事业部,初期推出CRM售楼软件切入市场。2013年正式成立公司,2015年获国家高新企业认证,2016年移动端上线,2017年启动SaaS平台建设,2019年云系列产品全面上线,2020-2023年快速扩展至全国25城,2024年实施全国服务计划。十七年从单一软件商成长为覆盖线上拓客、渠道管理、案场运营、交易收款、风险管控、售后交房、商业运营全链条的数字化营销领军企业","problem":"房企营销普遍面临销售动作失控(管理者看不到过程)、过度依赖个人经验(能力无法复制)、主观数据失真(汇报数据加工过)、沟通成本过高(大量时间用于开会汇报而非销售)等问题,导致营销费效比低、舞弊风险高、决策滞后。典型的低效组织闭环:越失控越依赖人,越依赖人越失真,越失真越沟通,越沟通越失控","background":"国家高新技术企业认证;拥有二级教授、高校博导、行业资深专家组成的顾问团队;项目获多位国家及省级领导视察指导;合作生态含中国电信/移动/联通、阿里云、旷视科技、中国银联、通联支付、法大大、企业微信、钉钉、用友、金蝶云等头部企业;服务客户含万达集团、中铁二局、中信国安、传化集团、中慧实业集团、四川三叶集团、广西华宏地产、南宁威宁集团等","case":"某全国性房企成都区域项目,使用云算科技数字化营销方案前,渠道舞弊频发、客户归属争议不断、案场数据手工统计滞后。部署云渠道+云风控+云销售系统后,实现渠道报备带看全流程线上化,刷脸核验杜绝虚假带看,客户判客准确率提升至99%,案场数据自动生成日报周报,营销费效比降低25%,项目去化周期缩短30%","other":"公司愿景为‘成为最优质的数字智能服务商’,秉持‘专注、专业、诚信、务实、高效、共赢’理念。提供1+1+1售楼管理体系(线上+外场+内场),智能报表系统覆盖5大类19+项核心报表自动推送,支持刷脸核验、无感抓拍、人证核验等AI能力。总部设于成都市高新区,全国25个核心城市设立直属服务网点,实现就近快速响应","service_cope":"","target_audience":"全国性大型房企、区域龙头开发商、本土中小开发企业、房产代理公司、商业地产运营方;有土地储备待开发的开发商、即将开盘的项目方、对现有数字化系统成本高或效果不满意寻求更换的房企"}}` + as := `{"question":"平台列表","plat_item":[{"platform":"小红书","platform_index":"xhs"},{"platform":"百家号","platform_index":"bjh"},{"platform":"今日头条","platform_index":"toutiao"},{"platform":"网易号","platform_index":"wyh"},{"platform":"搜狐号","platform_index":"shh"},{"platform":"知乎","platform_index":"zh"},{"platform":"简书","platform_index":"js"}]}` + appKey := "236ba4b6-9daa-4755-b22f-2fd274cd223a" + message := []*model.ChatCompletionMessage{ + { + Role: model.ChatMessageRoleUser, + Content: &model.ChatCompletionMessageContent{ + StringValue: volcengine.String(user), + }, + }, + { + Role: model.ChatMessageRoleSystem, + Content: &model.ChatCompletionMessageContent{ + StringValue: volcengine.String(as), + }, + }, + } + mes, err := NewHsyq().RequestHsyqBot(context.Background(), appKey, "bot-20260413000114-8bw62", message) + t.Log(mes, err) +} diff --git a/internal/biz/ai.go b/internal/biz/ai.go index 17668e0..c42881a 100644 --- a/internal/biz/ai.go +++ b/internal/biz/ai.go @@ -51,7 +51,7 @@ func (a *AiBiz) CreateArticlePrompt(ctx context.Context, data *entitys.BotChat) } if len(platList.PlatItem) > 0 { mes = append(mes, &volmodle.ChatCompletionMessage{ - Role: volmodle.ChatMessageRoleAssistant, + Role: volmodle.ChatMessageRoleSystem, Content: &volmodle.ChatCompletionMessageContent{ StringValue: volcengine.String(pkg.JsonStringIgonErr(platList)), }, diff --git a/internal/biz/product.go b/internal/biz/product.go index f1169e4..38459cc 100644 --- a/internal/biz/product.go +++ b/internal/biz/product.go @@ -2,45 +2,106 @@ package biz import ( "context" + "fmt" + "geo/internal/config" "geo/internal/data/impl" "geo/internal/data/model" - "geo/internal/entitys" - - "github.com/go-viper/mapstructure/v2" + "geo/pkg" + "geo/tmpl/errcode" + "geo/utils/utils_oss" + "os" + "path/filepath" + "strings" + "time" ) type ProductBiz struct { + cfg *config.Config productImpl *impl.ProductImpl productSourceImpl *impl.ProductSourceImpl + oss *utils_oss.Client } -func NewProductBiz(productImpl *impl.ProductImpl, productSourceImpl *impl.ProductSourceImpl) *ProductBiz { +func NewProductBiz(productImpl *impl.ProductImpl, productSourceImpl *impl.ProductSourceImpl, cfg *config.Config, oss *utils_oss.Client) *ProductBiz { return &ProductBiz{ productImpl: productImpl, productSourceImpl: productSourceImpl, + cfg: cfg, + oss: oss, } } -func (p *ProductBiz) GetBrandInfo(ctx context.Context, productId int32) (*entitys.BrandInfo, error) { +func (p *ProductBiz) GetProduct(ctx context.Context, productId int32) (*model.Product, error) { var product model.Product err := p.productImpl.GetByKey(ctx, p.productImpl.PrimaryKey(), productId, &product) if err != nil { return nil, err } - var BrandInfo entitys.BrandInfo - err = mapstructure.Decode(product, &BrandInfo) - - return &BrandInfo, err + if product.ID == 0 { + return nil, errcode.NotFound("产品未找到") + } + return &product, err } -func (p *ProductBiz) CreateAndUploadArticle(ctx context.Context, content string) error { - //var product model.Product - //err := p.productImpl.GetByKey(ctx, p.productImpl.PrimaryKey(), productId, &product) - //if err != nil { - // return nil, err - //} - //var BrandInfo entitys.BrandInfo - //err = mapstructure.Decode(product, &BrandInfo) +func (p *ProductBiz) CreateAndUploadArticle(ctx context.Context, content string, product *model.Product) (string, error) { + if err := os.MkdirAll(p.cfg.Sys.MdDir, 0755); err != nil { + return "", err + } + now := time.Now().Unix() + fileName := fmt.Sprintf("article_%d_%d.md", product.ID, now) + mdAbs := filepath.Join(p.cfg.Sys.MdDir, fileName) + // 创建并写入文件 + file, err := os.Create(mdAbs) + if err != nil { + return "", fmt.Errorf("创建文件失败: %w", err) + } + defer file.Close() + // 写入内容 + if _, err := file.WriteString(content); err != nil { + return "", fmt.Errorf("写入文件失败: %w", err) + } + var imgs []string + if product.Imgs != "" { + imgs = strings.Split(product.Imgs, ",") + } - return nil + docxPath, err := pkg.Md2wordFix(mdAbs, p.cfg.Sys.MdDir, imgs) + if err != nil { + return "", err + } + docxName := fmt.Sprintf("article_%d_%d.docx", product.ID, now) + docxAbs := filepath.Join(docxPath, docxName) + fileByte, err := pkg.ReadDocxToBytes(docxAbs) + if err != nil { + return "", err + } + + url, err := p.oss.UploadBytes(docxName, fileByte) + if err != nil { + return "", fmt.Errorf("上传文件失败: %w", err) + } + return url, nil +} + +func (p *ProductBiz) AddSource(ctx context.Context, source *model.ProductSource) error { + err := p.productSourceImpl.Add(ctx, source) + return err +} + +func (p *ProductBiz) SourceUpload(ctx context.Context, file []byte, fileName string) (string, error) { + url, err := p.oss.UploadBytes(fileName, file) + if err != nil { + return "", fmt.Errorf("上传文件失败: %w", err) + } + return url, nil +} + +func (p *ProductBiz) UpdateSourceById(ctx context.Context, id int32, update *model.ProductSource) error { + + return p.productSourceImpl.UpdateByKey(ctx, p.productSourceImpl.PrimaryKey(), id, update) +} + +func (p *ProductBiz) DelSourceById(ctx context.Context, id int32) error { + + return p.productSourceImpl.DeleteByKey(ctx, p.productSourceImpl.PrimaryKey(), id) } diff --git a/internal/config/config.go b/internal/config/config.go index 73a839b..dc4e484 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -55,6 +55,7 @@ type Sys struct { QrcodesDir string `mapstructure:"qrcodesDir"` ChromePath string `mapstructure:"chromePath"` ChromeDataDir string `mapstructure:"chromeDataDir"` + MdDir string `mapstructure:"mdDIr"` } // LoadConfig 加载配置 @@ -83,6 +84,7 @@ func LoadConfig() (*Config, error) { QrcodesDir: filepath.Join(BaseDir, "qrcodes"), ChromePath: filepath.Join(BaseDir, "chrome", "chrome.exe"), ChromeDataDir: filepath.Join(BaseDir, "chrome_data"), + MdDir: filepath.Join(BaseDir, "md"), }, Hsyq: Hsyq{ ApiKey: "236ba4b6-9daa-4755-b22f-2fd274cd223a", diff --git a/internal/data/model/product_source.gen.go b/internal/data/model/product_source.gen.go index 7fb3cd9..d705b21 100644 --- a/internal/data/model/product_source.gen.go +++ b/internal/data/model/product_source.gen.go @@ -12,14 +12,19 @@ const TableNameProductSource = "product_source" // ProductSource mapped from table type ProductSource struct { - ProductSource int64 `gorm:"column:product_source;primaryKey" json:"product_source"` - Title string `gorm:"column:title;not null" json:"title"` - SourceURL string `gorm:"column:source_url;not null" json:"source_url"` - CreateAt time.Time `gorm:"column:create_at" json:"create_at"` - RecommendPlatfrom string `gorm:"column:recommend_platfrom" json:"recommend_platfrom"` - UpdateAt time.Time `gorm:"column:update_at" json:"update_at"` - DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"` - Status int32 `gorm:"column:status;default:1" json:"status"` + Id int64 `gorm:"column:id;primaryKey" json:"id"` + Ques string `gorm:"column:ques;not nul" json:"ques"` + Tag string `gorm:"column:tag;not nul" json:"tag"` + SourceType int32 `gorm:"column:source_type;not nul" json:"source_type"` + ProductId int32 `gorm:"column:product_id;not nul" json:"product_id"` + ArticleType string `gorm:"column:article_type;not nul" json:"article_type"` + Title string `gorm:"column:title;not null" json:"title"` + SourceURL string `gorm:"column:source_url;not null" json:"source_url"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + RecommendPlatfrom string `gorm:"column:recommend_platfrom" json:"recommend_platfrom"` + UpdatedAt *time.Time `gorm:"column:updated_at" json:"updated_at"` + DeletedAt *time.Time `gorm:"column:deleted_at" json:"deleted_at"` + Status int32 `gorm:"column:status;default:1" json:"status"` } // TableName ProductSource's table name diff --git a/internal/entitys/request.go b/internal/entitys/request.go index ff5fc70..f719458 100644 --- a/internal/entitys/request.go +++ b/internal/entitys/request.go @@ -135,26 +135,32 @@ type ( ProductSourceCreateRequest struct { AccessToken string `json:"access_token" validate:"required" zh:"access_token"` - Id int32 `json:"id" validate:"required" zh:"产品id"` + ProductId int32 `json:"product_id" validate:"required" zh:"产品id"` Ques string `json:"ques" validate:"required" zh:"问题"` ArticleType string `json:"article_type" validate:"required" zh:"文章类型"` } ProductSourceListRequest struct { AccessToken string `json:"access_token" validate:"required" zh:"access_token"` - UserIndex string `json:"user_index" validate:"required" zh:"用户索引"` - PlatIndex string `json:"plat_index" validate:"required" zh:"平台索引"` + ProductId int32 `json:"product_id" validate:"required" zh:"产品id"` + Page int `json:"page"` + PageSize int `json:"page_size"` } ProductSourceUploadRequest struct { AccessToken string `json:"access_token" validate:"required" zh:"access_token"` - UserIndex string `json:"user_index" validate:"required" zh:"用户索引"` - PlatIndex string `json:"plat_index" validate:"required" zh:"平台索引"` + SourceId int32 `json:"source_id" validate:"required" zh:"资源id"` + } + + ProductSourceUpdateRequest struct { + AccessToken string `json:"access_token" validate:"required" zh:"access_token"` + SourceId int32 `json:"source_id" validate:"required" zh:"资源id"` + Title string `json:"title" zh:"标题"` + Tag []string `json:"tag" zh:"标题"` } ProductSourceDelRequest struct { AccessToken string `json:"access_token" validate:"required" zh:"access_token"` - UserIndex string `json:"user_index" validate:"required" zh:"用户索引"` - PlatIndex string `json:"plat_index" validate:"required" zh:"平台索引"` + SourceId int32 `json:"source_id" validate:"required" zh:"资源id"` } ) diff --git a/internal/server/router/app.go b/internal/server/router/app.go index dc42f77..d838d7e 100644 --- a/internal/server/router/app.go +++ b/internal/server/router/app.go @@ -57,7 +57,8 @@ func (m *AppModule) Register(router fiber.Router) { router.Post("/product/del", vali(m.productService.Del, &entitys.ProductDelRequest{})) router.Post("/product/word/create", vali(m.productSourceService.Create, &entitys.ProductSourceCreateRequest{})) - router.Post("/product/word/list", vali(m.productSourceService.List, &entitys.ProductSourceListRequest{})) - router.Post("/product/word/upload", vali(m.productSourceService.Upload, &entitys.ProductSourceUploadRequest{})) - router.Post("/product/word/del", vali(m.productSourceService.Del, &entitys.ProductSourceDelRequest{})) + router.Post("/product/source/list", vali(m.productSourceService.List, &entitys.ProductSourceListRequest{})) + router.Post("/product/source/upload", m.productSourceService.UploadSource) + router.Post("/product/source/update", vali(m.productSourceService.Update, &entitys.ProductSourceUpdateRequest{})) + router.Post("/product/source/del", vali(m.productSourceService.Del, &entitys.ProductSourceDelRequest{})) } diff --git a/internal/service/product_source.go b/internal/service/product_source.go index 88b22cb..16cf2bd 100644 --- a/internal/service/product_source.go +++ b/internal/service/product_source.go @@ -2,31 +2,42 @@ package service import ( "encoding/json" + "fmt" "geo/internal/ai_tool" "geo/internal/biz" "geo/internal/config" "geo/internal/data/impl" "geo/internal/data/model" "geo/internal/entitys" + "geo/pkg" + "geo/tmpl/dataTemp" + "geo/tmpl/errcode" "github.com/go-viper/mapstructure/v2" "github.com/gofiber/fiber/v2" + "io" + "strconv" + "strings" + "time" + "xorm.io/builder" ) type ProductSourceService struct { - cfg *config.Config - productImpl *impl.ProductImpl - authBiz *biz.AuthBiz - aiBiz *biz.AiBiz - productBiz *biz.ProductBiz + cfg *config.Config + productImpl *impl.ProductImpl + productSourceImpl *impl.ProductSourceImpl + authBiz *biz.AuthBiz + aiBiz *biz.AiBiz + productBiz *biz.ProductBiz } -func NewProductSourceService(cfg *config.Config, ProductImpl *impl.ProductImpl, authBiz *biz.AuthBiz, aiBiz *biz.AiBiz, productBiz *biz.ProductBiz) *ProductSourceService { +func NewProductSourceService(cfg *config.Config, ProductImpl *impl.ProductImpl, authBiz *biz.AuthBiz, aiBiz *biz.AiBiz, productBiz *biz.ProductBiz, productSource *impl.ProductSourceImpl) *ProductSourceService { return &ProductSourceService{ - cfg: cfg, - productImpl: ProductImpl, - authBiz: authBiz, - aiBiz: aiBiz, - productBiz: productBiz, + cfg: cfg, + productImpl: ProductImpl, + authBiz: authBiz, + aiBiz: aiBiz, + productBiz: productBiz, + productSourceImpl: productSource, } } @@ -36,14 +47,19 @@ func (p *ProductSourceService) Create(c *fiber.Ctx, req *entitys.ProductSourceCr if err != nil { return err } - brandInfo, err := p.productBiz.GetBrandInfo(c.UserContext(), req.Id) + product, err := p.productBiz.GetProduct(c.UserContext(), req.ProductId) + if err != nil { + return err + } + var brandInfo entitys.BrandInfo + err = mapstructure.Decode(product, &brandInfo) if err != nil { return err } BotChatMes := &entitys.BotChat{ Question: req.Ques, ArticleType: req.ArticleType, - BrandInfo: brandInfo, + BrandInfo: &brandInfo, } mes := p.aiBiz.CreateArticlePrompt(c.UserContext(), BotChatMes) content, err := ai_tool.NewHsyq().RequestHsyqBot(c.UserContext(), p.cfg.Hsyq.ApiKey, "bot-20260413000114-8bw62", mes) @@ -51,39 +67,130 @@ func (p *ProductSourceService) Create(c *fiber.Ctx, req *entitys.ProductSourceCr return err } var resp entitys.BotChatResponse - if err := json.Unmarshal(content, &resp); err != nil { + if err := json.Unmarshal([]byte(*content), &resp); err != nil { + return errcode.SysErr("文章生成失败,请重试") + } + docxUrl, err := p.productBiz.CreateAndUploadArticle(c.UserContext(), resp.Content, product) + if err != nil { + return err + } + add := &model.ProductSource{ + Title: resp.Title, + Ques: req.Ques, + SourceType: 1, + ProductId: product.ID, + ArticleType: req.ArticleType, + SourceURL: docxUrl, + RecommendPlatfrom: pkg.JsonStringIgonErr(resp.RecommendPlatform), + CreatedAt: time.Now(), + } + if resp.Tag != nil { + add.Tag = strings.Join(resp.Tag, ",") + } + err = p.productBiz.AddSource(c.UserContext(), add) + if err != nil { return err } - return nil } func (p *ProductSourceService) List(c *fiber.Ctx, req *entitys.ProductSourceListRequest) error { // 验证token - _, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) + _, err := p.authBiz.ValidateAccessToken(c.UserContext(), req.AccessToken) + if err != nil { + return err + } + page := req.Page + if page < 1 { + page = 1 + } + pageSize := req.PageSize + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 100 { + pageSize = 100 + } + var list []*model.ProductSource + cond := builder.NewCond(). + And(builder.Eq{"product_id": req.ProductId}). + And(builder.Eq{"status": 1}) + total, err := p.productSourceImpl.GetListToStruct(c.UserContext(), &cond, &dataTemp.ReqPageBo{Page: page, Limit: pageSize}, &list, "id desc") if err != nil { return err } + return pkg.SuccessWithPageMsg(c, list, total.Total, page, pageSize) +} + +func (p *ProductSourceService) UploadSource(c *fiber.Ctx) error { + access := c.FormValue("access_token", "") + if access == "" { + return errcode.ParamErr("access_token未找到") + } + sourceId := c.FormValue("source_id") + if sourceId == "" { + return errcode.ParamErr("source_id未找到哦") + } + sourceIdInt, err := strconv.Atoi(sourceId) + if err != nil { + return errcode.ParamErr("source_id未必须是数字") + } + // 验证token + _, err = p.authBiz.ValidateAccessToken(c.UserContext(), access) + if err != nil { + return err + } + + fileHeader, err := c.FormFile("file") + if err != nil { + return errcode.ParamErr("未找到上传文件") + } + file, err := fileHeader.Open() + if err != nil { + return errcode.ParamErrf("无法打开文件:%S", err.Error()) + } + defer file.Close() + + fileBytes, err := io.ReadAll(file) + if err != nil { + return errcode.ParamErrf("读取文件失败:%s", err.Error()) + } + fileName := fmt.Sprintf("article_%d_%d.docx", sourceIdInt, time.Now().Unix()) + url, err := p.productBiz.SourceUpload(c.UserContext(), fileBytes, fileName) + if err != nil { + return err + } + err = p.productBiz.UpdateSourceById(c.UserContext(), int32(sourceIdInt), &model.ProductSource{SourceURL: url}) + if err != nil { + return errcode.ParamErrf("文件上传失败") + } return nil } -func (p *ProductSourceService) Upload(c *fiber.Ctx, req *entitys.ProductSourceUploadRequest) error { +func (p *ProductSourceService) Update(c *fiber.Ctx, req *entitys.ProductSourceUpdateRequest) error { // 验证token - _, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) + _, err := p.authBiz.ValidateAccessToken(c.UserContext(), req.AccessToken) if err != nil { return err } + var update = &model.ProductSource{} + if req.Title != "" { + update.Title = req.Title + } + if len(req.Tag) > 0 { + update.Tag = strings.Join(req.Tag, ",") + } - return nil + return p.productBiz.UpdateSourceById(c.UserContext(), req.SourceId, update) } func (p *ProductSourceService) Del(c *fiber.Ctx, req *entitys.ProductSourceDelRequest) error { // 验证token - _, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) + _, err := p.authBiz.ValidateAccessToken(c.UserContext(), req.AccessToken) if err != nil { return err } - return nil + return p.productBiz.DelSourceById(c.UserContext(), req.SourceId) } diff --git a/pkg/func.go b/pkg/func.go index a32cddb..00e20b6 100644 --- a/pkg/func.go +++ b/pkg/func.go @@ -342,3 +342,14 @@ func StructToMap(v any) (map[string]any, error) { err = json.Unmarshal(b, &m) return m, err } + +// ReadDocxToBytes 读取 docx 文件并返回 []byte +func ReadDocxToBytes(filePath string) ([]byte, error) { + // 读取文件 + data, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("读取文件失败: %w", err) + } + + return data, nil +} diff --git a/pkg/plugin.go b/pkg/plugin.go index 4e15eca..63c3bdb 100644 --- a/pkg/plugin.go +++ b/pkg/plugin.go @@ -114,27 +114,27 @@ func CopyImageToDoc(docPath, imgPath string) error { // // 返回: // - error: 错误信息,成功返回 -func Md2wordFix(mdFile, outPutDIr string, img []string) error { +func Md2wordFix(mdFile, outPutDIr string, img []string) (string, error) { baseDir, err := os.Getwd() if err != nil { - return fmt.Errorf("获取工作目录失败: %w", err) + return "", fmt.Errorf("获取工作目录失败: %w", err) } exePath := filepath.Join(baseDir, "plugins", "md2word_fix.exe") // 3. 检查 exe 是否存在 if _, err = os.Stat(exePath); os.IsNotExist(err) { - return fmt.Errorf("exe不存在: %s", exePath) + return "", fmt.Errorf("exe不存在: %s", exePath) } // 4. 检查 Markdown 文件是否存在 if _, err = os.Stat(mdFile); os.IsNotExist(err) { - return fmt.Errorf("Markdown文件不存在: %s", mdFile) + return "", fmt.Errorf("Markdown文件不存在: %s", mdFile) } // 5. 确保输出目录存在 if err = os.MkdirAll(outPutDIr, 0755); err != nil { - return fmt.Errorf("创建输出目录失败: %w", err) + return "", fmt.Errorf("创建输出目录失败: %w", err) } //7. 构建命令行参数 @@ -159,7 +159,7 @@ func Md2wordFix(mdFile, outPutDIr string, img []string) error { cmd := exec.Command(exePath, args...) output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("执行失败: %v", err) + return "", fmt.Errorf("执行失败: %v", err) } var result struct { @@ -169,12 +169,12 @@ func Md2wordFix(mdFile, outPutDIr string, img []string) error { } if err = json.Unmarshal(output, &result); err != nil { - return fmt.Errorf("解析结果失败: %v, 输出: %s", err, string(output)) + return "", fmt.Errorf("解析结果失败: %v, 输出: %s", err, string(output)) } if !result.Success { - return fmt.Errorf("插入图片失败: %s", result.Error) + return "", fmt.Errorf("插入图片失败: %s", result.Error) } - return nil + return result.Content, nil }