This commit is contained in:
parent
6c11412307
commit
a06ae5c8cf
|
|
@ -28,7 +28,7 @@ func InitializeApp(configConfig *config.Config, allLogger log.AllLogger) (*serve
|
||||||
publishImpl := impl.NewPublishImpl(db)
|
publishImpl := impl.NewPublishImpl(db)
|
||||||
loginRelationImpl := impl.NewLoginRelationImpl(db)
|
loginRelationImpl := impl.NewLoginRelationImpl(db)
|
||||||
publishBiz := biz.NewPublishBiz(configConfig, publishImpl, userImpl, platImpl, tokenImpl, loginRelationImpl)
|
publishBiz := biz.NewPublishBiz(configConfig, publishImpl, userImpl, platImpl, tokenImpl, loginRelationImpl)
|
||||||
appService := service.NewAppService(configConfig, tokenImpl, userImpl, platImpl, publishBiz)
|
appService := service.NewAppService(configConfig, tokenImpl, userImpl, platImpl, publishBiz, loginRelationImpl)
|
||||||
loginService := service.NewLoginService(configConfig, publishBiz)
|
loginService := service.NewLoginService(configConfig, publishBiz)
|
||||||
publishService := service.NewPublishService(configConfig, publishBiz, db)
|
publishService := service.NewPublishService(configConfig, publishBiz, db)
|
||||||
appModule := router.NewAppModule(configConfig, appService, loginService, publishService)
|
appModule := router.NewAppModule(configConfig, appService, loginService, publishService)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
[{"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":"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":"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":"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":"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_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":"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":"xsecappid","value":"ugc","domain":".xiaohongshu.com","path":"/","expires":1807379925,"size":12,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"loadts","value":"1775843925008","domain":".xiaohongshu.com","path":"/","expires":1807379925,"size":19,"httpOnly":false,"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},{"name":"websectiga","value":"2a3d3ea002e7d92b5c9743590ebd24010cf3710ff3af8029153751e41a6af4a3","domain":".xiaohongshu.com","path":"/","expires":1776103125,"size":74,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"sec_poison_id","value":"680df974-cc97-44bd-9c18-b9efaafb66cc","domain":".xiaohongshu.com","path":"/","expires":1775844530,"size":49,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"acw_tc","value":"0a00076417758431730566823e6256f5349e529f992a6ba74dc0835439d93a","domain":"creator.xiaohongshu.com","path":"/","expires":1775844709.365943,"size":68,"httpOnly":true,"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":"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}]
|
||||||
1
go.mod
1
go.mod
|
|
@ -18,6 +18,7 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
gitea.cdlsxd.cn/self-tools/l_request v1.0.8 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -1,5 +1,7 @@
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
gitea.cdlsxd.cn/self-tools/l_request v1.0.8 h1:FaKRql9mCVcSoaGqPeBOAruZ52slzRngQ6VRTYKNSsA=
|
||||||
|
gitea.cdlsxd.cn/self-tools/l_request v1.0.8/go.mod h1:Qf4hVXm2Eu5vOvwXk8D7U0q/aekMCkZ4Fg9wnRKlasQ=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,11 @@ package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"geo/internal/entitys"
|
||||||
|
"geo/pkg"
|
||||||
|
"gitea.cdlsxd.cn/self-tools/l_request"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"geo/internal/config"
|
"geo/internal/config"
|
||||||
|
|
@ -101,5 +106,44 @@ func (b *PublishBiz) UpdateLoginStatus(ctx context.Context, userIndex, platIndex
|
||||||
cond := builder.NewCond().
|
cond := builder.NewCond().
|
||||||
And(builder.Eq{"user_index": userIndex}).
|
And(builder.Eq{"user_index": userIndex}).
|
||||||
And(builder.Eq{"plat_index": platIndex})
|
And(builder.Eq{"plat_index": platIndex})
|
||||||
return b.loginRelationImpl.UpdateByCond(ctx, &cond, &model.User{Status: loginStatus})
|
return b.loginRelationImpl.UpdateByCond(ctx, &cond, &model.LoginRelation{LoginStatus: loginStatus})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PublishBiz) Notify(data *entitys.NotifyData) (err error) {
|
||||||
|
if data.NotifyUrl != "" {
|
||||||
|
if data.TokenId == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cond := builder.NewCond().
|
||||||
|
And(builder.Eq{"id": data.TokenId}).
|
||||||
|
And(builder.Eq{"status": 1})
|
||||||
|
|
||||||
|
tokenInfo := &model.Token{}
|
||||||
|
err = b.tokenImpl.GetOneBySearchStructWithOutCtx(&cond, tokenInfo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tokenInfo.ID == 0 {
|
||||||
|
return fmt.Errorf("未找到用户信息")
|
||||||
|
}
|
||||||
|
data.NotifyUrl = tokenInfo.Notifyurl
|
||||||
|
}
|
||||||
|
if data.NotifyUrl == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendData, _ := pkg.StructToMap(data)
|
||||||
|
req := l_request.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Url: data.NotifyUrl,
|
||||||
|
Json: sendData,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, err := req.Send()
|
||||||
|
log.Infof("回调完成:%v", res)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("请求失败,err: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ type Token struct {
|
||||||
ID int32 `gorm:"column:id;primaryKey" json:"id"`
|
ID int32 `gorm:"column:id;primaryKey" json:"id"`
|
||||||
Name string `gorm:"column:name" json:"name"`
|
Name string `gorm:"column:name" json:"name"`
|
||||||
Secret string `gorm:"column:secret;not null" json:"secret"`
|
Secret string `gorm:"column:secret;not null" json:"secret"`
|
||||||
|
Notifyurl string `gorm:"column:notify_url;not null" json:"notify_url"`
|
||||||
AccessToken string `gorm:"column:access_token;not null" json:"access_token"`
|
AccessToken string `gorm:"column:access_token;not null" json:"access_token"`
|
||||||
UserLimit int32 `gorm:"column:user_limit;primaryKey" json:"user_limit"`
|
UserLimit int32 `gorm:"column:user_limit;primaryKey" json:"user_limit"`
|
||||||
ExpireTime time.Time `gorm:"column:expire_time;not null" json:"expire_time"`
|
ExpireTime time.Time `gorm:"column:expire_time;not null" json:"expire_time"`
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,18 @@ type PublishTaskDetail struct {
|
||||||
LoginedUrl string `db:"logined_url"`
|
LoginedUrl string `db:"logined_url"`
|
||||||
Desc string `db:"desc"`
|
Desc string `db:"desc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NotifyData struct {
|
||||||
|
NotifyUrl string
|
||||||
|
TokenId int
|
||||||
|
Type NotifyType
|
||||||
|
Status int
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotifyType int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotifyTypePublish NotifyType = 1
|
||||||
|
NotifyTypeToken NotifyType = 1
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
DelUserRequest struct {
|
DelUserRequest struct {
|
||||||
ID int `json:"id" validate:"required" zh:"用户ID"`
|
AccessToken string `json:"access_token" validate:"required" zh:"access_token"`
|
||||||
|
UserIndex string `json:"user_index" validate:"required" zh:"用户索引"`
|
||||||
}
|
}
|
||||||
|
|
||||||
GetAppRequest struct {
|
GetAppRequest struct {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package manager
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"geo/internal/biz"
|
||||||
"geo/internal/config"
|
"geo/internal/config"
|
||||||
"geo/internal/entitys"
|
"geo/internal/entitys"
|
||||||
"geo/internal/publisher"
|
"geo/internal/publisher"
|
||||||
|
|
@ -41,7 +42,8 @@ type PublishManager struct {
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
db *utils.Db
|
db *utils.Db
|
||||||
stopOnce sync.Once
|
stopOnce sync.Once
|
||||||
|
stopOnceMu sync.Mutex
|
||||||
|
publicBiz *biz.PublishBiz
|
||||||
// 并发控制
|
// 并发控制
|
||||||
workerNum int // 并发worker数量
|
workerNum int // 并发worker数量
|
||||||
workerWg sync.WaitGroup // 等待所有worker退出
|
workerWg sync.WaitGroup // 等待所有worker退出
|
||||||
|
|
@ -53,7 +55,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPublishManager 获取单例实例
|
// GetPublishManager 获取单例实例
|
||||||
func GetPublishManager(config *config.Config, db *utils.Db) (*PublishManager, error) {
|
func GetPublishManager(config *config.Config, db *utils.Db, publicBiz *biz.PublishBiz) (*PublishManager, error) {
|
||||||
if config == nil || db == nil {
|
if config == nil || db == nil {
|
||||||
return nil, fmt.Errorf("config和db参数不能为空")
|
return nil, fmt.Errorf("config和db参数不能为空")
|
||||||
}
|
}
|
||||||
|
|
@ -64,6 +66,7 @@ func GetPublishManager(config *config.Config, db *utils.Db) (*PublishManager, er
|
||||||
Conf: config,
|
Conf: config,
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
db: db,
|
db: db,
|
||||||
|
publicBiz: publicBiz,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return publishManager, nil
|
return publishManager, nil
|
||||||
|
|
@ -80,13 +83,11 @@ func (pm *PublishManager) getTaskLogger(requestID string) (*log.Logger, *os.File
|
||||||
logsDir = "./logs"
|
logsDir = "./logs"
|
||||||
}
|
}
|
||||||
|
|
||||||
dateDir := time.Now().Format("2006-01-02")
|
if err := os.MkdirAll(logsDir, 0755); err != nil {
|
||||||
taskLogDir := filepath.Join(logsDir, "tasks", dateDir)
|
|
||||||
if err := os.MkdirAll(taskLogDir, 0755); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("创建日志目录失败: %v", err)
|
return nil, nil, fmt.Errorf("创建日志目录失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logPath := filepath.Join(taskLogDir, fmt.Sprintf("%s.log", requestID))
|
logPath := filepath.Join(logsDir, fmt.Sprintf("%s.log", requestID))
|
||||||
|
|
||||||
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -125,6 +126,11 @@ func (pm *PublishManager) Start(tokenID int, workerNum int) bool {
|
||||||
pm.workerNum = workerNum
|
pm.workerNum = workerNum
|
||||||
pm.stopCh = make(chan struct{})
|
pm.stopCh = make(chan struct{})
|
||||||
|
|
||||||
|
// ✅ 修改:重置 sync.Once(关键修复)
|
||||||
|
pm.stopOnceMu.Lock()
|
||||||
|
pm.stopOnce = sync.Once{}
|
||||||
|
pm.stopOnceMu.Unlock()
|
||||||
|
|
||||||
for i := 0; i < workerNum; i++ {
|
for i := 0; i < workerNum; i++ {
|
||||||
pm.workerWg.Add(1)
|
pm.workerWg.Add(1)
|
||||||
go pm.workerLoop(i)
|
go pm.workerLoop(i)
|
||||||
|
|
@ -134,7 +140,6 @@ func (pm *PublishManager) Start(tokenID int, workerNum int) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop 停止自动发布
|
|
||||||
func (pm *PublishManager) Stop() bool {
|
func (pm *PublishManager) Stop() bool {
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
if !pm.AutoStatus {
|
if !pm.AutoStatus {
|
||||||
|
|
@ -144,9 +149,12 @@ func (pm *PublishManager) Stop() bool {
|
||||||
pm.AutoStatus = false
|
pm.AutoStatus = false
|
||||||
pm.mu.Unlock()
|
pm.mu.Unlock()
|
||||||
|
|
||||||
|
// ✅ 修改:使用互斥锁保护 Once 调用
|
||||||
|
pm.stopOnceMu.Lock()
|
||||||
pm.stopOnce.Do(func() {
|
pm.stopOnce.Do(func() {
|
||||||
close(pm.stopCh)
|
close(pm.stopCh)
|
||||||
})
|
})
|
||||||
|
pm.stopOnceMu.Unlock()
|
||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -322,7 +330,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
errMsg := fmt.Sprintf("任务执行发生panic: %v", r)
|
errMsg := fmt.Sprintf("任务执行发生panic: %v", r)
|
||||||
taskLogger.Printf("❌ CRITICAL: %s", errMsg)
|
taskLogger.Printf("❌ CRITICAL: %s", errMsg)
|
||||||
pm.updatePublishStatus(publishData.ID, StatusFailed, errMsg)
|
pm.updatePublishStatus(publishData, StatusFailed, errMsg)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -330,7 +338,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
||||||
|
|
||||||
params, sourceUrl := pm.extractTaskParams(publishData, taskLogger)
|
params, sourceUrl := pm.extractTaskParams(publishData, taskLogger)
|
||||||
if params == nil {
|
if params == nil {
|
||||||
pm.updatePublishStatus(publishData.ID, StatusFailed, "提取任务参数失败")
|
pm.updatePublishStatus(publishData, StatusFailed, "提取任务参数失败")
|
||||||
return &SingleResult{Success: false, Message: "提取任务参数失败", RequestId: publishData.RequestID}
|
return &SingleResult{Success: false, Message: "提取任务参数失败", RequestId: publishData.RequestID}
|
||||||
}
|
}
|
||||||
params.Headless = headless
|
params.Headless = headless
|
||||||
|
|
@ -339,7 +347,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
||||||
if publisherClass == nil {
|
if publisherClass == nil {
|
||||||
errMsg := fmt.Sprintf("不支持的平台: %s", params.PlatIndex)
|
errMsg := fmt.Sprintf("不支持的平台: %s", params.PlatIndex)
|
||||||
taskLogger.Printf("[任务 %s] ❌ %s", publishData.RequestID, errMsg)
|
taskLogger.Printf("[任务 %s] ❌ %s", publishData.RequestID, errMsg)
|
||||||
pm.updatePublishStatus(publishData.ID, StatusFailed, errMsg)
|
pm.updatePublishStatus(publishData, StatusFailed, errMsg)
|
||||||
return &SingleResult{Success: false, Message: errMsg, RequestId: publishData.RequestID}
|
return &SingleResult{Success: false, Message: errMsg, RequestId: publishData.RequestID}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,7 +355,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := fmt.Sprintf("准备文件失败: %v", err)
|
errMsg := fmt.Sprintf("准备文件失败: %v", err)
|
||||||
taskLogger.Printf("[任务 %s] ❌ %s", publishData.RequestID, errMsg)
|
taskLogger.Printf("[任务 %s] ❌ %s", publishData.RequestID, errMsg)
|
||||||
pm.updatePublishStatus(publishData.ID, StatusFailed, errMsg)
|
pm.updatePublishStatus(publishData, StatusFailed, errMsg)
|
||||||
return &SingleResult{Success: false, Message: errMsg, RequestId: publishData.RequestID}
|
return &SingleResult{Success: false, Message: errMsg, RequestId: publishData.RequestID}
|
||||||
}
|
}
|
||||||
defer pm.cleanupFiles(docPath, imgPath, taskLogger, publishData.RequestID)
|
defer pm.cleanupFiles(docPath, imgPath, taskLogger, publishData.RequestID)
|
||||||
|
|
@ -356,7 +364,7 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := fmt.Sprintf("提取文档内容失败: %v", err)
|
errMsg := fmt.Sprintf("提取文档内容失败: %v", err)
|
||||||
taskLogger.Printf("[任务 %s] ❌ %s", publishData.RequestID, errMsg)
|
taskLogger.Printf("[任务 %s] ❌ %s", publishData.RequestID, errMsg)
|
||||||
pm.updatePublishStatus(publishData.ID, StatusFailed, errMsg)
|
pm.updatePublishStatus(publishData, StatusFailed, errMsg)
|
||||||
return &SingleResult{Success: false, Message: errMsg, RequestId: publishData.RequestID}
|
return &SingleResult{Success: false, Message: errMsg, RequestId: publishData.RequestID}
|
||||||
}
|
}
|
||||||
params.ImagePath = imgPath
|
params.ImagePath = imgPath
|
||||||
|
|
@ -368,10 +376,10 @@ func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.
|
||||||
|
|
||||||
if success {
|
if success {
|
||||||
taskLogger.Printf("[任务 %s] ✅ 发布成功: %s", publishData.RequestID, message)
|
taskLogger.Printf("[任务 %s] ✅ 发布成功: %s", publishData.RequestID, message)
|
||||||
pm.updatePublishStatus(publishData.ID, StatusSuccess, message)
|
pm.updatePublishStatus(publishData, StatusSuccess, message)
|
||||||
} else {
|
} else {
|
||||||
taskLogger.Printf("[任务 %s] ❌ 发布失败: %s", publishData.RequestID, message)
|
taskLogger.Printf("[任务 %s] ❌ 发布失败: %s", publishData.RequestID, message)
|
||||||
pm.updatePublishStatus(publishData.ID, StatusFailed, message)
|
pm.updatePublishStatus(publishData, StatusFailed, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
taskLogger.Printf(strings.Repeat("=", 80))
|
taskLogger.Printf(strings.Repeat("=", 80))
|
||||||
|
|
@ -408,6 +416,7 @@ func (pm *PublishManager) GetTaskByRequestID(requestID string, tokenId int) (*en
|
||||||
SELECT
|
SELECT
|
||||||
p.id,
|
p.id,
|
||||||
p.request_id,
|
p.request_id,
|
||||||
|
p.token_id,
|
||||||
p.plat_index,
|
p.plat_index,
|
||||||
p.title,
|
p.title,
|
||||||
p.tag,
|
p.tag,
|
||||||
|
|
@ -524,19 +533,25 @@ func (pm *PublishManager) extractContent(docPath string, publisherClass *publish
|
||||||
}
|
}
|
||||||
|
|
||||||
// updatePublishStatus 更新发布状态
|
// updatePublishStatus 更新发布状态
|
||||||
func (pm *PublishManager) updatePublishStatus(id int, status int, message string) error {
|
func (pm *PublishManager) updatePublishStatus(publishData *entitys.PublishTaskDetail, status int, message string) error {
|
||||||
if id == 0 {
|
if publishData.ID == 0 {
|
||||||
return fmt.Errorf("id不能为空")
|
return fmt.Errorf("id不能为空")
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if message != "" {
|
if message != "" {
|
||||||
_, err = pm.db.Execute("UPDATE publish SET status = ?, msg = ? WHERE id=?", status, message, id)
|
_, err = pm.db.Execute("UPDATE publish SET status = ?, msg = ? WHERE id=?", status, message, publishData.ID)
|
||||||
} else {
|
} else {
|
||||||
_, err = pm.db.Execute("UPDATE publish SET status = ? WHERE id=?", status, id)
|
_, err = pm.db.Execute("UPDATE publish SET status = ? WHERE id=?", status, publishData.ID)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("更新发布状态失败: id=%s, status=%d, error=%v", id, status, err)
|
log.Printf("更新发布状态失败: id=%s, status=%d, error=%v", publishData.ID, status, err)
|
||||||
}
|
}
|
||||||
|
_ = pm.publicBiz.Notify(&entitys.NotifyData{
|
||||||
|
TokenId: publishData.TokenID,
|
||||||
|
Type: entitys.NotifyTypePublish,
|
||||||
|
Status: status,
|
||||||
|
Msg: message,
|
||||||
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,18 @@ func (b *BasePublisher) JSClick(element *rod.Element) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BasePublisher) JSClick2(element *rod.Element) error {
|
||||||
|
if element == nil {
|
||||||
|
b.Logger.Printf("element is nil")
|
||||||
|
return fmt.Errorf("element is nil")
|
||||||
|
}
|
||||||
|
err := element.Click(proto.InputMouseButtonLeft, 1)
|
||||||
|
if err != nil {
|
||||||
|
b.Logger.Printf("click fail:" + err.Error())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BasePublisher) ScrollToElement(element *rod.Element) error {
|
func (b *BasePublisher) ScrollToElement(element *rod.Element) error {
|
||||||
_, err := element.Evaluate(&rod.EvalOptions{
|
_, err := element.Evaluate(&rod.EvalOptions{
|
||||||
JS: `el => el.scrollIntoView({block: 'center', behavior: 'smooth'})`,
|
JS: `el => el.scrollIntoView({block: 'center', behavior: 'smooth'})`,
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,10 @@ package publisher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"geo/internal/config"
|
"geo/internal/config"
|
||||||
|
"github.com/go-rod/rod"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -15,6 +13,7 @@ import (
|
||||||
// ShipinhaoVideoPublisher 视频号视频发布器
|
// ShipinhaoVideoPublisher 视频号视频发布器
|
||||||
type ShipinhaoVideoPublisher struct {
|
type ShipinhaoVideoPublisher struct {
|
||||||
*BasePublisher
|
*BasePublisher
|
||||||
|
iframe *rod.Page
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewShipinhaoVideoPublisher 创建视频号发布器
|
// NewShipinhaoVideoPublisher 创建视频号发布器
|
||||||
|
|
@ -24,6 +23,67 @@ func NewShipinhaoVideoPublisher(ctx context.Context, task *TaskParams, config *c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ShipinhaoVideoPublisher) CheckLogin() (bool, string) {
|
||||||
|
p.LogInfo("检查登录状态...")
|
||||||
|
|
||||||
|
if err := p.SetupDriver(); err != nil {
|
||||||
|
return false, fmt.Sprintf("浏览器启动失败: %v", err)
|
||||||
|
}
|
||||||
|
defer p.Close()
|
||||||
|
|
||||||
|
p.Page.MustNavigate(p.LoginedURL)
|
||||||
|
p.Sleep(3)
|
||||||
|
p.WaitForPageReady(5)
|
||||||
|
|
||||||
|
if p.CheckLoginStatus() {
|
||||||
|
p.SaveCookies()
|
||||||
|
return true, "已登录"
|
||||||
|
}
|
||||||
|
return false, "未登录"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ShipinhaoVideoPublisher) CheckLoginStatus() bool {
|
||||||
|
currentURL := p.GetCurrentURL()
|
||||||
|
if strings.Contains(currentURL, "login") || strings.Contains(currentURL, "passport") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.Contains(currentURL, "channels.weixin.qq.com") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ShipinhaoVideoPublisher) WaitLogin() (bool, string) {
|
||||||
|
p.LogInfo("开始等待登录...")
|
||||||
|
|
||||||
|
if err := p.SetupDriver(); err != nil {
|
||||||
|
return false, fmt.Sprintf("浏览器启动失败: %v", err)
|
||||||
|
}
|
||||||
|
defer p.Close()
|
||||||
|
|
||||||
|
p.Page.MustNavigate(p.LoginURL)
|
||||||
|
p.Sleep(3)
|
||||||
|
|
||||||
|
if p.CheckLoginStatus() {
|
||||||
|
p.SaveCookies()
|
||||||
|
return true, "already_logged_in"
|
||||||
|
}
|
||||||
|
|
||||||
|
p.LogInfo("请扫描二维码登录...")
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
timeout := 120
|
||||||
|
for time.Since(startTime) < time.Duration(timeout)*time.Second {
|
||||||
|
if p.CheckLoginStatus() {
|
||||||
|
p.SaveCookies()
|
||||||
|
return true, "login_success"
|
||||||
|
}
|
||||||
|
p.SleepMs(2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, "登录超时"
|
||||||
|
}
|
||||||
|
|
||||||
// PublishNote 发布视频主流程
|
// PublishNote 发布视频主流程
|
||||||
func (p *ShipinhaoVideoPublisher) PublishNote() (bool, string) {
|
func (p *ShipinhaoVideoPublisher) PublishNote() (bool, string) {
|
||||||
p.StartNote()
|
p.StartNote()
|
||||||
|
|
@ -32,7 +92,7 @@ func (p *ShipinhaoVideoPublisher) PublishNote() (bool, string) {
|
||||||
if err := p.SetupDriver(); err != nil {
|
if err := p.SetupDriver(); err != nil {
|
||||||
return false, fmt.Sprintf("浏览器启动失败: %v", err)
|
return false, fmt.Sprintf("浏览器启动失败: %v", err)
|
||||||
}
|
}
|
||||||
defer p.Close()
|
defer p.Browser.MustClose()
|
||||||
// 3. 加载 cookies 并检查登录状态
|
// 3. 加载 cookies 并检查登录状态
|
||||||
if err := p.LoadCookies(); err == nil {
|
if err := p.LoadCookies(); err == nil {
|
||||||
p.Page.Navigate(p.EditorURL)
|
p.Page.Navigate(p.EditorURL)
|
||||||
|
|
@ -58,10 +118,6 @@ func (p *ShipinhaoVideoPublisher) PublishNote() (bool, string) {
|
||||||
fn func() error
|
fn func() error
|
||||||
}{
|
}{
|
||||||
{"CDP拦截上传", p.uploadViaCDPIntercept},
|
{"CDP拦截上传", p.uploadViaCDPIntercept},
|
||||||
//{"拖拽事件上传", p.uploadViaDragEvent},
|
|
||||||
//{"网络拦截上传", p.uploadViaNetworkIntercept},
|
|
||||||
//{"React事件上传", p.uploadViaReactEvent},
|
|
||||||
//{"文件输入框上传", p.uploadViaFileInput},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, method := range methods {
|
for _, method := range methods {
|
||||||
|
|
@ -97,7 +153,7 @@ func (p *ShipinhaoVideoPublisher) PublishNote() (bool, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. 等待发布完成
|
// 10. 等待发布完成
|
||||||
p.Sleep(10)
|
|
||||||
currentURL := p.GetCurrentURL()
|
currentURL := p.GetCurrentURL()
|
||||||
if strings.Contains(currentURL, "https://channels.weixin.qq.com/platform/post/list") {
|
if strings.Contains(currentURL, "https://channels.weixin.qq.com/platform/post/list") {
|
||||||
p.LogInfo("发布完成")
|
p.LogInfo("发布完成")
|
||||||
|
|
@ -125,7 +181,7 @@ func (p *ShipinhaoVideoPublisher) ensureInEditorIframe() {
|
||||||
if err == nil && exist {
|
if err == nil && exist {
|
||||||
frame, err := frameElement.Frame()
|
frame, err := frameElement.Frame()
|
||||||
if err == nil && frame != nil {
|
if err == nil && frame != nil {
|
||||||
p.Page = frame
|
p.iframe = frame
|
||||||
p.LogInfo(fmt.Sprintf("已切换到 iframe: %s", selector))
|
p.LogInfo(fmt.Sprintf("已切换到 iframe: %s", selector))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -135,43 +191,14 @@ func (p *ShipinhaoVideoPublisher) ensureInEditorIframe() {
|
||||||
p.LogInfo("未找到 iframe,使用主页面")
|
p.LogInfo("未找到 iframe,使用主页面")
|
||||||
}
|
}
|
||||||
|
|
||||||
// uploadViaFileInput 通过文件输入框上传(最基础的方法)
|
|
||||||
func (p *ShipinhaoVideoPublisher) uploadViaFileInput(filePath string) error {
|
|
||||||
p.LogInfo("使用文件输入框上传...")
|
|
||||||
|
|
||||||
// 确保在正确的 iframe 中
|
|
||||||
p.ensureInEditorIframe()
|
|
||||||
|
|
||||||
// 查找文件输入框(使用非阻塞方式)
|
|
||||||
fileInputs, err := p.Page.Elements("input[type='file']")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("查找文件输入框失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fileInputs) == 0 {
|
|
||||||
return fmt.Errorf("未找到文件输入框")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fileInputs[0].SetFiles([]string{filePath})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("设置文件失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.LogInfo(fmt.Sprintf("文件已选择: %s", filepath.Base(filePath)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// uploadViaCDPIntercept 使用 CDP 拦截并注入文件上传
|
// uploadViaCDPIntercept 使用 CDP 拦截并注入文件上传
|
||||||
func (p *ShipinhaoVideoPublisher) uploadViaCDPIntercept() error {
|
func (p *ShipinhaoVideoPublisher) uploadViaCDPIntercept() error {
|
||||||
p.LogInfo("使用 CDP 协议拦截文件上传...")
|
p.LogInfo("使用 CDP 协议拦截文件上传...")
|
||||||
|
|
||||||
// 确保在正确的 iframe 中
|
|
||||||
p.ensureInEditorIframe()
|
|
||||||
p.LogInfo("已切换到 iframe,开始查找文件输入框")
|
|
||||||
p.Sleep(1)
|
p.Sleep(1)
|
||||||
|
|
||||||
// 先在当前 iframe 中查找文件输入框
|
// 先在当前 iframe 中查找文件输入框
|
||||||
fileInputs, err := p.Page.Elements("input[type='file'][accept*='video']")
|
fileInputs, err := p.iframe.Elements("input[type='file'][accept*='video']")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.LogInfo(fmt.Sprintf("查找文件输入框失败: %v", err))
|
p.LogInfo(fmt.Sprintf("查找文件输入框失败: %v", err))
|
||||||
}
|
}
|
||||||
|
|
@ -185,303 +212,6 @@ func (p *ShipinhaoVideoPublisher) uploadViaCDPIntercept() error {
|
||||||
}
|
}
|
||||||
p.LogInfo(fmt.Sprintf("直接设置文件失败: %v", err))
|
p.LogInfo(fmt.Sprintf("直接设置文件失败: %v", err))
|
||||||
}
|
}
|
||||||
//filePath := p.SourcePath
|
|
||||||
//// 读取文件为 Base64
|
|
||||||
//fileData, err := os.ReadFile(filePath)
|
|
||||||
//if err != nil {
|
|
||||||
// return fmt.Errorf("读取文件失败: %v", err)
|
|
||||||
//}
|
|
||||||
//base64Data := base64.StdEncoding.EncodeToString(fileData)
|
|
||||||
//fileName := filepath.Base(filePath)
|
|
||||||
//// 如果直接设置失败,使用 JS 注入方式
|
|
||||||
//p.LogInfo("使用 JS 注入方式上传文件")
|
|
||||||
//
|
|
||||||
//// 注入 JS 代码模拟文件上传
|
|
||||||
//script := fmt.Sprintf(`
|
|
||||||
// (function() {
|
|
||||||
// // 创建 File 对象
|
|
||||||
// var byteCharacters = atob('%s');
|
|
||||||
// var byteNumbers = new Array(byteCharacters.length);
|
|
||||||
// for (var i = 0; i < byteCharacters.length; i++) {
|
|
||||||
// byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
||||||
// }
|
|
||||||
// var byteArray = new Uint8Array(byteNumbers);
|
|
||||||
// var blob = new Blob([byteArray], {type: 'video/mp4'});
|
|
||||||
// var file = new File([blob], '%s', {type: 'video/mp4'});
|
|
||||||
//
|
|
||||||
// // 查找隐藏的文件输入框
|
|
||||||
// var fileInput = document.querySelector('input[type="file"][accept*="video"]');
|
|
||||||
// if (!fileInput) {
|
|
||||||
// fileInput = document.querySelector('input[type="file"]');
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!fileInput) {
|
|
||||||
// // 如果还是找不到,创建一个
|
|
||||||
// fileInput = document.createElement('input');
|
|
||||||
// fileInput.type = 'file';
|
|
||||||
// fileInput.accept = 'video/mp4,video/x-m4v,video/*';
|
|
||||||
// fileInput.multiple = true;
|
|
||||||
// fileInput.style.display = 'none';
|
|
||||||
// document.body.appendChild(fileInput);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 临时显示文件输入框(如果需要)
|
|
||||||
// var originalDisplay = fileInput.style.display;
|
|
||||||
// fileInput.style.display = 'block';
|
|
||||||
//
|
|
||||||
// // 使用 DataTransfer 设置文件
|
|
||||||
// var dataTransfer = new DataTransfer();
|
|
||||||
// dataTransfer.items.add(file);
|
|
||||||
// fileInput.files = dataTransfer.files;
|
|
||||||
//
|
|
||||||
// // 恢复原始显示状态
|
|
||||||
// fileInput.style.display = originalDisplay;
|
|
||||||
//
|
|
||||||
// // 触发 change 事件
|
|
||||||
// var changeEvent = new Event('change', { bubbles: true });
|
|
||||||
// fileInput.dispatchEvent(changeEvent);
|
|
||||||
//
|
|
||||||
// // 触发 input 事件
|
|
||||||
// var inputEvent = new Event('input', { bubbles: true });
|
|
||||||
// fileInput.dispatchEvent(inputEvent);
|
|
||||||
//
|
|
||||||
// // 查找上传区域并触发点击
|
|
||||||
// var uploadArea = document.querySelector('.upload-wrap, .video-plugin-title-action, [class*="upload"]');
|
|
||||||
// if (uploadArea) {
|
|
||||||
// uploadArea.click();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 模拟拖拽事件
|
|
||||||
// var dropZones = document.querySelectorAll('.upload-wrap, [class*="upload"], [class*="drop"]');
|
|
||||||
// for (var i = 0; i < dropZones.length; i++) {
|
|
||||||
// var zone = dropZones[i];
|
|
||||||
// if (zone.offsetParent !== null) {
|
|
||||||
// var dragOverEvent = new DragEvent('dragover', {
|
|
||||||
// bubbles: true,
|
|
||||||
// cancelable: true,
|
|
||||||
// dataTransfer: dataTransfer
|
|
||||||
// });
|
|
||||||
// zone.dispatchEvent(dragOverEvent);
|
|
||||||
//
|
|
||||||
// var dropEvent = new DragEvent('drop', {
|
|
||||||
// bubbles: true,
|
|
||||||
// cancelable: true,
|
|
||||||
// dataTransfer: dataTransfer
|
|
||||||
// });
|
|
||||||
// zone.dispatchEvent(dropEvent);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return {success: true, fileName: '%s', hasFileInput: !!fileInput};
|
|
||||||
// })();
|
|
||||||
//`, base64Data, fileName, fileName)
|
|
||||||
//
|
|
||||||
//result, err := p.Page.Eval(script)
|
|
||||||
//if err != nil {
|
|
||||||
// return fmt.Errorf("CDP 注入失败: %v", err)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//p.LogInfo(fmt.Sprintf("CDP 注入完成,结果: %v", result))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// uploadViaDragEvent 通过模拟拖拽事件上传
|
|
||||||
func (p *ShipinhaoVideoPublisher) uploadViaDragEvent(filePath string) error {
|
|
||||||
p.LogInfo("模拟拖拽事件上传...")
|
|
||||||
|
|
||||||
// 读取文件为 Base64
|
|
||||||
fileData, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("读取文件失败: %v", err)
|
|
||||||
}
|
|
||||||
base64Data := base64.StdEncoding.EncodeToString(fileData)
|
|
||||||
fileName := filepath.Base(filePath)
|
|
||||||
|
|
||||||
// 确保在正确的 iframe 中
|
|
||||||
p.ensureInEditorIframe()
|
|
||||||
p.Sleep(1)
|
|
||||||
|
|
||||||
script := fmt.Sprintf(`
|
|
||||||
(function() {
|
|
||||||
// 创建模拟文件
|
|
||||||
var byteCharacters = atob('%s');
|
|
||||||
var byteNumbers = new Array(byteCharacters.length);
|
|
||||||
for (var i = 0; i < byteCharacters.length; i++) {
|
|
||||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
||||||
}
|
|
||||||
var byteArray = new Uint8Array(byteNumbers);
|
|
||||||
var blob = new Blob([byteArray], {type: 'video/mp4'});
|
|
||||||
var file = new File([blob], '%s', {type: 'video/mp4'});
|
|
||||||
|
|
||||||
var dataTransfer = new DataTransfer();
|
|
||||||
dataTransfer.items.add(file);
|
|
||||||
|
|
||||||
// 查找上传区域
|
|
||||||
var dropZones = document.querySelectorAll('[class*="upload"], [class*="drop"], [class*="video"]');
|
|
||||||
var targetZone = null;
|
|
||||||
|
|
||||||
for (var i = 0; i < dropZones.length; i++) {
|
|
||||||
var zone = dropZones[i];
|
|
||||||
if (zone.offsetParent !== null &&
|
|
||||||
(zone.innerText.includes('上传') ||
|
|
||||||
zone.innerText.includes('时长') ||
|
|
||||||
zone.className.includes('upload'))) {
|
|
||||||
targetZone = zone;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetZone) {
|
|
||||||
targetZone = document.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟拖拽事件
|
|
||||||
var dragOverEvent = new DragEvent('dragover', {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
dataTransfer: dataTransfer
|
|
||||||
});
|
|
||||||
targetZone.dispatchEvent(dragOverEvent);
|
|
||||||
|
|
||||||
var dropEvent = new DragEvent('drop', {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
dataTransfer: dataTransfer
|
|
||||||
});
|
|
||||||
targetZone.dispatchEvent(dropEvent);
|
|
||||||
|
|
||||||
return {success: true, message: '拖拽事件已触发'};
|
|
||||||
})();
|
|
||||||
`, base64Data, fileName)
|
|
||||||
|
|
||||||
_, err = p.Page.Eval(script)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("拖拽事件上传失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.LogInfo("拖拽事件已触发")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// uploadViaNetworkIntercept 通过拦截网络请求上传
|
|
||||||
func (p *ShipinhaoVideoPublisher) uploadViaNetworkIntercept(filePath string) error {
|
|
||||||
p.LogInfo("尝试网络拦截上传...")
|
|
||||||
|
|
||||||
// 确保在正确的 iframe 中
|
|
||||||
p.ensureInEditorIframe()
|
|
||||||
|
|
||||||
// 点击上传区域
|
|
||||||
clickScript := `
|
|
||||||
var areas = document.querySelectorAll('[class*="upload"]');
|
|
||||||
for (var i = 0; i < areas.length; i++) {
|
|
||||||
if (areas[i].offsetParent !== null) {
|
|
||||||
areas[i].click();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
`
|
|
||||||
_, err := p.Page.Eval(clickScript)
|
|
||||||
if err != nil {
|
|
||||||
p.LogInfo(fmt.Sprintf("点击上传区域失败: %v", err))
|
|
||||||
}
|
|
||||||
p.Sleep(1)
|
|
||||||
|
|
||||||
// 使用 CDP 设置文件输入
|
|
||||||
fileInputs, err := p.Page.Elements("input[type='file']")
|
|
||||||
if err != nil || len(fileInputs) == 0 {
|
|
||||||
return fmt.Errorf("未找到文件输入框")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fileInputs[0].SetFiles([]string{filePath})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("设置文件失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.LogInfo("网络拦截上传完成")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// uploadViaReactEvent 通过 React 内部事件上传
|
|
||||||
func (p *ShipinhaoVideoPublisher) uploadViaReactEvent(filePath string) error {
|
|
||||||
p.LogInfo("尝试 React 事件上传...")
|
|
||||||
|
|
||||||
// 读取文件为 Base64
|
|
||||||
fileData, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("读取文件失败: %v", err)
|
|
||||||
}
|
|
||||||
base64Data := base64.StdEncoding.EncodeToString(fileData)
|
|
||||||
fileName := filepath.Base(filePath)
|
|
||||||
|
|
||||||
// 确保在正确的 iframe 中
|
|
||||||
p.ensureInEditorIframe()
|
|
||||||
|
|
||||||
script := fmt.Sprintf(`
|
|
||||||
(function() {
|
|
||||||
// 查找所有 DOM 元素
|
|
||||||
var allElements = document.querySelectorAll('*');
|
|
||||||
var uploadComponent = null;
|
|
||||||
|
|
||||||
for (var i = 0; i < allElements.length; i++) {
|
|
||||||
var el = allElements[i];
|
|
||||||
// 检查是否有 React 内部属性
|
|
||||||
if (el._reactRootContainer ||
|
|
||||||
Object.keys(el).some(key => key.startsWith('__react'))) {
|
|
||||||
// 查找包含上传相关文本的元素
|
|
||||||
if (el.innerText &&
|
|
||||||
(el.innerText.includes('上传') ||
|
|
||||||
el.innerText.includes('时长'))) {
|
|
||||||
uploadComponent = el;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uploadComponent) {
|
|
||||||
// 创建模拟文件
|
|
||||||
var byteCharacters = atob('%s');
|
|
||||||
var byteNumbers = new Array(byteCharacters.length);
|
|
||||||
for (var i = 0; i < byteCharacters.length; i++) {
|
|
||||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
||||||
}
|
|
||||||
var byteArray = new Uint8Array(byteNumbers);
|
|
||||||
var blob = new Blob([byteArray], {type: 'video/mp4'});
|
|
||||||
var file = new File([blob], '%s', {type: 'video/mp4'});
|
|
||||||
|
|
||||||
var dataTransfer = new DataTransfer();
|
|
||||||
dataTransfer.items.add(file);
|
|
||||||
|
|
||||||
// 触发 change 事件
|
|
||||||
var fileInput = document.querySelector('input[type="file"]');
|
|
||||||
if (fileInput) {
|
|
||||||
fileInput.files = dataTransfer.files;
|
|
||||||
var event = new Event('change', {bubbles: true});
|
|
||||||
fileInput.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试触发 React 的 onChange
|
|
||||||
var syntheticEvent = new Event('change', {bubbles: true});
|
|
||||||
syntheticEvent.target = {files: dataTransfer.files};
|
|
||||||
uploadComponent.dispatchEvent(syntheticEvent);
|
|
||||||
|
|
||||||
return {success: true};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {success: false, message: '未找到 React 组件'};
|
|
||||||
})();
|
|
||||||
`, base64Data, fileName)
|
|
||||||
|
|
||||||
result, err := p.Page.Eval(script)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("React 事件上传失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查结果
|
|
||||||
if result != nil {
|
|
||||||
p.LogInfo(fmt.Sprintf("React 事件触发结果: %v", result))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -493,7 +223,7 @@ func (p *ShipinhaoVideoPublisher) waitForUploadComplete(timeout int) (bool, stri
|
||||||
|
|
||||||
for time.Since(startTime).Seconds() < float64(timeout) {
|
for time.Since(startTime).Seconds() < float64(timeout) {
|
||||||
// 检查是否还存在上传区域特征
|
// 检查是否还存在上传区域特征
|
||||||
exists, _, err := p.Page.Has(".form-item.flex-start")
|
exists, _, err := p.iframe.Has(".form-item.flex-start")
|
||||||
if err == nil && exists {
|
if err == nil && exists {
|
||||||
p.LogInfo("视频上传成功")
|
p.LogInfo("视频上传成功")
|
||||||
p.Sleep(2)
|
p.Sleep(2)
|
||||||
|
|
@ -524,116 +254,41 @@ func (p *ShipinhaoVideoPublisher) inputTitleAndDescription() (bool, string) {
|
||||||
p.LogInfo(fmt.Sprintf("目标内容: %s", fullContent))
|
p.LogInfo(fmt.Sprintf("目标内容: %s", fullContent))
|
||||||
|
|
||||||
// 确保在正确的 iframe 中
|
// 确保在正确的 iframe 中
|
||||||
p.ensureInEditorIframe()
|
|
||||||
p.Sleep(1)
|
|
||||||
|
|
||||||
// 使用 JavaScript 直接设置编辑器内容
|
exist, input, err := p.iframe.Has("div[data-placeholder*='描述']")
|
||||||
script := `
|
|
||||||
function setEditorContent(content) {
|
|
||||||
// 定位编辑器元素
|
|
||||||
var editor = document.querySelector('.post-desc-box .input-editor, .input-editor, [contenteditable="true"]');
|
|
||||||
if (!editor) {
|
|
||||||
console.error('Editor element not found');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 聚焦编辑器
|
|
||||||
editor.focus();
|
|
||||||
|
|
||||||
// 清空现有内容
|
|
||||||
editor.innerText = '';
|
|
||||||
|
|
||||||
// 设置新内容
|
|
||||||
editor.innerText = content;
|
|
||||||
|
|
||||||
// 触发所有可能的事件
|
|
||||||
var events = ['input', 'change', 'blur', 'focus', 'keyup', 'keydown'];
|
|
||||||
events.forEach(function(eventType) {
|
|
||||||
var event = new Event(eventType, { bubbles: true, cancelable: true });
|
|
||||||
editor.dispatchEvent(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 尝试触发 React 的合成事件
|
|
||||||
var reactKey = Object.keys(editor).find(function(key) {
|
|
||||||
return key.startsWith('__reactEventHandlers');
|
|
||||||
});
|
|
||||||
if (reactKey && editor[reactKey] && editor[reactKey].onChange) {
|
|
||||||
var syntheticEvent = { target: { value: content, innerText: content }, type: 'change' };
|
|
||||||
editor[reactKey].onChange(syntheticEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Content set successfully, final value:', editor.innerText);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return setEditorContent(arguments[0]);
|
|
||||||
`
|
|
||||||
|
|
||||||
result, err := p.Page.Eval(script, fullContent)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Sprintf("设置内容失败: %v", err)
|
return false, "未找到编辑器元素"
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
return false, "未找到编辑器元素"
|
||||||
}
|
}
|
||||||
|
|
||||||
if result != nil {
|
input.Input(fullContent)
|
||||||
p.LogInfo("通过 JS 成功设置内容")
|
return true, "标签设置完成"
|
||||||
p.Sleep(1)
|
|
||||||
return true, "内容输入成功"
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, "未找到编辑器元素"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clickPublish 点击发布按钮
|
||||||
|
|
||||||
|
//func (p *ShipinhaoVideoPublisher) clickPublish() (bool, string) {
|
||||||
|
// p.LogInfo("点击发布按钮...")
|
||||||
|
// p.Sleep(5)
|
||||||
|
// // 直接用 JS 选择器,不经过 rod 元素
|
||||||
|
// _, err := p.iframe.Eval(`document.querySelector('div.form-btns button.weui-desktop-btn_primary')?.click()`)
|
||||||
|
// if err != nil {
|
||||||
|
// return false, fmt.Sprintf("点击按钮失败: %v", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// p.LogInfo("成功点击发表按钮")
|
||||||
|
// return true, ""
|
||||||
|
//}
|
||||||
|
|
||||||
// clickPublish 点击发布按钮
|
// clickPublish 点击发布按钮
|
||||||
func (p *ShipinhaoVideoPublisher) clickPublish() (bool, string) {
|
func (p *ShipinhaoVideoPublisher) clickPublish() (bool, string) {
|
||||||
p.LogInfo("点击发布按钮...")
|
p.LogInfo("点击发布按钮...")
|
||||||
|
p.Sleep(5)
|
||||||
|
// 直接在 iframe 上执行 JS,通过选择器找到并点击
|
||||||
|
p.iframe.Eval(`document.querySelector('div.form-btns button.weui-desktop-btn_primary')?.click()`)
|
||||||
|
|
||||||
// 滚动到底部
|
p.LogInfo("成功点击发表按钮")
|
||||||
p.Page.Eval(`window.scrollTo(0, document.body.scrollHeight);`)
|
return true, ""
|
||||||
p.Sleep(1)
|
|
||||||
|
|
||||||
// 确保在正确的 iframe 中
|
|
||||||
p.ensureInEditorIframe()
|
|
||||||
p.Sleep(1)
|
|
||||||
|
|
||||||
// 方法1: 通过文本 "发表" 查找按钮
|
|
||||||
script := `
|
|
||||||
var buttons = document.querySelectorAll('button');
|
|
||||||
for (var i = 0; i < buttons.length; i++) {
|
|
||||||
var btn = buttons[i];
|
|
||||||
var text = btn.innerText || btn.textContent || '';
|
|
||||||
if (text.trim() === '发表' && btn.offsetParent !== null) {
|
|
||||||
btn.click();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
`
|
|
||||||
|
|
||||||
result, err := p.Page.Eval(script)
|
|
||||||
if err == nil && result != nil {
|
|
||||||
p.LogInfo("已点击发表按钮")
|
|
||||||
return true, "已点击发表"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法2: 通过 CSS 选择器查找
|
|
||||||
publishSelectors := []string{
|
|
||||||
".weui-desktop-btn.weui-desktop-btn_primary",
|
|
||||||
"button.weui-desktop-btn_primary",
|
|
||||||
".weui-desktop-btn_wrp button",
|
|
||||||
"button[class*='primary']",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, selector := range publishSelectors {
|
|
||||||
btn, err := p.Page.Element(selector)
|
|
||||||
if err == nil && btn != nil {
|
|
||||||
visible, _ := btn.Visible()
|
|
||||||
if visible {
|
|
||||||
p.JSClick(btn)
|
|
||||||
p.LogInfo(fmt.Sprintf("通过选择器 %s 点击发表按钮", selector))
|
|
||||||
return true, "已点击发表"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.LogError("所有方法都未找到发表按钮")
|
|
||||||
return false, "未找到发表按钮"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,4 +44,5 @@ func (m *AppModule) Register(router fiber.Router) {
|
||||||
router.Post("/get_publish_list", vali(m.publishService.GetPublishList, &entitys.GetPublishListRequest{}))
|
router.Post("/get_publish_list", vali(m.publishService.GetPublishList, &entitys.GetPublishListRequest{}))
|
||||||
router.Post("/login_platform", vali(m.loginService.LoginPlatform, &entitys.LoginPlatformRequest{}))
|
router.Post("/login_platform", vali(m.loginService.LoginPlatform, &entitys.LoginPlatformRequest{}))
|
||||||
router.Post("/logout_platform", vali(m.loginService.LogoutPlatform, &entitys.LogoutPlatformRequest{}))
|
router.Post("/logout_platform", vali(m.loginService.LogoutPlatform, &entitys.LogoutPlatformRequest{}))
|
||||||
|
router.Get("/logs/:request_id", m.loginService.Log)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppService struct {
|
type AppService struct {
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
tokenImpl *impl.TokenImpl
|
tokenImpl *impl.TokenImpl
|
||||||
userImpl *impl.UserImpl
|
userImpl *impl.UserImpl
|
||||||
platImpl *impl.PlatImpl
|
platImpl *impl.PlatImpl
|
||||||
publishBiz *biz.PublishBiz
|
loginRelationImpl *impl.LoginRelationImpl
|
||||||
|
publishBiz *biz.PublishBiz
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAppService(
|
func NewAppService(
|
||||||
|
|
@ -31,14 +32,15 @@ func NewAppService(
|
||||||
userImpl *impl.UserImpl,
|
userImpl *impl.UserImpl,
|
||||||
platImpl *impl.PlatImpl,
|
platImpl *impl.PlatImpl,
|
||||||
publishBiz *biz.PublishBiz,
|
publishBiz *biz.PublishBiz,
|
||||||
|
loginRelationImpl *impl.LoginRelationImpl,
|
||||||
) *AppService {
|
) *AppService {
|
||||||
return &AppService{
|
return &AppService{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
tokenImpl: tokenImpl,
|
tokenImpl: tokenImpl,
|
||||||
|
loginRelationImpl: loginRelationImpl,
|
||||||
userImpl: userImpl,
|
userImpl: userImpl,
|
||||||
platImpl: platImpl,
|
platImpl: platImpl,
|
||||||
publishBiz: publishBiz,
|
publishBiz: publishBiz,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,11 +61,18 @@ func (a *AppService) LoginApp(c *fiber.Ctx, req *entitys.LoginAppRequest) error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
a.publishBiz.Notify(&entitys.NotifyData{
|
||||||
|
TokenId: int(tokenInfo.ID),
|
||||||
|
NotifyUrl: tokenInfo.Notifyurl,
|
||||||
|
Type: entitys.NotifyTypeToken,
|
||||||
|
Status: 1,
|
||||||
|
Msg: accessToken,
|
||||||
|
})
|
||||||
return pkg.HandleResponse(c, fiber.Map{
|
return pkg.HandleResponse(c, fiber.Map{
|
||||||
"access_token": accessToken,
|
"access_token": accessToken,
|
||||||
"user_limit": tokenInfo.UserLimit,
|
"user_limit": tokenInfo.UserLimit,
|
||||||
"expire_time": tokenInfo.ExpireTime,
|
"expire_time": tokenInfo.ExpireTime,
|
||||||
|
"user_name": tokenInfo.Name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +91,7 @@ func (a *AppService) GetUserAndAutoStatus(c *fiber.Ctx, req *entitys.GetUserAndA
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pm, err := manager.GetPublishManager(a.cfg, a.tokenImpl.GetDb())
|
pm, err := manager.GetPublishManager(a.cfg, a.tokenImpl.GetDb(), a.publishBiz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -124,22 +133,24 @@ func (a *AppService) AddUser(c *fiber.Ctx, req *entitys.AddUserRequest) error {
|
||||||
// 获取平台列表并关联
|
// 获取平台列表并关联
|
||||||
platCond := builder.NewCond().And(builder.Eq{"status": 1})
|
platCond := builder.NewCond().And(builder.Eq{"status": 1})
|
||||||
plats := make([]model.Plat, 0)
|
plats := make([]model.Plat, 0)
|
||||||
_, err = a.platImpl.GetListToStruct(c.UserContext(), &platCond, nil, &plats, "")
|
_, err = a.platImpl.GetListToStruct(c.UserContext(), &platCond, nil, &plats, "id desc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
loginRelations := make([]model.LoginRelation, 0, len(plats))
|
||||||
for _, plat := range plats {
|
for _, plat := range plats {
|
||||||
loginRelation := &model.LoginRelation{
|
loginRelations = append(loginRelations, model.LoginRelation{
|
||||||
UserIndex: userIndex,
|
UserIndex: userIndex,
|
||||||
PlatIndex: plat.Index,
|
PlatIndex: plat.Index,
|
||||||
LoginStatus: 2,
|
LoginStatus: 2,
|
||||||
Status: 1,
|
Status: 1,
|
||||||
}
|
})
|
||||||
// 这里需要调用 loginRelationImpl 的 Add 方法
|
|
||||||
_ = loginRelation
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
err = a.loginRelationImpl.Add(c.UserContext(), loginRelations)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return pkg.HandleResponse(c, fiber.Map{
|
return pkg.HandleResponse(c, fiber.Map{
|
||||||
"user_name": req.Name,
|
"user_name": req.Name,
|
||||||
"user_index": userIndex,
|
"user_index": userIndex,
|
||||||
|
|
@ -148,8 +159,12 @@ func (a *AppService) AddUser(c *fiber.Ctx, req *entitys.AddUserRequest) error {
|
||||||
|
|
||||||
func (a *AppService) DelUser(c *fiber.Ctx, req *entitys.DelUserRequest) error {
|
func (a *AppService) DelUser(c *fiber.Ctx, req *entitys.DelUserRequest) error {
|
||||||
// 需要从请求中获取 access_token
|
// 需要从请求中获取 access_token
|
||||||
|
_, err := a.publishBiz.ValidateAccessToken(c.UserContext(), req.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// 这里简化处理
|
// 这里简化处理
|
||||||
err := a.userImpl.DeleteByKey(c.UserContext(), a.userImpl.PrimaryKey(), int64(req.ID))
|
err = a.userImpl.DeleteByKey(c.UserContext(), "user_index", req.UserIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +179,6 @@ func (a *AppService) GetApp(c *fiber.Ctx, req *entitys.GetAppRequest) error {
|
||||||
|
|
||||||
cond := builder.NewCond().
|
cond := builder.NewCond().
|
||||||
And(builder.Eq{"login_relation.user_index": req.UserIndex}).
|
And(builder.Eq{"login_relation.user_index": req.UserIndex}).
|
||||||
And(builder.Eq{"login_relation.status": 1}).
|
|
||||||
And(builder.Eq{"plat.status": 1})
|
And(builder.Eq{"plat.status": 1})
|
||||||
|
|
||||||
result, err := a.platImpl.GetPlatListWithLoginStatus(c.UserContext(), &cond)
|
result, err := a.platImpl.GetPlatListWithLoginStatus(c.UserContext(), &cond)
|
||||||
|
|
|
||||||
|
|
@ -95,3 +95,20 @@ func (s *LoginService) ServeQrcode(c *fiber.Ctx, filename string) error {
|
||||||
}
|
}
|
||||||
return c.SendFile(filepath)
|
return c.SendFile(filepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LoginService) Log(c *fiber.Ctx) error {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return errcode.SysErr("读取日志失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkg.HandleResponse(c, fiber.Map{"content": string(content)})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ func (s *PublishService) PublishOn(c *fiber.Ctx, req *entitys.PublishOnRequest)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pm, err := manager.GetPublishManager(s.cfg, s.db)
|
pm, err := manager.GetPublishManager(s.cfg, s.db, s.publishBiz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +102,7 @@ func (s *PublishService) PublishOff(c *fiber.Ctx, req *entitys.PublishOffRequest
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pm, err := manager.GetPublishManager(s.cfg, s.db)
|
pm, err := manager.GetPublishManager(s.cfg, s.db, s.publishBiz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +118,7 @@ func (s *PublishService) PublishStatus(c *fiber.Ctx, req *entitys.PublishStatusR
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pm, err := manager.GetPublishManager(s.cfg, s.db)
|
pm, err := manager.GetPublishManager(s.cfg, s.db, s.publishBiz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -152,11 +152,12 @@ func (s *PublishService) PublishExecuteRetry(c *fiber.Ctx, req *entitys.PublishE
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pm, err := manager.GetPublishManager(s.cfg, s.db)
|
pm, err := manager.GetPublishManager(s.cfg, s.db, s.publishBiz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
result := pm.RetryTask(c.UserContext(), int(tokenInfo.ID), req.RequestID)
|
result := pm.RetryTask(c.UserContext(), int(tokenInfo.ID), req.RequestID)
|
||||||
|
|
||||||
return pkg.HandleResponse(c, result)
|
return pkg.HandleResponse(c, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
11
pkg/func.go
11
pkg/func.go
|
|
@ -331,3 +331,14 @@ func GetFreePort() (int, error) {
|
||||||
|
|
||||||
return l.Addr().(*net.TCPAddr).Port, nil
|
return l.Addr().(*net.TCPAddr).Port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StructToMap 将结构体转换为 map[string]any
|
||||||
|
func StructToMap(v any) (map[string]any, error) {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var m map[string]any
|
||||||
|
err = json.Unmarshal(b, &m)
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,16 @@ func (k DataTemp) GetOneBySearchStruct(ctx context.Context, cond *builder.Cond,
|
||||||
return errcode.SqlErr(err)
|
return errcode.SqlErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k DataTemp) GetOneBySearchStructWithOutCtx(cond *builder.Cond, data interface{}) (err error) {
|
||||||
|
query, _ := builder.ToBoundSQL(*cond)
|
||||||
|
if err = k.Db.Model(k.modelInstance()).Where(query).Limit(1).Find(&data).Error; err != nil {
|
||||||
|
return errcode.SqlErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,9 +271,10 @@ func (k DataTemp) GetByKey(ctx context.Context, key string, value interface{}, d
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k DataTemp) DeleteByKey(ctx context.Context, key string, value int64) error {
|
func (k DataTemp) DeleteByKey(ctx context.Context, key string, value interface{}) error {
|
||||||
result := k.Db.WithContext(ctx).Model(k.modelInstance()).Where(fmt.Sprintf("%s = ?", key), value).
|
result := k.Db.WithContext(ctx).Model(k.modelInstance()).Where(fmt.Sprintf("%s = ?", key), value).
|
||||||
Update("deleted_at", gorm.Expr("CURRENT_TIMESTAMP"))
|
Update("deleted_at", gorm.Expr("CURRENT_TIMESTAMP")).
|
||||||
|
Update("status", 0)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue