fix: 淘宝与1688共用专属Chrome配置 (#37)
This commit is contained in:
@@ -146,13 +146,6 @@ func (a *App) beforeClose(ctx context.Context) bool {
|
||||
} else if closed {
|
||||
a.log.Info("退出前已关闭淘宝专属 Chrome")
|
||||
}
|
||||
stateFile = config.ResolveDataPath("1688浏览器运行状态.json")
|
||||
closed, err = taobao.CloseBrowserOnExit(stateFile, a.cfg.Alibaba1688.UserDataDir)
|
||||
if err != nil {
|
||||
a.log.Warn("退出时保留未能确认归属或关闭的 1688 专属 Chrome:%v", err)
|
||||
} else if closed {
|
||||
a.log.Info("退出前已关闭 1688 专属 Chrome")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -203,6 +196,9 @@ func (a *App) ResetConfig() config.Config {
|
||||
// OpenTaobaoLogin 打开专属 Chrome 的淘宝登录页。
|
||||
// 登录、验证码和安全验证全部由使用者在 Chrome 中手动完成。
|
||||
func (a *App) OpenTaobaoLogin() error {
|
||||
if err := a.ensureBrowserIdle(); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := a.ctx
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
@@ -228,6 +224,9 @@ func (a *App) OpenTaobaoLogin() error {
|
||||
|
||||
// CheckTaobaoLogin 执行 Cookie 名称与「我的淘宝」页面两层登录检查。
|
||||
func (a *App) CheckTaobaoLogin() (taobao.LoginStatus, error) {
|
||||
if err := a.ensureBrowserIdle(); err != nil {
|
||||
return taobao.LoginStatus{Message: err.Error()}, err
|
||||
}
|
||||
ctx := a.ctx
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
@@ -270,6 +269,9 @@ func (a *App) CheckTaobaoLogin() (taobao.LoginStatus, error) {
|
||||
|
||||
// CloseTaobaoBrowser 安全关闭归属明确的专属 Chrome。
|
||||
func (a *App) CloseTaobaoBrowser() error {
|
||||
if err := a.ensureBrowserIdle(); err != nil {
|
||||
return err
|
||||
}
|
||||
stateFile := config.ResolveDataPath("浏览器运行状态.json")
|
||||
closed, err := taobao.CloseBrowser(stateFile, a.cfg.Taobao.UserDataDir)
|
||||
if err != nil {
|
||||
@@ -286,6 +288,15 @@ func (a *App) CloseTaobaoBrowser() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ensureBrowserIdle() error {
|
||||
a.videoTaskMu.Lock()
|
||||
defer a.videoTaskMu.Unlock()
|
||||
if a.videoTask != nil && a.videoTask.Snapshot().State == task.StateRunning {
|
||||
return fmt.Errorf("取视频任务运行中,请完成或停止后再操作共用浏览器")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) emitTaobaoStatus(status taobao.LoginStatus) {
|
||||
if a.ctx != nil {
|
||||
runtime.EventsEmit(a.ctx, "taobao:status", status)
|
||||
|
||||
+7
-4
@@ -16,9 +16,12 @@ import (
|
||||
"cmsp/internal/task"
|
||||
)
|
||||
|
||||
// Open1688Login opens an isolated profile. The operator performs login manually.
|
||||
// Open1688Login opens 1688 in the shared dedicated Chrome. Login is manual.
|
||||
func (a *App) Open1688Login() error {
|
||||
state, err := taobao.EnsureBrowserWithState(a.appContext(), a.cfg.Alibaba1688, "https://www.1688.com/", "1688浏览器运行状态.json")
|
||||
if err := a.ensureBrowserIdle(); err != nil {
|
||||
return err
|
||||
}
|
||||
state, err := taobao.EnsureBrowser(a.appContext(), a.cfg.Taobao, "https://www.1688.com/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -30,7 +33,7 @@ func (a *App) Open1688Login() error {
|
||||
if err := page.Navigate("https://www.1688.com/", 0); err != nil {
|
||||
return err
|
||||
}
|
||||
a.log.Info("已打开 1688 专属 Chrome,请在浏览器中手动完成登录")
|
||||
a.log.Info("已在共用专属 Chrome 中打开 1688,请手动完成登录")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -49,7 +52,7 @@ func (a *App) prepare1688Video(ctx context.Context, productID string, cfg config
|
||||
if product.MainImage == "" {
|
||||
return result, task.Work{}, fmt.Errorf("商品没有可用于图搜的主图")
|
||||
}
|
||||
state, err := taobao.EnsureBrowserWithState(a.appContext(), cfg.Alibaba1688, "https://www.1688.com/", "1688浏览器运行状态.json")
|
||||
state, err := taobao.EnsureBrowser(a.appContext(), cfg.Taobao, "https://www.1688.com/")
|
||||
if err != nil {
|
||||
return result, task.Work{}, err
|
||||
}
|
||||
|
||||
+2
-9
@@ -37,8 +37,8 @@ huohanhan:
|
||||
|
||||
taobao:
|
||||
# [必须] 这里没有淘宝账号和密码,将来也不要加。
|
||||
# 淘宝登录必须由你在程序打开的专属 Chrome 里手动扫码完成,
|
||||
# 登录态保存在下面的 user_data_dir 里,程序不读取也不保存。
|
||||
# 淘宝和 1688 共用这一个专属 Chrome 用户数据目录,
|
||||
# 两站登录都由你手动完成;程序不读取或保存登录凭据。
|
||||
# 程序不会代填密码,也不会绕过验证码或滑块。
|
||||
|
||||
# chrome.exe 的完整路径。注意是文件不是目录。
|
||||
@@ -55,13 +55,6 @@ taobao:
|
||||
debug_port_start: 19666
|
||||
debug_port_end: 19765
|
||||
|
||||
alibaba1688:
|
||||
# 1688 单独的 Chrome Profile;在专属浏览器中手动登录。
|
||||
chrome_path: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
||||
user_data_dir: "C:\\Users\\你的用户名\\AppData\\Local\\cmsp\\1688浏览器"
|
||||
debug_port_start: 19766
|
||||
debug_port_end: 19865
|
||||
|
||||
download:
|
||||
# taobao 或 1688;切换仅影响新启动的下载批次。
|
||||
source: taobao
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Architecture-and-Code-Map
|
||||
wiki_url: https://git.ilapage.cn/chengma/cmsp/wiki/Architecture-and-Code-Map.-
|
||||
wiki_revision: 0ba3b5f3e6cf4630ef6fde05b0b17ba27a00b50b
|
||||
synchronized_at: 2026-10-07T07:49:50Z
|
||||
wiki_revision: 402b037c2ccb948e297aeb15e9679565ca80d2bb
|
||||
synchronized_at: 2026-10-07T08:13:39Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 架构与代码地图
|
||||
@@ -183,4 +183,4 @@ cmsp/
|
||||
|
||||
## 1688 视频下载来源(2026-10-07,#37)
|
||||
|
||||
商品列表“下载视频”旁选择淘宝或 1688;`download.source` 写入本机 `config.yaml`,批次启动时固定来源。1688 使用独立 Chrome Profile,操作者手动登录。`app_1688.go` 调用 `internal/alibaba1688/search.go`:上传商品主图到网页以图搜,按结果顺序进入候选详情,读取页面视频地址。`internal/downloader` 将候选 MP4 下载到临时文件;`internal/alibaba1688/quality.go` 用 ffprobe 校验、ffmpeg 抽帧检测黑底文字广告和静止拼图。只有判为 `usable` 的视频才晋升为商品文件,`internal/store.CompleteVideoDownload` 在同一 SQLite 事务中写视频记录与商品已下载状态。拒绝、无法判定、候选下载失败时保留原商品状态;上传仍沿用原有 erpgo 流程。
|
||||
商品列表“下载视频”旁选择淘宝或 1688;`download.source` 写入本机 `config.yaml`,批次启动时固定来源。1688 与淘宝共用现有专属 Chrome Profile、进程和运行状态文件;两个站点均由操作者手动登录。`app_1688.go` 调用 `internal/alibaba1688/search.go`:上传商品主图到网页以图搜,按结果顺序进入候选详情,读取页面视频地址。`internal/downloader` 将候选 MP4 下载到临时文件;`internal/alibaba1688/quality.go` 用 ffprobe 校验、ffmpeg 抽帧检测黑底文字广告和静止拼图。只有判为 `usable` 的视频才晋升为商品文件,`internal/store.CompleteVideoDownload` 在同一 SQLite 事务中写视频记录与商品已下载状态。拒绝、无法判定、候选下载失败时保留原商品状态;上传仍沿用原有 erpgo 流程。
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Business-Rules-and-Glossary
|
||||
wiki_url: https://git.ilapage.cn/chengma/cmsp/wiki/Business-Rules-and-Glossary.-
|
||||
wiki_revision: e05847d648d87a01f714ca7cb0af177e7630dcdf
|
||||
synchronized_at: 2026-10-07T07:49:50Z
|
||||
wiki_revision: 9050db999007f60f94f1d1b394a447836a61762e
|
||||
synchronized_at: 2026-10-07T08:13:39Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 业务规则与术语
|
||||
@@ -152,4 +152,4 @@ MTOP 搜索方式、签名与请求参数保持现状。商品原链接以实际
|
||||
|
||||
## 1688 作为可选视频来源(2026-10-07,#37)
|
||||
|
||||
下载来源由使用者在商品列表下载按钮旁选择并保存在本机配置,仅影响新批次。1688 与淘宝使用不同 Chrome Profile,登录由使用者手动完成。1688 图搜按结果顺序查找第一个有视频且通过内容检测的候选;每个候选先下载到临时文件,再检查时长、画面连续变化、黑底文字广告与静止图片。检测失败或无法可靠判定时删除临时文件,不写“已下载”;全部候选无可用视频时保留原状态,允许重试或换来源。通过检测后写入原有“已下载/待上传”状态,上传确认与 erpgo 上传流程不变。自动检测不能证明候选与目标商品完全同款,也不能证明视频使用权;批量上传的现有确认行为保持不变。
|
||||
下载来源由使用者在商品列表下载按钮旁选择并保存在本机配置,仅影响新批次。1688 与淘宝共用同一个专属 Chrome Profile;登录由使用者分别手动完成。取视频任务运行中不允许登录按钮导航或关闭该浏览器。1688 图搜按结果顺序查找第一个有视频且通过内容检测的候选;每个候选先下载到临时文件,再检查时长、画面连续变化、黑底文字广告与静止图片。检测失败或无法可靠判定时删除临时文件,不写“已下载”;全部候选无可用视频时保留原状态,允许重试或换来源。通过检测后写入原有“已下载/待上传”状态,上传确认与 erpgo 上传流程不变。自动检测不能证明候选与目标商品完全同款,也不能证明视频使用权;批量上传的现有确认行为保持不变。
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Local-Development-and-Verification
|
||||
wiki_url: https://git.ilapage.cn/chengma/cmsp/wiki/Local-Development-and-Verification.-
|
||||
wiki_revision: 8c23a3a5b52f132282e9b6b19ae08b6fbb879c55
|
||||
synchronized_at: 2026-10-07T07:49:51Z
|
||||
wiki_revision: 222b13d06ebc82783abd51eb5c82a9e6c8ea35e4
|
||||
synchronized_at: 2026-10-07T08:13:40Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 本地开发与验证
|
||||
@@ -235,4 +235,4 @@ Go 实现完成后适用:
|
||||
|
||||
## 1688 视频来源运行要求(2026-10-07,#37)
|
||||
|
||||
在商品列表下载按钮旁选“1688”,参数设置中检查 `alibaba1688.chrome_path`、独立 `user_data_dir` 与调试端口范围,点击“打开 1688 登录”并手动登录。1688 视频内容检测除了既有 ffprobe,还需要 `ffmpeg.exe`;打包运行时把二者放在 `cmsp.exe` 同目录,或加入 PATH。缺少 ffmpeg 时启动 1688 下载前即提示,不改变淘宝下载。`download.source` 保存在本机 `config.yaml`,默认 `taobao`。验证命令:`go test ./...`,`wails build`;真机验收分别用广告文字候选和正常商品视频检查拒绝、放行及 SQLite 状态。
|
||||
在商品列表下载按钮旁选“1688”,参数设置中检查 `taobao.chrome_path`、`taobao.user_data_dir` 与调试端口范围;这组配置供淘宝和 1688 共用。点击“打开 1688 登录”在同一专属 Chrome 中手动登录。旧 `alibaba1688` 配置块读取时忽略,保存后不再写出。1688 视频内容检测除了既有 ffprobe,还需要 `ffmpeg.exe`;打包运行时把二者放在 `cmsp.exe` 同目录,或加入 PATH。缺少 ffmpeg 时启动 1688 下载前即提示,不改变淘宝下载。`download.source` 保存在本机 `config.yaml`,默认 `taobao`。验证命令:`go test ./...`,`wails build`;真机验收分别用广告文字候选和正常商品视频检查拒绝、放行及 SQLite 状态。
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Troubleshooting
|
||||
wiki_url: https://git.ilapage.cn/chengma/cmsp/wiki/Troubleshooting
|
||||
wiki_revision: 7b2d3a3a39b3fa1b8b90f564e6ad5c727771dad7
|
||||
synchronized_at: 2026-10-07T07:49:52Z
|
||||
wiki_revision: 75c52d039b32fa90b24e594d49df79eb5a7302ad
|
||||
synchronized_at: 2026-10-07T08:13:41Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 故障排查
|
||||
@@ -145,4 +145,4 @@ SYNC_IN_PROGRESS 表示已有商品同步,请等待结束;Go侧拒绝重叠
|
||||
|
||||
## 1688 图搜与视频内容检测(2026-10-07,#37)
|
||||
|
||||
“1688 以图搜上传框未找到”或“图搜未打开结果页”时,先在独立 Chrome 中确认手动登录、首页正常显示和网页以图搜可用;不要自动绕过登录或安全验证。页面刚加载时控件和图片处理可能尚未就绪,程序会等待后再点击;持续失败时保留原任务状态,人工检查站点页面是否改版或访问受限。“1688 内容检测需要 ffmpeg.exe”时把 ffmpeg.exe 放在 cmsp.exe 同目录或加入 PATH,并保留既有 ffprobe。“候选均无可用商品视频”表示所查候选没有通过检测的 MP4,不表示该蝦皮商品在所有来源均无视频;可手动切换来源后重试。
|
||||
“1688 以图搜上传框未找到”或“图搜未打开结果页”时,先在淘宝与 1688 共用的专属 Chrome 中确认手动登录、首页正常显示和网页以图搜可用;不要自动绕过登录或安全验证。页面刚加载时控件和图片处理可能尚未就绪,程序会等待后再点击;持续失败时保留原任务状态,人工检查站点页面是否改版或访问受限。“1688 内容检测需要 ffmpeg.exe”时把 ffmpeg.exe 放在 cmsp.exe 同目录或加入 PATH,并保留既有 ffprobe。“候选均无可用商品视频”表示所查候选没有通过检测的 MP4,不表示该蝦皮商品在所有来源均无视频;可手动切换来源后重试。
|
||||
|
||||
@@ -112,7 +112,6 @@ async function pickDir(field, title) {
|
||||
if (!picked) return
|
||||
if (field === 'userDataDir') cfg.value.taobao.userDataDir = picked
|
||||
if (field === 'videoDir') cfg.value.download.videoDir = picked
|
||||
if (field === 'alibaba1688UserDataDir') cfg.value.alibaba1688.userDataDir = picked
|
||||
}
|
||||
|
||||
async function open1688Login() {
|
||||
@@ -226,8 +225,8 @@ onUnmounted(() => {
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<!-- 淘宝:注意这里没有账号密码 -->
|
||||
<n-card title="淘宝专属浏览器" size="small">
|
||||
<!-- 两个下载来源共用此浏览器;这里没有账号密码。 -->
|
||||
<n-card title="淘宝 / 1688 共用浏览器" size="small">
|
||||
<template #header-extra>
|
||||
<span class="faint">登录由你手动完成,程序不代填密码</span>
|
||||
</template>
|
||||
@@ -238,7 +237,7 @@ onUnmounted(() => {
|
||||
<n-button @click="pickChrome">选择文件</n-button>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="用户数据目录(保存淘宝登录态)">
|
||||
<n-form-item label="用户数据目录(保存淘宝和 1688 登录态)">
|
||||
<n-input-group>
|
||||
<n-input v-model:value="cfg.taobao.userDataDir" />
|
||||
<n-button @click="pickDir('userDataDir', '选择 Chrome 用户数据目录')">
|
||||
@@ -253,13 +252,16 @@ onUnmounted(() => {
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<div class="taobao-actions">
|
||||
<n-button :loading="openingTaobao" :disabled="checkingTaobao" @click="openTaobaoLogin">
|
||||
<n-button :loading="openingTaobao" :disabled="checkingTaobao || opening1688" @click="openTaobaoLogin">
|
||||
打开淘宝登录
|
||||
</n-button>
|
||||
<n-button :loading="opening1688" :disabled="checkingTaobao || openingTaobao" @click="open1688Login">
|
||||
打开 1688 登录
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="checkingTaobao"
|
||||
:disabled="openingTaobao"
|
||||
:disabled="openingTaobao || opening1688"
|
||||
@click="checkTaobaoLogin"
|
||||
>
|
||||
检查登录
|
||||
@@ -281,31 +283,11 @@ onUnmounted(() => {
|
||||
{{ taobaoStatus.message }}
|
||||
</div>
|
||||
<n-alert type="info" :bordered="false" style="margin-top: 4px">
|
||||
登录必须在程序打开的专属 Chrome 里手动扫码完成。程序不会代填账号密码,
|
||||
两个网站共用此 Chrome,登录均由你手动完成。程序不会代填账号密码,
|
||||
也不会绕过验证码或滑块。
|
||||
</n-alert>
|
||||
</n-card>
|
||||
|
||||
<n-card title="1688 专属浏览器" size="small">
|
||||
<n-form label-placement="top" size="small">
|
||||
<n-form-item label="Chrome 可执行文件">
|
||||
<n-input v-model:value="cfg.alibaba1688.chromePath" />
|
||||
</n-form-item>
|
||||
<n-form-item label="独立用户数据目录(登录由你手动完成)">
|
||||
<n-input-group>
|
||||
<n-input v-model:value="cfg.alibaba1688.userDataDir" />
|
||||
<n-button @click="pickDir('alibaba1688UserDataDir', '选择 1688 Chrome 用户数据目录')">选择目录</n-button>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="调试端口范围">
|
||||
<n-input-number v-model:value="cfg.alibaba1688.debugPortStart" :show-button="false" style="width: 96px" />
|
||||
<span class="dash">—</span>
|
||||
<n-input-number v-model:value="cfg.alibaba1688.debugPortEnd" :show-button="false" style="width: 96px" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button :loading="opening1688" @click="open1688Login">打开 1688 登录</n-button>
|
||||
</n-card>
|
||||
|
||||
<!-- 下载与任务 -->
|
||||
<n-card title="下载与任务" size="small">
|
||||
<n-form label-placement="top" size="small">
|
||||
|
||||
@@ -35,12 +35,11 @@ import (
|
||||
// yaml tag 就是配置文件里的键名。改名会导致老配置读不出来,
|
||||
// 所以除非有充分理由,不要改已有字段的 tag。
|
||||
type Config struct {
|
||||
ERPGo ERPGoConfig `yaml:"erpgo" json:"erpgo"`
|
||||
Huohanhan HuohanhanConfig `yaml:"huohanhan" json:"huohanhan"`
|
||||
Taobao TaobaoConfig `yaml:"taobao" json:"taobao"`
|
||||
Alibaba1688 TaobaoConfig `yaml:"alibaba1688" json:"alibaba1688"`
|
||||
Download DownloadConfig `yaml:"download" json:"download"`
|
||||
Upload UploadConfig `yaml:"upload" json:"upload"`
|
||||
ERPGo ERPGoConfig `yaml:"erpgo" json:"erpgo"`
|
||||
Huohanhan HuohanhanConfig `yaml:"huohanhan" json:"huohanhan"`
|
||||
Taobao TaobaoConfig `yaml:"taobao" json:"taobao"`
|
||||
Download DownloadConfig `yaml:"download" json:"download"`
|
||||
Upload UploadConfig `yaml:"upload" json:"upload"`
|
||||
}
|
||||
|
||||
// ERPGoConfig 用于查询店铺和商品;APIKey 只保存在本机,禁止进入日志。
|
||||
@@ -75,14 +74,14 @@ type HuohanhanConfig struct {
|
||||
OCRURL string `yaml:"ocr_url" json:"ocrUrl"`
|
||||
}
|
||||
|
||||
// TaobaoConfig 是淘宝专属浏览器的设置。
|
||||
// TaobaoConfig 是淘宝与 1688 共用的专属浏览器设置。
|
||||
//
|
||||
// 这里故意没有账号和密码字段:淘宝登录由使用者手动完成,
|
||||
// 登录态保存在 Chrome 用户数据目录里,程序不读取也不保存。
|
||||
type TaobaoConfig struct {
|
||||
// ChromePath 是 chrome.exe 的完整路径,注意是文件不是目录。
|
||||
ChromePath string `yaml:"chrome_path" json:"chromePath"`
|
||||
// UserDataDir 是专属 Chrome 的用户数据目录,淘宝登录态存在这里。
|
||||
// UserDataDir 是专属 Chrome 的用户数据目录,两个站点的登录态均存在这里。
|
||||
UserDataDir string `yaml:"user_data_dir" json:"userDataDir"`
|
||||
// DebugPortStart / DebugPortEnd 是分配调试端口的范围。
|
||||
DebugPortStart int `yaml:"debug_port_start" json:"debugPortStart"`
|
||||
@@ -133,12 +132,6 @@ func Default() Config {
|
||||
DebugPortStart: 19666,
|
||||
DebugPortEnd: 19765,
|
||||
},
|
||||
Alibaba1688: TaobaoConfig{
|
||||
ChromePath: `C:\Program Files\Google\Chrome\Application\chrome.exe`,
|
||||
UserDataDir: filepath.Join(os.Getenv("LOCALAPPDATA"), "cmsp", "1688浏览器"),
|
||||
DebugPortStart: 19766,
|
||||
DebugPortEnd: 19865,
|
||||
},
|
||||
Download: DownloadConfig{
|
||||
Source: "taobao",
|
||||
VideoDir: DefaultVideoDir(),
|
||||
@@ -207,21 +200,6 @@ func (c Config) Validate() error {
|
||||
if c.Download.Source != "taobao" && c.Download.Source != "1688" {
|
||||
return fmt.Errorf("下载来源须为 taobao 或 1688")
|
||||
}
|
||||
if strings.TrimSpace(c.Alibaba1688.ChromePath) == "" || strings.TrimSpace(c.Alibaba1688.UserDataDir) == "" {
|
||||
return fmt.Errorf("1688 专属 Chrome 路径和用户数据目录不能为空")
|
||||
}
|
||||
if filepath.Clean(c.Alibaba1688.UserDataDir) == filepath.Clean(t.UserDataDir) {
|
||||
return fmt.Errorf("1688 和淘宝不能共用 Chrome 用户数据目录")
|
||||
}
|
||||
if err := checkPort(c.Alibaba1688.DebugPortStart, "1688 调试端口起始"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkPort(c.Alibaba1688.DebugPortEnd, "1688 调试端口结束"); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.Alibaba1688.DebugPortEnd < c.Alibaba1688.DebugPortStart {
|
||||
return fmt.Errorf("1688 调试端口结束不能小于起始端口")
|
||||
}
|
||||
if strings.TrimSpace(t.ChromePath) == "" {
|
||||
return fmt.Errorf("Chrome 可执行文件路径不能为空")
|
||||
}
|
||||
@@ -402,13 +380,6 @@ taobao:
|
||||
debug_port_start: %d
|
||||
debug_port_end: %d
|
||||
|
||||
alibaba1688:
|
||||
# 1688 使用独立 Profile,登录只能在专属 Chrome 中手动完成。
|
||||
chrome_path: %s
|
||||
user_data_dir: %s
|
||||
debug_port_start: %d
|
||||
debug_port_end: %d
|
||||
|
||||
download:
|
||||
# 下载来源:taobao 或 1688;仅影响新批次。
|
||||
source: %s
|
||||
@@ -457,10 +428,6 @@ upload:
|
||||
quoted(c.Taobao.UserDataDir),
|
||||
c.Taobao.DebugPortStart,
|
||||
c.Taobao.DebugPortEnd,
|
||||
quoted(c.Alibaba1688.ChromePath),
|
||||
quoted(c.Alibaba1688.UserDataDir),
|
||||
c.Alibaba1688.DebugPortStart,
|
||||
c.Alibaba1688.DebugPortEnd,
|
||||
quoted(c.Download.Source),
|
||||
quoted(c.Download.VideoDir),
|
||||
c.Download.MaxVideosPerProduct,
|
||||
|
||||
@@ -53,20 +53,31 @@ func TestDefaultConfigIsValid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVideoSourcePersistsAndKeepsIndependentBrowser(t *testing.T) {
|
||||
func TestVideoSourcePersistsWithSharedBrowser(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||||
cfg := Default()
|
||||
cfg.Download.Source = "1688"
|
||||
if cfg.Alibaba1688.UserDataDir == cfg.Taobao.UserDataDir {
|
||||
t.Fatal("1688 and Taobao must not share Chrome profile")
|
||||
}
|
||||
cfg.Taobao.UserDataDir = filepath.Join(t.TempDir(), "shared-profile")
|
||||
if err := Save(path, cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loaded, err := Load(path)
|
||||
if err != nil || loaded.Download.Source != "1688" || loaded.Alibaba1688.UserDataDir != cfg.Alibaba1688.UserDataDir {
|
||||
if err != nil || loaded.Download.Source != "1688" || loaded.Taobao.UserDataDir != cfg.Taobao.UserDataDir {
|
||||
t.Fatalf("source/browser config not persisted: %v", err)
|
||||
}
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil || strings.Contains(string(content), "alibaba1688:") {
|
||||
t.Fatalf("config should contain only the shared browser: %v", err)
|
||||
}
|
||||
// Previously saved independent-profile settings must not block loading.
|
||||
old := append(content, []byte("\nalibaba1688:\n user_data_dir: legacy-profile\n")...)
|
||||
if err := os.WriteFile(path, old, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loaded, err = Load(path)
|
||||
if err != nil || loaded.Download.Source != "1688" || loaded.Taobao.UserDataDir != cfg.Taobao.UserDataDir {
|
||||
t.Fatalf("legacy config should use the shared browser: %v", err)
|
||||
}
|
||||
cfg.Download.Source = "unknown"
|
||||
if cfg.Validate() == nil {
|
||||
t.Fatal("unsupported source accepted")
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -22,6 +23,8 @@ import (
|
||||
|
||||
const browserStateName = "浏览器运行状态.json"
|
||||
|
||||
var browserStartMu sync.Mutex
|
||||
|
||||
// BrowserState 是专属 Chrome 的运行元数据。
|
||||
//
|
||||
// 文件里只保存进程和路径信息,不保存 Cookie、token 或任何登录凭据。
|
||||
@@ -35,11 +38,8 @@ type BrowserState struct {
|
||||
|
||||
// EnsureBrowser 复用归属明确且可连接的专属 Chrome;否则安全地启动一个新实例。
|
||||
func EnsureBrowser(ctx context.Context, cfg config.TaobaoConfig, openURL string) (BrowserState, error) {
|
||||
return EnsureBrowserWithState(ctx, cfg, openURL, browserStateName)
|
||||
}
|
||||
|
||||
// EnsureBrowserWithState isolates runtime ownership metadata for another site/profile.
|
||||
func EnsureBrowserWithState(ctx context.Context, cfg config.TaobaoConfig, openURL, stateName string) (BrowserState, error) {
|
||||
browserStartMu.Lock()
|
||||
defer browserStartMu.Unlock()
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
@@ -56,10 +56,7 @@ func EnsureBrowserWithState(ctx context.Context, cfg config.TaobaoConfig, openUR
|
||||
return BrowserState{}, fmt.Errorf("创建 Chrome 用户数据目录失败:%w", err)
|
||||
}
|
||||
|
||||
if stateName != browserStateName && stateName != "1688浏览器运行状态.json" {
|
||||
return BrowserState{}, fmt.Errorf("不支持的浏览器状态文件")
|
||||
}
|
||||
stateFile := config.ResolveDataPath(stateName)
|
||||
stateFile := config.ResolveDataPath(browserStateName)
|
||||
state, found, readErr := readBrowserState(stateFile)
|
||||
if readErr == nil && found && browserStateReusable(state, profile) {
|
||||
return state, nil
|
||||
|
||||
Reference in New Issue
Block a user