This commit is contained in:
parent
7eda42b8a7
commit
264788d3ec
|
|
@ -42,7 +42,7 @@ func InitializeApp(configConfig *config.Config, allLogger log.AllLogger) (*serve
|
|||
}
|
||||
productBiz := biz.NewProductBiz(productImpl, productSourceImpl, configConfig, oss)
|
||||
productService := service.NewProductService(configConfig, productImpl, authBiz, productBiz)
|
||||
aiBiz := biz.NewAiBiz(platImpl)
|
||||
aiBiz := biz.NewAiBiz(platImpl, articleTypeImpl)
|
||||
productSourceService := service.NewProductSourceService(configConfig, productImpl, authBiz, aiBiz, productBiz, productSourceImpl, publishBiz, articleTypeImpl)
|
||||
appModule := router.NewAppModule(configConfig, appService, loginService, publishService, productService, productSourceService)
|
||||
routerServer := router.NewRouterServer(appModule)
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
[{"name":"webId","value":"f840cc8b10e0a01b0bdb839482af19d1","domain":".xiaohongshu.com","path":"/","expires":1807155738,"size":37,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"gid","value":"yjfKDJiJ0J7SyjfKDJi8Dqf884ufUMYf3MlIVqfYTYl3D3q8Svu0VW888yyYW4Y8JD0j8Yd2","domain":".xiaohongshu.com","path":"/","expires":1810372429.581067,"size":75,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"x-user-id-creator.xiaohongshu.com","value":"65d74a4c0000000005032a98","domain":".xiaohongshu.com","path":"/","expires":1810179755.091531,"size":57,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"galaxy_creator_session_id","value":"nWC5PzTFCCSJsLYiRFLFORIWUoUf5c4Egr3v","domain":".xiaohongshu.com","path":"/","expires":1778211755.091586,"size":61,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"acw_tc","value":"0a00075317759153837631021e2d5e31fa378daf9c77578ea082108e84e3cf","domain":"creator.xiaohongshu.com","path":"/","expires":1775916920.153024,"size":68,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"loadts","value":"1775915120761","domain":".xiaohongshu.com","path":"/","expires":1807451120,"size":19,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"websectiga","value":"cffd9dcea65962b05ab048ac76962acee933d26157113bb213105a116241fa6c","domain":".xiaohongshu.com","path":"/","expires":1776174321,"size":74,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"xsecappid","value":"ugc","domain":".xiaohongshu.com","path":"/","expires":1807451120,"size":12,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"customerClientId","value":"231145420384063","domain":".xiaohongshu.com","path":"/","expires":1810179755.091552,"size":31,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"customer-sso-sid","value":"68c5176262287866115194944uxxdffhcemiwvwt","domain":".xiaohongshu.com","path":"/","expires":1776224554.091467,"size":56,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"ets","value":"1775619738206","domain":".xiaohongshu.com","path":"/","expires":1778211738.206388,"size":16,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"a1","value":"19d6b2f0b04zdps8dison8k8oibcyzaju3ji7d03d30000118748","domain":".xiaohongshu.com","path":"/","expires":1807155738,"size":54,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"sec_poison_id","value":"d361d271-b86e-4565-8105-4f11e54cd97c","domain":".xiaohongshu.com","path":"/","expires":1775915726,"size":49,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"access-token-creator.xiaohongshu.com","value":"customer.creator.AT-68c517626228786611519495vggizcwmifuvnaaw","domain":".xiaohongshu.com","path":"/","expires":1778211754.091569,"size":96,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"galaxy.creator.beaker.session.id","value":"1775619757404082990054","domain":".xiaohongshu.com","path":"/","expires":1778211755.091605,"size":54,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443}]
|
||||
|
|
@ -3,22 +3,22 @@ package biz
|
|||
import (
|
||||
"context"
|
||||
"geo/internal/data/impl"
|
||||
"geo/internal/data/model"
|
||||
"geo/internal/entitys"
|
||||
"geo/pkg"
|
||||
|
||||
volmodle "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
|
||||
"github.com/volcengine/volcengine-go-sdk/volcengine"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type AiBiz struct {
|
||||
platImpl *impl.PlatImpl
|
||||
platImpl *impl.PlatImpl
|
||||
articleImpl *impl.ArticleTypeImpl
|
||||
}
|
||||
|
||||
func NewAiBiz(platImpl *impl.PlatImpl) *AiBiz {
|
||||
func NewAiBiz(platImpl *impl.PlatImpl, articleImpl *impl.ArticleTypeImpl) *AiBiz {
|
||||
return &AiBiz{
|
||||
platImpl: platImpl,
|
||||
platImpl: platImpl,
|
||||
articleImpl: articleImpl,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,32 +31,57 @@ func (a *AiBiz) CreateArticlePrompt(ctx context.Context, data *entitys.BotChat)
|
|||
},
|
||||
},
|
||||
}
|
||||
var plats []*model.Plat
|
||||
cond := builder.NewCond().
|
||||
And(builder.Eq{"plat_type": 1}).
|
||||
And(builder.Eq{"status": 1})
|
||||
_, err := a.platImpl.GetListToStruct(ctx, &cond, nil, &plats, "id asc")
|
||||
if err != nil {
|
||||
return mes
|
||||
}
|
||||
var platList = &entitys.PlatList{
|
||||
Desc: "平台列表",
|
||||
PlatItem: make([]*entitys.PlatItem, 0, len(plats)),
|
||||
}
|
||||
for _, plat := range plats {
|
||||
platList.PlatItem = append(platList.PlatItem, &entitys.PlatItem{
|
||||
Platform: plat.Name,
|
||||
PlatformIndex: plat.Index,
|
||||
})
|
||||
}
|
||||
if len(platList.PlatItem) > 0 {
|
||||
mes = append(mes, &volmodle.ChatCompletionMessage{
|
||||
Role: volmodle.ChatMessageRoleSystem,
|
||||
Content: &volmodle.ChatCompletionMessageContent{
|
||||
StringValue: volcengine.String(pkg.JsonStringIgonErr(platList)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return mes
|
||||
//var plats []*model.Plat
|
||||
//cond := builder.NewCond().
|
||||
// And(builder.Eq{"plat_type": 1}).
|
||||
// And(builder.Eq{"status": 1})
|
||||
//_, err := a.platImpl.GetListToStruct(ctx, &cond, nil, &plats, "id asc")
|
||||
//if err != nil {
|
||||
// return mes
|
||||
//}
|
||||
//var platList = &entitys.PlatList{
|
||||
// Desc: "平台列表",
|
||||
// PlatItem: make([]*entitys.PlatItem, 0, len(plats)),
|
||||
//}
|
||||
//for _, plat := range plats {
|
||||
// platList.PlatItem = append(platList.PlatItem, &entitys.PlatItem{
|
||||
// Platform: plat.Name,
|
||||
// PlatformIndex: plat.Index,
|
||||
// })
|
||||
//}
|
||||
//if len(platList.PlatItem) > 0 {
|
||||
// mes = append(mes, &volmodle.ChatCompletionMessage{
|
||||
// Role: volmodle.ChatMessageRoleAssistant,
|
||||
// Content: &volmodle.ChatCompletionMessageContent{
|
||||
// StringValue: volcengine.String(pkg.JsonStringIgonErr(platList)),
|
||||
// },
|
||||
// })
|
||||
//}
|
||||
//var articleType []*model.ArticleType
|
||||
//cond := builder.NewCond().
|
||||
// And(builder.Eq{"status": 1})
|
||||
//_, err := a.articleImpl.GetListToStruct(ctx, &cond, nil, &articleType, "id asc")
|
||||
//if err != nil {
|
||||
// return mes
|
||||
//}
|
||||
//var articleList = &entitys.ArticleTypeList{
|
||||
// Desc: "文章类型以及描述",
|
||||
// ArticleItem: make([]*entitys.ArticleItem, 0, len(articleType)),
|
||||
//}
|
||||
//for _, article := range articleType {
|
||||
// articleList.ArticleItem = append(articleList.ArticleItem, &entitys.ArticleItem{
|
||||
// ArticleType: article.ArticleName,
|
||||
// TypeDesc: article.Desc,
|
||||
// })
|
||||
//}
|
||||
//if len(articleList.ArticleItem) > 0 {
|
||||
// mes = append(mes, &volmodle.ChatCompletionMessage{
|
||||
// Role: volmodle.ChatMessageRoleAssistant,
|
||||
// Content: &volmodle.ChatCompletionMessageContent{
|
||||
// StringValue: volcengine.String(pkg.JsonStringIgonErr(articleList)),
|
||||
// },
|
||||
// })
|
||||
//}
|
||||
return mes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,14 +91,14 @@ func (p *ProductBiz) AddSource(ctx context.Context, source *model.ProductSource)
|
|||
}
|
||||
|
||||
func (p *ProductBiz) SourceUpload(ctx context.Context, file []byte, fileName string) (string, error) {
|
||||
url, err := p.oss.UploadBytes(p.cfg.Oss.FilePath+fileName, file)
|
||||
url, err := p.oss.UploadBytes(p.cfg.Oss.FilePath+"source/"+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 {
|
||||
func (p *ProductBiz) UpdateSourceById(ctx context.Context, id int32, update any) error {
|
||||
|
||||
return p.productSourceImpl.UpdateByKey(ctx, p.productSourceImpl.PrimaryKey(), id, update)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,17 @@ func (b *PublishBiz) GetTaskByRequestID(ctx context.Context, requestID string) (
|
|||
return b.publishImpl.GetOneWithPlat(ctx, &cond)
|
||||
}
|
||||
|
||||
func (b *PublishBiz) GetTaskByPublishId(ctx context.Context, id int) (*model.Publish, error) {
|
||||
var data model.Publish
|
||||
cond := builder.NewCond().
|
||||
And(builder.Eq{"id": id})
|
||||
err := b.publishImpl.GetOneBySearchStruct(ctx, &cond, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func (b *PublishBiz) UpdatePublishStatus(ctx context.Context, requestID string, status int, msg string) error {
|
||||
return b.publishImpl.UpdateStatus(ctx, requestID, status, msg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const TableNameArticalType = "article_type"
|
|||
// ArticalType mapped from table <artical_type>
|
||||
type ArticleType struct {
|
||||
ID int32 `gorm:"column:id;primaryKey" json:"id"`
|
||||
ArticleName string `gorm:"column:artical_name;not null" json:"artical_name"`
|
||||
ArticleName string `gorm:"column:article_name;not null" json:"article_name"`
|
||||
Desc string `gorm:"column:desc;not null" json:"desc"`
|
||||
Status int32 `gorm:"column:status;not null;default:1" json:"status"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type Publish struct {
|
|||
URL string `gorm:"column:url;not null;comment:资源文件下载地址" json:"url"` // 资源文件下载地址
|
||||
PublishTime time.Time `gorm:"column:publish_time;not null;comment:发布时间" json:"publish_time"` // 发布时间
|
||||
Img string `gorm:"column:img;not null" json:"img"`
|
||||
LogUrl string `gorm:"column:log_url;not null" json:"log_url"`
|
||||
Status int32 `gorm:"column:status;not null;default:1;comment:1:待发布,2:发布中,3:发布失败,4:发布成功" json:"status"` // 1:待发布,2:发布中,3:发布失败,4:发布成功
|
||||
CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"create_time"` // 创建时间
|
||||
Msg string `gorm:"column:msg" json:"msg"`
|
||||
|
|
|
|||
|
|
@ -63,10 +63,20 @@ type BrandInfo struct {
|
|||
}
|
||||
|
||||
type PlatList struct {
|
||||
Desc string `json:"question"`
|
||||
Desc string `json:"desc"`
|
||||
PlatItem []*PlatItem `json:"plat_item"`
|
||||
}
|
||||
|
||||
type ArticleTypeList struct {
|
||||
Desc string `json:"desc"`
|
||||
ArticleItem []*ArticleItem `json:"article_item"`
|
||||
}
|
||||
|
||||
type ArticleItem struct {
|
||||
ArticleType string `json:"article_type"`
|
||||
TypeDesc string `json:"type_esc"`
|
||||
}
|
||||
|
||||
type PlatItem struct {
|
||||
Platform string `json:"platform"`
|
||||
PlatformIndex string `json:"platform_index"`
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
package entitys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
LoginAppRequest struct {
|
||||
Secret string `json:"secret" validate:"required" zh:"密钥"`
|
||||
|
|
@ -185,6 +191,40 @@ type (
|
|||
AccessToken string `json:"access_token" validate:"required" zh:"access_token"`
|
||||
SourceId int32 `json:"source_id" validate:"required" zh:"资源id"`
|
||||
Plat []string `json:"plat" validate:"required" zh:"平台"`
|
||||
PublishTime string `json:"publish_time" validate:"required" zh:"发布时间"`
|
||||
PublishTime MyTime `json:"publish_time" validate:"required" zh:"发布时间"`
|
||||
}
|
||||
)
|
||||
|
||||
type MyTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (t *MyTime) UnmarshalJSON(data []byte) error {
|
||||
str := strings.Trim(string(data), `"`)
|
||||
if str == "" || str == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析为本地时区(东八区)
|
||||
loc, _ := time.LoadLocation("Asia/Shanghai") // 或 "Local"
|
||||
|
||||
formats := []string{
|
||||
"2006-01-02T15:04",
|
||||
"2006-01-02T15:04:05",
|
||||
time.RFC3339,
|
||||
}
|
||||
|
||||
for _, format := range formats {
|
||||
if parsed, err := time.ParseInLocation(format, str, loc); err == nil {
|
||||
t.Time = parsed
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("无法解析时间: %s", str)
|
||||
}
|
||||
|
||||
func (t *MyTime) MarshalJSON() ([]byte, error) {
|
||||
// 输出时也使用相同格式和时区
|
||||
return []byte(`"` + t.Time.Format("2006-01-02T15:04") + `"`), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"geo/internal/publisher"
|
||||
"geo/pkg"
|
||||
"geo/utils"
|
||||
"geo/utils/utils_oss"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
|
@ -73,25 +74,17 @@ func GetPublishManager(config *config.Config, db *utils.Db, publicBiz *biz.Publi
|
|||
}
|
||||
|
||||
// getTaskLogger 获取任务专属日志记录器
|
||||
func (pm *PublishManager) getTaskLogger(requestID string) (*log.Logger, *os.File, error) {
|
||||
if requestID == "" {
|
||||
return nil, nil, fmt.Errorf("requestID不能为空")
|
||||
}
|
||||
|
||||
func (pm *PublishManager) getTaskLogger(publishID int, requestID string) (*log.Logger, *os.File, string, error) {
|
||||
logsDir := pm.Conf.Sys.LogsDir
|
||||
if logsDir == "" {
|
||||
logsDir = "./logs"
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(logsDir, 0755); err != nil {
|
||||
return nil, nil, fmt.Errorf("创建日志目录失败: %v", err)
|
||||
return nil, nil, "", fmt.Errorf("创建日志目录失败: %v", err)
|
||||
}
|
||||
|
||||
logPath := filepath.Join(logsDir, fmt.Sprintf("%s.log", requestID))
|
||||
logPath := filepath.Join(logsDir, fmt.Sprintf("%d_%s.log", publishID, requestID))
|
||||
|
||||
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("创建日志文件失败: %v", err)
|
||||
return nil, nil, "", fmt.Errorf("创建日志文件失败: %v", err)
|
||||
}
|
||||
|
||||
multiWriter := io.MultiWriter(logFile, os.Stdout)
|
||||
|
|
@ -101,7 +94,7 @@ func (pm *PublishManager) getTaskLogger(requestID string) (*log.Logger, *os.File
|
|||
taskLogger.Printf("任务开始 | RequestID: %s | 时间: %s", requestID, time.Now().Format("2006-01-02 15:04:05.000"))
|
||||
taskLogger.Printf(strings.Repeat("=", 80))
|
||||
|
||||
return taskLogger, logFile, nil
|
||||
return taskLogger, logFile, logPath, nil
|
||||
}
|
||||
|
||||
// Start 启动自动发布(支持并发worker数量)
|
||||
|
|
@ -317,7 +310,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
|||
default:
|
||||
}
|
||||
|
||||
taskLogger, logFile, err := pm.getTaskLogger(publishData.RequestID)
|
||||
taskLogger, logFile, logPath, err := pm.getTaskLogger(publishData.ID, publishData.RequestID)
|
||||
if err != nil {
|
||||
log.Printf("[任务 %s] 创建日志文件失败: %v,使用全局日志", publishData.RequestID, err)
|
||||
taskLogger = log.Default()
|
||||
|
|
@ -330,7 +323,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
|||
if r := recover(); r != nil {
|
||||
errMsg := fmt.Sprintf("任务执行发生panic: %v", r)
|
||||
taskLogger.Printf("❌ CRITICAL: %s", errMsg)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, errMsg)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, errMsg, "")
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
@ -338,7 +331,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
|||
|
||||
params, sourceUrl := pm.extractTaskParams(publishData, taskLogger)
|
||||
if params == nil {
|
||||
pm.updatePublishStatus(publishData, StatusFailed, "提取任务参数失败")
|
||||
pm.updatePublishStatus(publishData, StatusFailed, "提取任务参数失败", "")
|
||||
return &SingleResult{Success: false, Message: "提取任务参数失败", RequestId: publishData.RequestID}
|
||||
}
|
||||
params.Headless = headless
|
||||
|
|
@ -347,7 +340,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
|||
if publisherClass == nil {
|
||||
errMsg := fmt.Sprintf("不支持的平台: %s", params.PlatIndex)
|
||||
taskLogger.Printf("[任务 %s] ❌ %s", publishData.RequestID, errMsg)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, errMsg)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, errMsg, "")
|
||||
return &SingleResult{Success: false, Message: errMsg, RequestId: publishData.RequestID}
|
||||
}
|
||||
|
||||
|
|
@ -355,7 +348,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
|||
if err != nil {
|
||||
errMsg := fmt.Sprintf("准备文件失败: %v", err)
|
||||
taskLogger.Printf("[任务 %s] ❌ %s", publishData.RequestID, errMsg)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, errMsg)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, errMsg, "")
|
||||
return &SingleResult{Success: false, Message: errMsg, RequestId: publishData.RequestID}
|
||||
}
|
||||
defer pm.cleanupFiles(docPath, imgPath, taskLogger, publishData.RequestID)
|
||||
|
|
@ -364,7 +357,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
|||
if err != nil {
|
||||
errMsg := fmt.Sprintf("提取文档内容失败: %v", err)
|
||||
taskLogger.Printf("[任务 %s] ❌ %s", publishData.RequestID, errMsg)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, errMsg)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, errMsg, "")
|
||||
return &SingleResult{Success: false, Message: errMsg, RequestId: publishData.RequestID}
|
||||
}
|
||||
params.ImagePath = imgPath
|
||||
|
|
@ -374,17 +367,37 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
|||
taskLogger.Printf("[任务 %s] 开始执行发布...", publishData.RequestID)
|
||||
success, message := pub.PublishNote()
|
||||
|
||||
if success {
|
||||
taskLogger.Printf("[任务 %s] ✅ 发布成功: %s", publishData.RequestID, message)
|
||||
pm.updatePublishStatus(publishData, StatusSuccess, message)
|
||||
} else {
|
||||
taskLogger.Printf("[任务 %s] ❌ 发布失败: %s", publishData.RequestID, message)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, message)
|
||||
}
|
||||
|
||||
taskLogger.Printf(strings.Repeat("=", 80))
|
||||
taskLogger.Printf("任务结束 | RequestID: %s | 结果: %v", publishData.RequestID, success)
|
||||
return &SingleResult{Success: success, Message: message, RequestId: publishData.RequestID}
|
||||
|
||||
res := &SingleResult{Success: success, Message: message, RequestId: publishData.RequestID}
|
||||
pm.uploadToOss(ctx, logPath, fmt.Sprintf("%slog/%d_%s.log", pm.Conf.Oss.FilePath, publishData.ID, publishData.RequestID))
|
||||
|
||||
url, err := pm.uploadToOss(ctx, logPath, fmt.Sprintf("%s%d_%s.log", pm.Conf.Oss.FilePath, publishData.ID, publishData.RequestID))
|
||||
if err != nil {
|
||||
taskLogger.Printf("日志上传失败")
|
||||
}
|
||||
if success {
|
||||
taskLogger.Printf("[任务 %s] ✅ 发布成功: %s", publishData.RequestID, message)
|
||||
pm.updatePublishStatus(publishData, StatusSuccess, message, url)
|
||||
} else {
|
||||
taskLogger.Printf("[任务 %s] ❌ 发布失败: %s", publishData.RequestID, message)
|
||||
pm.updatePublishStatus(publishData, StatusFailed, message, url)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (pm *PublishManager) uploadToOss(ctx context.Context, filePath string, path string) (url string, err error) {
|
||||
client, err := utils_oss.NewClient(pm.Conf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fileBytes, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
url, err = client.UploadBytes(path, fileBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// RetryTask 重试任务(非无头模式)
|
||||
|
|
@ -533,15 +546,15 @@ func (pm *PublishManager) extractContent(docPath string, publisherClass *publish
|
|||
}
|
||||
|
||||
// updatePublishStatus 更新发布状态
|
||||
func (pm *PublishManager) updatePublishStatus(publishData *entitys.PublishTaskDetail, status int, message string) error {
|
||||
func (pm *PublishManager) updatePublishStatus(publishData *entitys.PublishTaskDetail, status int, message string, log_url string) error {
|
||||
if publishData.ID == 0 {
|
||||
return fmt.Errorf("id不能为空")
|
||||
}
|
||||
var err error
|
||||
if message != "" {
|
||||
_, err = pm.db.Execute("UPDATE publish SET status = ?, msg = ? WHERE id=?", status, message, publishData.ID)
|
||||
_, err = pm.db.Execute("UPDATE publish SET status = ?, msg = ?,log_url=? WHERE id=?", status, message, log_url, publishData.ID)
|
||||
} else {
|
||||
_, err = pm.db.Execute("UPDATE publish SET status = ? WHERE id=?", status, publishData.ID)
|
||||
_, err = pm.db.Execute("UPDATE publish SET status = ?,log_url=? WHERE id=?", status, publishData.ID, log_url, log_url)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("更新发布状态失败: id=%s, status=%d, error=%v", publishData.ID, status, err)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"geo/internal/config"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -91,13 +91,15 @@ func (p *BaijiahaoPublisher) doPublish() (bool, string) {
|
|||
name string
|
||||
fn func() error
|
||||
}{
|
||||
{"修改文档名称", p.RenameDocument},
|
||||
{"点击hover", p.clickHoverButton},
|
||||
{"输入内容", p.inputContent},
|
||||
{"输入标题", p.inputTitle},
|
||||
//{"输入标题", p.inputTitle},
|
||||
{"设置封面", p.uploadImage},
|
||||
{"点击发布按钮", p.clickPublish},
|
||||
//{"处理确认弹窗", p.handleConfirmModal},
|
||||
}
|
||||
defer os.Remove(p.SourcePath)
|
||||
//https://baijiahao.baidu.com/builder/rc/clue?aside=0&footer=true&from=news&firstPublish=undefined&word_bag_id=null
|
||||
for _, step := range steps {
|
||||
if err := step.fn(); err != nil {
|
||||
|
|
@ -182,7 +184,13 @@ func (p *BaijiahaoPublisher) inputTitle() error {
|
|||
currentTitle, _ := titleInput.Text()
|
||||
if currentTitle != "" {
|
||||
p.LogInfo(fmt.Sprintf("清空当前标题: %s", currentTitle[:min(50, len(currentTitle))]))
|
||||
p.ClearContentEditable(titleInput)
|
||||
p.SleepMs(200)
|
||||
err := titleInput.SelectAllText()
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
p.SleepMs(200)
|
||||
titleInput.MustInput(p.Title)
|
||||
p.SleepMs(200)
|
||||
}
|
||||
titleInput.Input(p.Title)
|
||||
|
|
@ -297,39 +305,39 @@ func (p *BaijiahaoPublisher) inputContent() error {
|
|||
|
||||
// 5. 等待导入成功
|
||||
// 提取文件名(不含路径)
|
||||
fileName := filepath.Base(p.SourcePath)
|
||||
|
||||
// 等待导入成功的提示
|
||||
for i := 0; i < 30; i++ {
|
||||
// 查找包含文件名的成功提示
|
||||
successMsg, err := p.Page.ElementX(fmt.Sprintf("//*[contains(text(), '%s') and (contains(text(), '成功') or contains(text(), '导入'))]", fileName))
|
||||
if err == nil && successMsg != nil {
|
||||
text, _ := successMsg.Text()
|
||||
p.LogInfo(fmt.Sprintf("文档导入成功: %s", text))
|
||||
p.SleepMs(2000) // 等待内容加载完成
|
||||
return nil
|
||||
}
|
||||
|
||||
// 通用成功提示查找
|
||||
successMsg, err = p.Page.ElementX("//*[contains(text(), '导入成功')]")
|
||||
if err == nil && successMsg != nil {
|
||||
text, _ := successMsg.Text()
|
||||
p.LogInfo(fmt.Sprintf("文档导入成功: %s", text))
|
||||
p.SleepMs(2000)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 查找是否有错误提示
|
||||
errorMsg, err := p.Page.ElementX("//*[contains(text(), '失败') or contains(text(), '错误')]")
|
||||
if err == nil && errorMsg != nil {
|
||||
text, _ := errorMsg.Text()
|
||||
if strings.Contains(text, fileName) || strings.Contains(text, "导入") {
|
||||
return fmt.Errorf("文档导入失败: %s", text)
|
||||
}
|
||||
}
|
||||
|
||||
p.SleepMs(500)
|
||||
}
|
||||
//fileName := filepath.Base(p.SourcePath)
|
||||
//
|
||||
//// 等待导入成功的提示
|
||||
//for i := 0; i < 30; i++ {
|
||||
// // 查找包含文件名的成功提示
|
||||
// successMsg, err := p.Page.ElementX(fmt.Sprintf("//*[contains(text(), '%s') and (contains(text(), '成功') or contains(text(), '导入'))]", fileName))
|
||||
// if err == nil && successMsg != nil {
|
||||
// text, _ := successMsg.Text()
|
||||
// p.LogInfo(fmt.Sprintf("文档导入成功: %s", text))
|
||||
// p.SleepMs(2000) // 等待内容加载完成
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// // 通用成功提示查找
|
||||
// successMsg, err = p.Page.ElementX("//*[contains(text(), '导入成功')]")
|
||||
// if err == nil && successMsg != nil {
|
||||
// text, _ := successMsg.Text()
|
||||
// p.LogInfo(fmt.Sprintf("文档导入成功: %s", text))
|
||||
// p.SleepMs(2000)
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// // 查找是否有错误提示
|
||||
// errorMsg, err := p.Page.ElementX("//*[contains(text(), '失败') or contains(text(), '错误')]")
|
||||
// if err == nil && errorMsg != nil {
|
||||
// text, _ := errorMsg.Text()
|
||||
// if strings.Contains(text, fileName) || strings.Contains(text, "导入") {
|
||||
// return fmt.Errorf("文档导入失败: %s", text)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// p.SleepMs(500)
|
||||
//}
|
||||
|
||||
// 虽然没有明确的成功提示,但等待几秒让内容加载
|
||||
p.LogInfo("等待内容加载完成...")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"geo/internal/entitys"
|
||||
"geo/pkg"
|
||||
"geo/utils/utils_oss"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -46,6 +48,7 @@ type BasePublisher struct {
|
|||
|
||||
MaxRetries int
|
||||
RetryDelay int
|
||||
ossClient *utils_oss.Client
|
||||
}
|
||||
|
||||
// taskParams 任务参数结构体
|
||||
|
|
@ -65,9 +68,6 @@ type TaskParams struct {
|
|||
|
||||
// NewBasePublisher 构造函数,增加 logger 参数
|
||||
func NewBasePublisher(ctx context.Context, task *TaskParams, config *config.Config, logger *log.Logger) *BasePublisher {
|
||||
cookiesDir := filepath.Join(config.Sys.CookiesDir, task.UserIndex)
|
||||
os.MkdirAll(cookiesDir, 0755)
|
||||
cookiesFile := filepath.Join(cookiesDir, task.PlatIndex+".json")
|
||||
|
||||
var baseLogger *log.Logger
|
||||
var logFile *os.File
|
||||
|
|
@ -87,28 +87,36 @@ func NewBasePublisher(ctx context.Context, task *TaskParams, config *config.Conf
|
|||
baseLogger = log.New(logFile, "", log.LstdFlags)
|
||||
}
|
||||
|
||||
return &BasePublisher{
|
||||
ctx: ctx,
|
||||
Headless: task.Headless,
|
||||
Title: task.Title,
|
||||
Content: task.Content,
|
||||
Tags: task.Tags,
|
||||
UserIndex: task.UserIndex,
|
||||
PlatIndex: task.PlatIndex,
|
||||
RequestID: task.RequestID,
|
||||
ImagePath: task.ImagePath,
|
||||
SourcePath: task.SourcePath,
|
||||
Logger: baseLogger,
|
||||
LogFile: logFile,
|
||||
CookiesFile: cookiesFile,
|
||||
PlatInfo: task.PublishData,
|
||||
config: config,
|
||||
LoginURL: task.PublishData.LoginUrl,
|
||||
EditorURL: task.PublishData.EditUrl,
|
||||
LoginedURL: task.PublishData.LoginedUrl,
|
||||
MaxRetries: 3,
|
||||
RetryDelay: 200,
|
||||
base := &BasePublisher{
|
||||
ctx: ctx,
|
||||
Headless: task.Headless,
|
||||
Title: task.Title,
|
||||
Content: task.Content,
|
||||
Tags: task.Tags,
|
||||
UserIndex: task.UserIndex,
|
||||
PlatIndex: task.PlatIndex,
|
||||
RequestID: task.RequestID,
|
||||
ImagePath: task.ImagePath,
|
||||
SourcePath: task.SourcePath,
|
||||
Logger: baseLogger,
|
||||
LogFile: logFile,
|
||||
PlatInfo: task.PublishData,
|
||||
config: config,
|
||||
LoginURL: task.PublishData.LoginUrl,
|
||||
EditorURL: task.PublishData.EditUrl,
|
||||
LoginedURL: task.PublishData.LoginedUrl,
|
||||
MaxRetries: 3,
|
||||
RetryDelay: 200,
|
||||
}
|
||||
|
||||
base.CookiesFile = filepath.Join(base.cookiesDir(), task.PlatIndex+".json")
|
||||
return base
|
||||
}
|
||||
|
||||
func (b *BasePublisher) cookiesDir() string {
|
||||
dir := filepath.Join(b.config.Sys.CookiesDir, b.UserIndex)
|
||||
os.MkdirAll(dir, 0755)
|
||||
return dir
|
||||
}
|
||||
|
||||
func (b *BasePublisher) SetupDriver() error {
|
||||
|
|
@ -185,21 +193,30 @@ func (b *BasePublisher) SaveCookies() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.Marshal(cookies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(b.CookiesFile, data, 0644)
|
||||
}
|
||||
|
||||
func (b *BasePublisher) LoadCookies() error {
|
||||
data, err := os.ReadFile(b.CookiesFile)
|
||||
os.WriteFile(b.CookiesFile, data, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.uploadToOss(data)
|
||||
}
|
||||
|
||||
func (b *BasePublisher) LoadCookies() error {
|
||||
var data []byte
|
||||
if _, err := os.Stat(b.CookiesFile); os.IsNotExist(err) {
|
||||
_err := b.getCookieFromUrl()
|
||||
if _err != nil {
|
||||
return _err
|
||||
}
|
||||
}
|
||||
data, err := os.ReadFile(b.CookiesFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cookies []*proto.NetworkCookieParam
|
||||
if err := json.Unmarshal(data, &cookies); err != nil {
|
||||
return err
|
||||
|
|
@ -430,3 +447,76 @@ func (b *BasePublisher) SafeElementInParent(parent *rod.Element, selector string
|
|||
}
|
||||
return children[0], nil
|
||||
}
|
||||
|
||||
// RenameDocument 将指定绝对路径的文档更名为新名称(不含后缀)
|
||||
// filePath: 文档的绝对路径
|
||||
// newName: 希望的新文件名(不含后缀,例如 "newfile")
|
||||
// 返回值: 新文件的绝对路径, 错误信息
|
||||
func (b *BasePublisher) RenameDocument() error {
|
||||
// 检查原文件是否存在
|
||||
if _, err := os.Stat(b.SourcePath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("文件不存在: %s", b.SourcePath)
|
||||
}
|
||||
|
||||
// 获取原文件的扩展名
|
||||
ext := filepath.Ext(b.SourcePath)
|
||||
|
||||
// 如果新文件名已经包含扩展名,则不再添加
|
||||
var fullNewName string
|
||||
if strings.HasSuffix(b.Title, ext) {
|
||||
fullNewName = b.Title
|
||||
} else {
|
||||
fullNewName = b.Title + ext
|
||||
}
|
||||
|
||||
// 获取原文件所在目录
|
||||
dir := filepath.Dir(b.Title)
|
||||
|
||||
// 构造新路径
|
||||
newPath := filepath.Join(dir, fullNewName)
|
||||
|
||||
// 如果目标文件已存在,先删除它(实现覆盖)
|
||||
if _, err := os.Stat(newPath); err == nil {
|
||||
err := os.Remove(newPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("删除已存在的目标文件失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 执行重命名(移动)
|
||||
err := os.Rename(b.SourcePath, newPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("重命名失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回更名后的绝对路径
|
||||
absPath, err := filepath.Abs(newPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取绝对路径失败: %v", err)
|
||||
}
|
||||
b.SourcePath = absPath
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BasePublisher) uploadToOss(data []byte) (err error) {
|
||||
if b.ossClient == nil {
|
||||
b.ossClient, err = utils_oss.NewClient(b.config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = b.ossClient.UploadBytes(fmt.Sprintf("%scookie/%s/%s.json", b.config.Oss.FilePath, b.UserIndex, b.PlatIndex), data)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *BasePublisher) getCookieFromUrl() (err error) {
|
||||
b.LogInfo("正在加载cookie。。。。")
|
||||
url := fmt.Sprintf("%s/%scookie/%s/%s.json", b.config.Oss.Domain, b.config.Oss.FilePath, b.UserIndex, b.PlatIndex)
|
||||
cookieDir := b.cookiesDir()
|
||||
_, err = pkg.DownloadFile(url, cookieDir, b.PlatIndex+".json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.LogInfo("cookie加载完成。。。。")
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,37 +57,139 @@ func (p *XiaohongshuPublisher) WaitLogin() (bool, string) {
|
|||
return false, "登录超时"
|
||||
}
|
||||
|
||||
func (p *XiaohongshuPublisher) inputContent() error {
|
||||
p.LogInfo("输入文章内容...")
|
||||
//func (p *XiaohongshuPublisher) inputContent() error {
|
||||
// p.LogInfo("输入文章内容...")
|
||||
//
|
||||
// // 等待编辑器加载
|
||||
// contentEditor, err := p.WaitForElementVisible(".tiptap.ProseMirror", 10)
|
||||
// if err != nil {
|
||||
// // 尝试其他选择器
|
||||
// contentEditor, err = p.WaitForElementVisible("[contenteditable='true']", 10)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("未找到内容编辑器: %v", err)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 点击获取焦点 - 使用 Click 方法
|
||||
// if err := contentEditor.Click(proto.InputMouseButtonLeft, 1); err != nil {
|
||||
// return fmt.Errorf("点击编辑器失败: %v", err)
|
||||
// }
|
||||
// p.SleepMs(500)
|
||||
//
|
||||
// // 清空现有内容 - 使用 JavaScript 清空
|
||||
// if err := p.ClearContentEditable(contentEditor); err != nil {
|
||||
// p.LogInfo(fmt.Sprintf("清空编辑器失败: %v", err))
|
||||
// }
|
||||
// p.SleepMs(300)
|
||||
//
|
||||
// // 输入新内容 - 使用 JavaScript 设置内容
|
||||
// if err := p.SetContentEditable(contentEditor, p.Content); err != nil {
|
||||
// // 如果 JS 方式失败,尝试直接输入
|
||||
// contentEditor.Input(p.Content)
|
||||
// }
|
||||
// p.LogInfo(fmt.Sprintf("内容已输入,长度: %d", len(p.Content)))
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// 等待编辑器加载
|
||||
contentEditor, err := p.WaitForElementVisible(".tiptap.ProseMirror", 10)
|
||||
func (p *XiaohongshuPublisher) inputContent() error {
|
||||
p.LogInfo("开始导入文档内容...")
|
||||
|
||||
// 1. 找到菜单容器并点击最后一个菜单项
|
||||
menuContainer, err := p.WaitForElementVisible(".menu-items-container", 10)
|
||||
if err != nil {
|
||||
// 尝试其他选择器
|
||||
contentEditor, err = p.WaitForElementVisible("[contenteditable='true']", 10)
|
||||
if err != nil {
|
||||
return fmt.Errorf("未找到内容编辑器: %v", err)
|
||||
return fmt.Errorf("未找到菜单容器: %v", err)
|
||||
}
|
||||
p.LogInfo("找到菜单容器")
|
||||
|
||||
// 获取所有菜单项
|
||||
menuItems, err := menuContainer.Elements(".menu-item")
|
||||
if err != nil {
|
||||
return fmt.Errorf("未找到菜单项: %v", err)
|
||||
}
|
||||
|
||||
if len(menuItems) == 0 {
|
||||
return fmt.Errorf("菜单容器中没有找到菜单项")
|
||||
}
|
||||
|
||||
// 点击最后一个菜单项
|
||||
lastMenuItem := menuItems[len(menuItems)-1]
|
||||
if err := p.JSClick(lastMenuItem); err != nil {
|
||||
return fmt.Errorf("点击最后一个菜单项失败: %v", err)
|
||||
}
|
||||
p.LogInfo("已点击最后一个菜单项")
|
||||
p.SleepMs(500)
|
||||
|
||||
// 2. 等待导入文件模态框出现
|
||||
var importModal *rod.Element
|
||||
for i := 0; i < 10; i++ {
|
||||
importModal, err = p.Page.Element("[class*='import-from-file-modal']")
|
||||
if err == nil && importModal != nil {
|
||||
visible, _ := importModal.Visible()
|
||||
if visible {
|
||||
p.LogInfo("找到导入文件模态框")
|
||||
break
|
||||
}
|
||||
}
|
||||
p.SleepMs(500)
|
||||
}
|
||||
if importModal == nil {
|
||||
return fmt.Errorf("未找到导入文件模态框")
|
||||
}
|
||||
|
||||
// 查找模态框内的内容容器
|
||||
modalContent, err := importModal.Element(".d-modal-content")
|
||||
if err != nil {
|
||||
return fmt.Errorf("未找到模态框内容容器: %v", err)
|
||||
}
|
||||
p.LogInfo("找到模态框内容容器")
|
||||
|
||||
modalContent.MustClick()
|
||||
p.SleepMs(300)
|
||||
// 3. 查找隐藏的文件输入框
|
||||
var fileInput *rod.Element
|
||||
// 尝试多种选择器
|
||||
selectors := []string{
|
||||
"input[type='file'][accept*='.docx']",
|
||||
"input[type='file'][accept*='.doc']",
|
||||
"input[type='file']",
|
||||
}
|
||||
for _, selector := range selectors {
|
||||
fileInput, err = modalContent.Element(selector)
|
||||
if err == nil && fileInput != nil {
|
||||
p.LogInfo(fmt.Sprintf("通过选择器找到文件输入框: %s", selector))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 点击获取焦点 - 使用 Click 方法
|
||||
if err := contentEditor.Click(proto.InputMouseButtonLeft, 1); err != nil {
|
||||
return fmt.Errorf("点击编辑器失败: %v", err)
|
||||
if fileInput == nil {
|
||||
// 尝试在整个页面中查找
|
||||
for _, selector := range selectors {
|
||||
fileInput, err = p.Page.Element(selector)
|
||||
if err == nil && fileInput != nil {
|
||||
p.LogInfo(fmt.Sprintf("在全局中找到文件输入框: %s", selector))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
p.SleepMs(500)
|
||||
|
||||
// 清空现有内容 - 使用 JavaScript 清空
|
||||
if err := p.ClearContentEditable(contentEditor); err != nil {
|
||||
p.LogInfo(fmt.Sprintf("清空编辑器失败: %v", err))
|
||||
if fileInput == nil {
|
||||
return fmt.Errorf("未找到文件输入框")
|
||||
}
|
||||
p.SleepMs(300)
|
||||
|
||||
// 输入新内容 - 使用 JavaScript 设置内容
|
||||
if err := p.SetContentEditable(contentEditor, p.Content); err != nil {
|
||||
// 如果 JS 方式失败,尝试直接输入
|
||||
contentEditor.Input(p.Content)
|
||||
// 4. 上传文件
|
||||
if p.SourcePath == "" {
|
||||
return fmt.Errorf("源文件路径为空")
|
||||
}
|
||||
p.LogInfo(fmt.Sprintf("内容已输入,长度: %d", len(p.Content)))
|
||||
|
||||
// 使用 SetFiles 方法上传文件
|
||||
if err := fileInput.SetFiles([]string{p.SourcePath}); err != nil {
|
||||
return fmt.Errorf("上传文件失败: %v", err)
|
||||
}
|
||||
p.LogInfo(fmt.Sprintf("已选择文件: %s", p.SourcePath))
|
||||
|
||||
// 等待文件上传完成
|
||||
p.SleepMs(2000)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (m *AppModule) Register(router fiber.Router) {
|
|||
router.Post("/get_publish_list", vali(m.publishService.GetPublishList, &entitys.GetPublishListRequest{}))
|
||||
router.Post("/login_platform", vali(m.loginService.LoginPlatform, &entitys.LoginPlatformRequest{}))
|
||||
router.Post("/logout_platform", vali(m.loginService.LogoutPlatform, &entitys.LogoutPlatformRequest{}))
|
||||
router.Get("/logs/:request_id", m.loginService.Log)
|
||||
router.Get("/logs/:publish_id/:request_id", m.loginService.Log)
|
||||
|
||||
router.Post("/product/add", vali(m.productService.Add, &entitys.CreateProductRequest{}))
|
||||
router.Post("/product/list", vali(m.productService.List, &entitys.ProductListRequest{}))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"geo/internal/manager"
|
||||
"geo/internal/publisher"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
|
|
@ -99,17 +101,38 @@ func (s *LoginService) ServeQrcode(c *fiber.Ctx, filename string) error {
|
|||
}
|
||||
|
||||
func (s *LoginService) Log(c *fiber.Ctx) error {
|
||||
var content []byte
|
||||
r_id := c.Params("request_id", "")
|
||||
if r_id == "" {
|
||||
return errcode.ParamErr("request_id未传")
|
||||
}
|
||||
logFile := filepath.Join(s.cfg.Sys.LogsDir, r_id+".log")
|
||||
if _, err := os.Stat(logFile); os.IsNotExist(err) {
|
||||
return errcode.NotFound("未找到日志")
|
||||
}
|
||||
content, err := os.ReadFile(logFile)
|
||||
idStr := c.Params("publish_id", "")
|
||||
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
return errcode.SysErr("读取日志失败")
|
||||
// 转换失败处理
|
||||
return errcode.ParamErr("idStr格式错误")
|
||||
}
|
||||
logFile := filepath.Join(s.cfg.Sys.LogsDir, fmt.Sprintf("%d_%s.log", id, r_id))
|
||||
|
||||
if _, err := os.Stat(logFile); !os.IsNotExist(err) {
|
||||
content, err = os.ReadFile(logFile)
|
||||
if err != nil {
|
||||
return errcode.SysErr("读取日志失败")
|
||||
}
|
||||
} else {
|
||||
publishData, _err := s.publishBiz.GetTaskByPublishId(c.UserContext(), id)
|
||||
if _err != nil || len(publishData.LogUrl) == 0 {
|
||||
return errcode.SysErr("读取日志失败")
|
||||
}
|
||||
logPath, _err := pkg.DownloadFile(publishData.LogUrl, s.cfg.Sys.LogsDir, fmt.Sprintf("%d_%s.log", id, r_id))
|
||||
if _err != nil {
|
||||
return errcode.SysErr("读取日志失败")
|
||||
}
|
||||
content, _err = os.ReadFile(logPath)
|
||||
if _err != nil {
|
||||
return errcode.SysErr("读取日志失败")
|
||||
}
|
||||
}
|
||||
|
||||
return pkg.HandleResponse(c, fiber.Map{"content": string(content)})
|
||||
|
|
|
|||
|
|
@ -79,9 +79,16 @@ func (p *ProductSourceService) Create(c *fiber.Ctx, req *entitys.ProductSourceCr
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contentByte := []byte(*content)
|
||||
var resp entitys.BotChatResponse
|
||||
if err := json.Unmarshal([]byte(*content), &resp); err != nil {
|
||||
return errcode.SysErr("文章生成失败,请重试")
|
||||
if err := json.Unmarshal(contentByte, &resp); err != nil {
|
||||
contentStr, err := pkg.JsonRepair(*content)
|
||||
if err != nil {
|
||||
return errcode.SysErr("文章生成失败,请重试")
|
||||
}
|
||||
if err := json.Unmarshal([]byte(contentStr), &resp); err != nil {
|
||||
return errcode.SysErr("文章生成失败,请重试")
|
||||
}
|
||||
}
|
||||
docxUrl, err := p.productBiz.CreateAndUploadArticle(c.UserContext(), resp.Content, product)
|
||||
if err != nil {
|
||||
|
|
@ -187,12 +194,12 @@ func (p *ProductSourceService) Update(c *fiber.Ctx, req *entitys.ProductSourceUp
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var update = &model.ProductSource{}
|
||||
var update = map[string]any{}
|
||||
if req.Title != nil {
|
||||
update.Title = *req.Title
|
||||
update["title"] = *req.Title
|
||||
}
|
||||
if req.Tag != nil {
|
||||
update.Tag = strings.Join(*req.Tag, ",")
|
||||
update["tag"] = strings.Join(*req.Tag, ",")
|
||||
}
|
||||
|
||||
return p.productBiz.UpdateSourceById(c.UserContext(), req.SourceId, update)
|
||||
|
|
@ -227,10 +234,10 @@ func (p *ProductSourceService) Publish(c *fiber.Ctx, req *entitys.ProductPublish
|
|||
if product.Imgs == "" {
|
||||
return errcode.NotFound("请先上传产品图片")
|
||||
}
|
||||
ptime, err := time.Parse(time.DateTime, req.PublishTime)
|
||||
if err != nil {
|
||||
return errcode.ParamErr("发布时间格式错误")
|
||||
}
|
||||
//ptime, err := time.Parse(time.DateTime, req.PublishTime)
|
||||
//if err != nil {
|
||||
// return errcode.ParamErr("发布时间格式错误")
|
||||
//}
|
||||
var validRecords = make([]*model.Publish, 0, len(req.Plat))
|
||||
for _, v := range req.Plat {
|
||||
validRecords = append(validRecords, &model.Publish{
|
||||
|
|
@ -242,7 +249,7 @@ func (p *ProductSourceService) Publish(c *fiber.Ctx, req *entitys.ProductPublish
|
|||
Type: source.SourceType,
|
||||
PlatIndex: v,
|
||||
URL: source.SourceURL,
|
||||
PublishTime: ptime,
|
||||
PublishTime: req.PublishTime.Time,
|
||||
Img: strings.Split(product.Imgs, ",")[0],
|
||||
TokenID: tokenInfo.ID,
|
||||
})
|
||||
|
|
@ -261,7 +268,8 @@ func (p *ProductSourceService) Publish(c *fiber.Ctx, req *entitys.ProductPublish
|
|||
func (p *ProductSourceService) ArticalTypeList(c *fiber.Ctx) error {
|
||||
cond := builder.NewCond().
|
||||
And(builder.Eq{"status": 1})
|
||||
list, err := p.articleTypeImpl.GetRange(c.UserContext(), &cond)
|
||||
var list []*model.ArticleType
|
||||
err := p.articleTypeImpl.GetRangeToMapStruct(c.UserContext(), &cond, &list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,3 +178,42 @@ func Md2wordFix(mdFile, outPutDIr string, img []string) (string, error) {
|
|||
|
||||
return result.Content, nil
|
||||
}
|
||||
|
||||
func JsonRepair(badJson string) (string, error) {
|
||||
baseDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取工作目录失败: %w", err)
|
||||
}
|
||||
|
||||
exePath := filepath.Join(baseDir, "plugins", "json_fix.exe")
|
||||
|
||||
// 3. 检查 exe 是否存在
|
||||
if _, err = os.Stat(exePath); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("exe不存在: %s", exePath)
|
||||
}
|
||||
|
||||
//7. 构建命令行参数
|
||||
args := []string{"-j", badJson}
|
||||
|
||||
cmd := exec.Command(exePath, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("执行失败: %v", err)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Success bool `json:"success"`
|
||||
Content string `json:"content"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(output, &result); err != nil {
|
||||
return "", fmt.Errorf("解析结果失败: %v, 输出: %s", err, string(output))
|
||||
}
|
||||
|
||||
if !result.Success {
|
||||
return "", fmt.Errorf("转换失败: %s", result.Error)
|
||||
}
|
||||
|
||||
return result.Content, nil
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue