From 21c895a0089eb448835d7499999a605d215e6f9e Mon Sep 17 00:00:00 2001 From: ceyhandagdas51272 Date: Wed, 24 Jun 2026 03:10:09 +0000 Subject: [PATCH 1/3] =?UTF-8?q?bypos-collector:=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E7=A1=AC=E7=BC=96=E7=A0=81=20sdogid=E3=80=81=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E3=80=81=E8=A1=A5=E5=85=A8=20CSV=20=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E3=80=81=E5=A2=9E=E5=8A=A0=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 改进内容: - sdogid 不再硬编码,优先从 $BYPOS_SDOGID 环境变量读取,其次 -sdogid 参数, 也可在 Web 控制台输入框填写;三者都未设时启动警告、采集报错 - 网络错误自动重试(最多 3 次,指数退避 500ms/1s/2s) - Web 控制台采集参数区增加 sdogid 输入框 - CSV 导出补全 retmsg 和 source 两列 - 新增 Go 单元测试(ean13Check、md5hex、sanitizeBarcodes、resolveSdogID 等) - README 更新配置说明 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tools/bypos-collector/README.md | 7 +- tools/bypos-collector/collect.go | 50 ++++++++++-- tools/bypos-collector/collect_test.go | 113 ++++++++++++++++++++++++++ tools/bypos-collector/main.go | 13 +-- tools/bypos-collector/web/index.html | 5 ++ 5 files changed, 171 insertions(+), 17 deletions(-) create mode 100644 tools/bypos-collector/collect_test.go diff --git a/tools/bypos-collector/README.md b/tools/bypos-collector/README.md index 6039816..34c5b8d 100644 --- a/tools/bypos-collector/README.md +++ b/tools/bypos-collector/README.md @@ -58,9 +58,10 @@ GET http://zc.bypos.net/byGoodsService/byMessage.asmx/GetGoodsinfo ## 配置 -- `sdogid`:中心库账号 id(本项目所属云店账号的授权 id)。默认值见 - `collect.go` 的 `defaultSdogID`,也可用 `-sdogid` 参数或控制台覆盖。 - **这是账号级凭证**——若本仓库对外公开,建议改为从环境变量/外部配置读取。 +- `sdogid`:中心库账号 id(本项目所属云店账号的授权 id)。 + 优先从环境变量 `BYPOS_SDOGID` 读取,其次是命令行 `-sdogid` 参数, + 也可以在 Web 控制台的输入框中填写。**三者都未设时启动会警告、开始采集时会报错。** +- 网络错误自动重试(最多 3 次,指数退避 500ms/1s/2s)。 ## 导入 goods/天工库 diff --git a/tools/bypos-collector/collect.go b/tools/bypos-collector/collect.go index ebe6672..7ecb619 100644 --- a/tools/bypos-collector/collect.go +++ b/tools/bypos-collector/collect.go @@ -20,12 +20,21 @@ import ( ) // ---- upstream config ---- -// sdogid is the 云店 account license id observed in the live request. It is -// configurable so the tool is not tied to a single account. -const defaultSdogID = "137966" const endpoint = "http://zc.bypos.net/byGoodsService/byMessage.asmx/GetGoodsinfo" +// maxRetries is the number of retry attempts for transient network errors. +const maxRetries = 3 + +// resolveSdogID reads the account id from $BYPOS_SDOGID, falling back to the +// explicit argument (CLI flag or Web UI input). Returns empty if neither set. +func resolveSdogID(explicit string) string { + if v := os.Getenv("BYPOS_SDOGID"); v != "" { + return v + } + return explicit +} + var stringTagRe = regexp.MustCompile(`(?s)]*>(.*)`) // Product is the normalized record we persist (one JSON object per line). @@ -87,8 +96,8 @@ func ean13Check(body string) (string, bool) { return body + strconv.Itoa(chk), true } -// lookup queries the upstream central library for one barcode. -func (c *Collector) lookup(ctx context.Context, barcode string) (*Product, error) { +// lookupOnce performs a single HTTP request to the upstream central library. +func (c *Collector) lookupOnce(ctx context.Context, barcode string) (*Product, error) { tsMs := strconv.FormatInt(time.Now().Unix()*1000, 10) // always ends in 000 sparm1 := md5hex(c.sdogID) sparm2 := md5hex(barcode + tsMs) @@ -147,6 +156,30 @@ func (c *Collector) lookup(ctx context.Context, barcode string) (*Product, error return p, nil } +// lookup queries the upstream with up to maxRetries retries on transient errors. +func (c *Collector) lookup(ctx context.Context, barcode string) (*Product, error) { + var lastErr error + for attempt := 0; attempt <= maxRetries; attempt++ { + if ctx.Err() != nil { + return nil, ctx.Err() + } + p, err := c.lookupOnce(ctx, barcode) + if err == nil { + return p, nil + } + lastErr = err + if attempt < maxRetries { + backoff := time.Duration(1<

② 采集参数

