mirror of
https://github.com/fish2018/pansou.git
synced 2025-11-25 19:37:43 +08:00
update
This commit is contained in:
@@ -23,11 +23,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"pansou/model"
|
"pansou/model"
|
||||||
"pansou/plugin"
|
"pansou/plugin"
|
||||||
"pansou/util/json"
|
"pansou/util/json"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
cloudscraper "github.com/Advik-B/cloudscraper/lib"
|
cloudscraper "github.com/Advik-B/cloudscraper/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
MaxConcurrentUsers = 10 // 最多使用的用户数
|
MaxConcurrentUsers = 10 // 最多使用的用户数
|
||||||
MaxConcurrentDetails = 50 // 最大并发详情请求数
|
MaxConcurrentDetails = 50 // 最大并发详情请求数
|
||||||
DebugLog = false // 调试日志开关(排查问题时改为true)
|
DebugLog = true // 调试日志开关(排查问题时改为true)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 默认账户配置(可通过Web界面添加更多账户)
|
// 默认账户配置(可通过Web界面添加更多账户)
|
||||||
@@ -566,6 +567,8 @@ func (p *GyingPlugin) loadAllUsers() {
|
|||||||
// initDefaultAccounts 初始化所有账户(异步执行,不阻塞启动)
|
// initDefaultAccounts 初始化所有账户(异步执行,不阻塞启动)
|
||||||
// 包括:1. DefaultAccounts(代码配置) 2. 从文件加载的用户(使用加密密码重新登录)
|
// 包括:1. DefaultAccounts(代码配置) 2. 从文件加载的用户(使用加密密码重新登录)
|
||||||
func (p *GyingPlugin) initDefaultAccounts() {
|
func (p *GyingPlugin) initDefaultAccounts() {
|
||||||
|
fmt.Printf("[Gying] ========== 异步初始化所有账户 ==========\n")
|
||||||
|
|
||||||
// 步骤1:处理DefaultAccounts(代码中配置的默认账户)
|
// 步骤1:处理DefaultAccounts(代码中配置的默认账户)
|
||||||
for i, account := range DefaultAccounts {
|
for i, account := range DefaultAccounts {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
@@ -604,6 +607,8 @@ func (p *GyingPlugin) initDefaultAccounts() {
|
|||||||
p.initOrRestoreUser(user.Username, password, "restore")
|
p.initOrRestoreUser(user.Username, password, "restore")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[Gying] ========== 所有账户初始化完成 ==========\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// initOrRestoreUser 初始化或恢复单个用户(登录并保存)
|
// initOrRestoreUser 初始化或恢复单个用户(登录并保存)
|
||||||
@@ -1126,7 +1131,14 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建cloudscraper实例(每个用户独立的实例)
|
// 创建cloudscraper实例(每个用户独立的实例)
|
||||||
scraper, err := cloudscraper.New()
|
// 关键配置:禁用403自动刷新,防止cookie被清空
|
||||||
|
scraper, err := cloudscraper.New(
|
||||||
|
cloudscraper.WithSessionConfig(
|
||||||
|
false, // refreshOn403 = false,禁用403时自动刷新(重要!)
|
||||||
|
365*24*time.Hour, // interval = 1年,基本不刷新
|
||||||
|
0, // maxRetries = 0
|
||||||
|
),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 创建cloudscraper失败: %v\n", err)
|
fmt.Printf("[Gying] 创建cloudscraper失败: %v\n", err)
|
||||||
@@ -1135,7 +1147,7 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] cloudscraper创建成功\n")
|
fmt.Printf("[Gying] cloudscraper创建成功(已禁用403自动刷新)\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建cookieMap用于收集所有cookies
|
// 创建cookieMap用于收集所有cookies
|
||||||
@@ -1354,6 +1366,54 @@ func min(a, b int) int {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============ 重新登录逻辑 ============
|
||||||
|
|
||||||
|
// reloginUser 重新登录指定用户
|
||||||
|
func (p *GyingPlugin) reloginUser(user *User) error {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] 🔄 开始重新登录用户: %s\n", user.UsernameMasked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密密码
|
||||||
|
password, err := p.decryptPassword(user.EncryptedPassword)
|
||||||
|
if err != nil {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 解密密码失败: %v\n", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("解密密码失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行登录
|
||||||
|
scraper, cookie, err := p.doLogin(user.Username, password)
|
||||||
|
if err != nil {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 重新登录失败: %v\n", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("重新登录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新scraper实例
|
||||||
|
p.scrapers.Store(user.Hash, scraper)
|
||||||
|
|
||||||
|
// 更新用户信息
|
||||||
|
user.Cookie = cookie
|
||||||
|
user.LoginAt = time.Now()
|
||||||
|
user.ExpireAt = time.Now().AddDate(0, 4, 0)
|
||||||
|
user.Status = "active"
|
||||||
|
|
||||||
|
if err := p.saveUser(user); err != nil {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ⚠️ 保存用户失败: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ✅ 用户 %s 重新登录成功\n", user.UsernameMasked)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ============ 搜索逻辑 ============
|
// ============ 搜索逻辑 ============
|
||||||
|
|
||||||
// executeSearchTasks 并发执行搜索任务
|
// executeSearchTasks 并发执行搜索任务
|
||||||
@@ -1376,8 +1436,14 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
fmt.Printf("[Gying] 用户 %s 没有scraper实例,尝试使用已保存的cookie创建\n", u.UsernameMasked)
|
fmt.Printf("[Gying] 用户 %s 没有scraper实例,尝试使用已保存的cookie创建\n", u.UsernameMasked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为用户创建新的cloudscraper实例
|
// 为用户创建新的cloudscraper实例(禁用403自动刷新)
|
||||||
newScraper, err := cloudscraper.New()
|
newScraper, err := cloudscraper.New(
|
||||||
|
cloudscraper.WithSessionConfig(
|
||||||
|
false, // refreshOn403 = false
|
||||||
|
365*24*time.Hour, // interval = 1年
|
||||||
|
0, // maxRetries = 0
|
||||||
|
),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 为用户 %s 创建scraper失败: %v\n", u.UsernameMasked, err)
|
fmt.Printf("[Gying] 为用户 %s 创建scraper失败: %v\n", u.UsernameMasked, err)
|
||||||
@@ -1390,7 +1456,7 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
scraper = newScraper
|
scraper = newScraper
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 已为用户 %s 创建新的scraper实例\n", u.UsernameMasked)
|
fmt.Printf("[Gying] 已为用户 %s 创建新的scraper实例(已禁用403刷新)\n", u.UsernameMasked)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var ok bool
|
var ok bool
|
||||||
@@ -1403,10 +1469,10 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err := p.searchWithScraper(keyword, scraper)
|
results, err := p.searchWithScraperWithRetry(keyword, scraper, u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 用户 %s 搜索失败: %v\n", u.UsernameMasked, err)
|
fmt.Printf("[Gying] 用户 %s 搜索失败(已重试): %v\n", u.UsernameMasked, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1423,7 +1489,49 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
return p.deduplicateResults(allResults)
|
return p.deduplicateResults(allResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchWithCookie 使用scraper搜索
|
// searchWithScraperWithRetry 使用scraper搜索(带403自动重新登录重试)
|
||||||
|
func (p *GyingPlugin) searchWithScraperWithRetry(keyword string, scraper *cloudscraper.Scraper, user *User) ([]model.SearchResult, error) {
|
||||||
|
results, err := p.searchWithScraper(keyword, scraper)
|
||||||
|
|
||||||
|
// 检测是否为403错误
|
||||||
|
if err != nil && strings.Contains(err.Error(), "403") {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ⚠️ 检测到403错误,尝试重新登录用户 %s\n", user.UsernameMasked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试重新登录
|
||||||
|
if reloginErr := p.reloginUser(user); reloginErr != nil {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 重新登录失败: %v\n", reloginErr)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("403错误且重新登录失败: %w", reloginErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取新的scraper实例
|
||||||
|
scraperVal, exists := p.scrapers.Load(user.Hash)
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("重新登录后未找到scraper实例")
|
||||||
|
}
|
||||||
|
|
||||||
|
newScraper, ok := scraperVal.(*cloudscraper.Scraper)
|
||||||
|
if !ok || newScraper == nil {
|
||||||
|
return nil, fmt.Errorf("重新登录后scraper实例无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用新scraper重试搜索
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] 🔄 使用新登录状态重试搜索\n")
|
||||||
|
}
|
||||||
|
results, err = p.searchWithScraper(keyword, newScraper)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("重新登录后搜索仍然失败: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchWithScraper 使用scraper搜索
|
||||||
func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Scraper) ([]model.SearchResult, error) {
|
func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Scraper) ([]model.SearchResult, error) {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] ---------- searchWithScraper 开始 ----------\n")
|
fmt.Printf("[Gying] ---------- searchWithScraper 开始 ----------\n")
|
||||||
@@ -1451,6 +1559,7 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
fmt.Printf("[Gying] 搜索响应状态码: %d\n", resp.StatusCode)
|
fmt.Printf("[Gying] 搜索响应状态码: %d\n", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取响应body
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
@@ -1471,6 +1580,21 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查403错误
|
||||||
|
if resp.StatusCode == 403 {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 收到403 Forbidden - Cookie可能已过期或被网站拒绝\n")
|
||||||
|
if len(body) > 0 {
|
||||||
|
preview := string(body)
|
||||||
|
if len(preview) > 300 {
|
||||||
|
preview = preview[:300] + "..."
|
||||||
|
}
|
||||||
|
fmt.Printf("[Gying] 403响应内容: %s\n", preview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("HTTP 403 Forbidden - 可能需要重新登录")
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 提取 _obj.search JSON
|
// 2. 提取 _obj.search JSON
|
||||||
re := regexp.MustCompile(`_obj\.search=(\{.*?\});`)
|
re := regexp.MustCompile(`_obj\.search=(\{.*?\});`)
|
||||||
matches := re.FindSubmatch(body)
|
matches := re.FindSubmatch(body)
|
||||||
@@ -1624,7 +1748,18 @@ func (p *GyingPlugin) fetchDetail(resourceID, resourceType string, scraper *clou
|
|||||||
fmt.Printf("[Gying] 响应状态码: %d\n", resp.StatusCode)
|
fmt.Printf("[Gying] 响应状态码: %d\n", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查403错误
|
||||||
|
if resp.StatusCode == 403 {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 详情接口返回403 - Cookie可能已过期\n")
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("HTTP 403 Forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ HTTP错误: %d\n", resp.StatusCode)
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
|
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1974,4 +2109,3 @@ func (p *GyingPlugin) markInactiveUsers() int {
|
|||||||
|
|
||||||
return markedCount
|
return markedCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user