+
+ + +
@@ -165,6 +169,7 @@ async function start(){ start_body: document.getElementById('start').value.trim(), end_body: document.getElementById('end').value.trim(), list: document.getElementById('list').value, + sdog_id: document.getElementById('sdogid').value.trim(), concurrency: parseInt(document.getElementById('concurrency').value)||3, delay_ms: parseInt(document.getElementById('delay').value)||0, log_miss: document.getElementById('logmiss').checked, -- 2.52.0 From 5a641705dd2e0a750f9fcf1d07693f3881d7b24c Mon Sep 17 00:00:00 2001 From: ceyhandagdas51272 Date: Wed, 24 Jun 2026 03:23:07 +0000 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20Windows=20=E9=97=AA=E9=80=80=20?= =?UTF-8?q?=E2=80=94=20=E5=A2=9E=E5=8A=A0=E6=97=A5=E5=BF=97=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E8=BE=93=E5=87=BA=E3=80=81=E9=94=99=E8=AF=AF=E6=97=B6?= =?UTF-8?q?=E4=BF=9D=E6=8C=81=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tools/bypos-collector/main.go | 42 ++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/tools/bypos-collector/main.go b/tools/bypos-collector/main.go index fcc7017..e5d80a8 100644 --- a/tools/bypos-collector/main.go +++ b/tools/bypos-collector/main.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "embed" "encoding/csv" "encoding/json" @@ -22,7 +23,31 @@ var webFS embed.FS var collector *Collector +// waitExit keeps the console window open on Windows so the user can read errors. +func waitExit() { + if runtime.GOOS == "windows" { + fmt.Println("\n按回车键退出...") + bufio.NewReader(os.Stdin).ReadBytes('\n') + } +} + func main() { + // Log to file so crashes are diagnosable even if console closes. + lf, lfErr := os.OpenFile("bypos-collector.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if lfErr == nil { + log.SetOutput(io.MultiWriter(os.Stderr, lf)) + defer lf.Close() + } + + defer func() { + if r := recover(); r != nil { + log.Printf("程序崩溃: %v", r) + waitExit() + } + }() + + log.Println("bypos-collector 启动中...") + addr := flag.String("addr", "127.0.0.1:8765", "本地监听地址") noOpen := flag.Bool("no-open", false, "不自动打开浏览器") sdog := flag.String("sdogid", "", "中心库账号 id(优先读 $BYPOS_SDOGID 环境变量)") @@ -32,7 +57,12 @@ func main() { log.Println("警告: 未配置 sdogid,请通过 $BYPOS_SDOGID 环境变量、-sdogid 参数或控制台输入框指定") } - sub, _ := fs.Sub(webFS, "web") + sub, err := fs.Sub(webFS, "web") + if err != nil { + log.Printf("错误: 无法加载内嵌 web 资源: %v", err) + waitExit() + return + } mux := http.NewServeMux() mux.Handle("/", http.FileServer(http.FS(sub))) mux.HandleFunc("/api/start", handleStart) @@ -43,7 +73,9 @@ func main() { ln, err := net.Listen("tcp", *addr) if err != nil { - log.Fatalf("无法监听 %s: %v", *addr, err) + log.Printf("错误: 无法监听 %s: %v", *addr, err) + waitExit() + return } realAddr := ln.Addr().String() urlStr := "http://" + realAddr + "/" @@ -52,10 +84,14 @@ func main() { fmt.Println(" 控制台: " + urlStr) fmt.Println(" 关闭本窗口即停止程序") fmt.Println("==============================================") + log.Printf("监听地址: %s", realAddr) if !*noOpen { go openBrowser(urlStr) } - log.Fatal(http.Serve(ln, mux)) + if err := http.Serve(ln, mux); err != nil { + log.Printf("HTTP 服务异常退出: %v", err) + waitExit() + } } func openBrowser(url string) { -- 2.52.0 From cbb0968256de11dd7f7288b06fe8ca83c52bdbe2 Mon Sep 17 00:00:00 2001 From: ceyhandagdas51272 Date: Wed, 24 Jun 2026 04:17:55 +0000 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E7=AB=AF=E5=8F=A3=E5=8D=A0=E7=94=A8?= =?UTF-8?q?=E6=97=B6=E8=87=AA=E5=8A=A8=E9=80=89=E6=8B=A9=E5=8F=AF=E7=94=A8?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=EF=BC=8C=E4=B8=8D=E5=86=8D=E9=97=AA=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tools/bypos-collector/main.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/bypos-collector/main.go b/tools/bypos-collector/main.go index e5d80a8..009ebfd 100644 --- a/tools/bypos-collector/main.go +++ b/tools/bypos-collector/main.go @@ -73,9 +73,13 @@ func main() { ln, err := net.Listen("tcp", *addr) if err != nil { - log.Printf("错误: 无法监听 %s: %v", *addr, err) - waitExit() - return + log.Printf("端口 %s 被占用,自动选择可用端口...", *addr) + ln, err = net.Listen("tcp", "127.0.0.1:0") + if err != nil { + log.Printf("错误: 无法监听: %v", err) + waitExit() + return + } } realAddr := ln.Addr().String() urlStr := "http://" + realAddr + "/" -- 2.52.0