fix(t234): log OCR result in diagnostics
This commit is contained in:
@@ -154,9 +154,16 @@ func (manager *SessionManager) EnsureAuthenticated(ctx context.Context) error {
|
||||
return domain.ErrFreightSourceProtocol
|
||||
}
|
||||
code, err := manager.recognizer.Recognize(ctx, image.Content, image.ContentType)
|
||||
if err != nil || !validCaptchaCode(code) {
|
||||
if err != nil {
|
||||
manager.logOCRResultFailed()
|
||||
return domain.ErrFreightSourceOCRInvalid
|
||||
}
|
||||
code = strings.TrimSpace(code)
|
||||
if !validCaptchaCode(code) {
|
||||
manager.logOCRResultInvalid()
|
||||
return domain.ErrFreightSourceOCRInvalid
|
||||
}
|
||||
manager.logOCRResult(code)
|
||||
_, err = manager.Login(ctx, status.CaptchaTicket, code)
|
||||
if errors.Is(err, ErrLoginRejected) {
|
||||
return domain.ErrFreightSourceLoginRejected
|
||||
@@ -479,6 +486,30 @@ func (manager *SessionManager) logERPTransportFailure(request *http.Request) {
|
||||
)
|
||||
}
|
||||
|
||||
func (manager *SessionManager) logOCRResult(value string) {
|
||||
if !manager.diagnosticsOn {
|
||||
return
|
||||
}
|
||||
manager.diagnosticLog(
|
||||
"erp_ocr_result value=" + strconv.Quote(value) +
|
||||
" length=" + strconv.Itoa(len(value)),
|
||||
)
|
||||
}
|
||||
|
||||
func (manager *SessionManager) logOCRResultInvalid() {
|
||||
if !manager.diagnosticsOn {
|
||||
return
|
||||
}
|
||||
manager.diagnosticLog("erp_ocr_result class=invalid")
|
||||
}
|
||||
|
||||
func (manager *SessionManager) logOCRResultFailed() {
|
||||
if !manager.diagnosticsOn {
|
||||
return
|
||||
}
|
||||
manager.diagnosticLog("erp_ocr_result class=failed")
|
||||
}
|
||||
|
||||
func (manager *SessionManager) logERPResponsePreview(
|
||||
request *http.Request,
|
||||
response *http.Response,
|
||||
|
||||
@@ -252,6 +252,7 @@ func TestSessionManagerDiagnosticLogsAreRedacted(t *testing.T) {
|
||||
"erp_request method=GET path=/api/p/code1",
|
||||
"erp_response method=GET path=/api/p/code1 status=200",
|
||||
"body=omitted_non_json",
|
||||
`erp_ocr_result value="1234" length=4`,
|
||||
"erp_request method=POST path=/am/auth/login",
|
||||
"erp_response method=POST path=/am/auth/login status=200",
|
||||
`"status":false`,
|
||||
@@ -270,6 +271,67 @@ func TestSessionManagerDiagnosticLogsAreRedacted(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionManagerDiagnosticLogsHideInvalidOCRResults(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
recognizer *fixedRecognizer
|
||||
wantEvent string
|
||||
secret string
|
||||
}{
|
||||
{
|
||||
name: "invalid text",
|
||||
recognizer: &fixedRecognizer{code: "private\nOCR output"},
|
||||
wantEvent: "erp_ocr_result class=invalid",
|
||||
secret: "private",
|
||||
},
|
||||
{
|
||||
name: "recognizer failure",
|
||||
recognizer: &fixedRecognizer{err: errors.New("private OCR response")},
|
||||
wantEvent: "erp_ocr_result class=failed",
|
||||
secret: "private",
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
) {
|
||||
if request.URL.Path != CaptchaPath {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writer.Header().Set("Content-Type", "image/png")
|
||||
_, _ = writer.Write([]byte("private-captcha-image"))
|
||||
}))
|
||||
defer server.Close()
|
||||
var events []string
|
||||
manager, err := NewSessionManager(SessionConfig{
|
||||
BaseURL: server.URL,
|
||||
Username: "test-user",
|
||||
Password: "test-password",
|
||||
Timeout: time.Second,
|
||||
AllowInsecureHTTP: true,
|
||||
CaptchaRecognizer: testCase.recognizer,
|
||||
DiagnosticLogger: func(event string) {
|
||||
events = append(events, event)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewSessionManager() error = %v", err)
|
||||
}
|
||||
if err := manager.EnsureAuthenticated(context.Background()); !errors.Is(err, domain.ErrFreightSourceOCRInvalid) {
|
||||
t.Fatalf("EnsureAuthenticated() error = %v", err)
|
||||
}
|
||||
actual := strings.Join(events, "\n")
|
||||
if !strings.Contains(actual, testCase.wantEvent) || strings.Contains(actual, testCase.secret) ||
|
||||
strings.Contains(actual, "private-captcha-image") || strings.Contains(actual, "test-password") {
|
||||
t.Fatalf("diagnostic log = %s", actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fixedRecognizer struct {
|
||||
code string
|
||||
err error
|
||||
|
||||
+3
-1
@@ -225,7 +225,9 @@ ERP 配置来源:
|
||||
最多 4 KiB 的递归脱敏 JSON 摘要;不记录请求 body、完整 URL query、header、Cookie、验证码、
|
||||
OCR 文本、账号、密码、token、订单标识、收件信息、非 JSON body 或图片。诊断完成后必须设回
|
||||
`false` 并重启 API。Windows 可使用根目录 `start-backend.bat --erp-debug` 仅为本次 API
|
||||
进程覆盖开启;也可与 `--migrate` 组合。
|
||||
进程覆盖开启;也可与 `--migrate` 组合。该模式额外在 ERP 登录前输出单行
|
||||
`erp_ocr_result value="..." length=...`,值只限本次有效 OCR 结果;OCR 失败或无效时只输出
|
||||
`class=failed` 或 `class=invalid`,不输出原文。
|
||||
|
||||
会话不写 SQLite 或 Redis;服务重启后在下一次货运导入前重新经 OCR 建立会话。
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-07-29
|
||||
- 阶段:T-234 计划在受控诊断模式输出 OCR 验证码文本
|
||||
- 阶段:T-234 已在受控诊断模式输出 OCR 验证码文本
|
||||
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-219
|
||||
均按文档提交、实现提交的顺序纳入历史
|
||||
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码
|
||||
@@ -192,6 +192,7 @@
|
||||
| `docs/tasks/T-231.md` | DONE | 显示 ERP/OCR 导入预检稳定错误 |
|
||||
| `docs/tasks/T-232.md` | DONE | 记录 ERP/OCR 导入预检安全诊断日志 |
|
||||
| `docs/tasks/T-233.md` | DONE | 修复预检错误映射并增加受控 ERP 诊断日志 |
|
||||
| `docs/tasks/T-234.md` | DONE | 在受控诊断模式输出 OCR 验证码文本 |
|
||||
| `docs/design/` | 已确认 | T-202 原型索引、4 个管理页和 7 个 Android 页面 |
|
||||
| `deepseek总结.txt` | 已有 | 历史讨论摘要,不是正式需求权威 |
|
||||
| `android-buyer/` | 已有 | Roubao `main` 固定 commit 的 Android 基线 |
|
||||
@@ -203,11 +204,10 @@
|
||||
## 任务摘要
|
||||
|
||||
- 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-219。
|
||||
- 已完成:另含 T-220 至 T-233 ERP 契约、货运存储、采购需求生成、日期增量同步、Go
|
||||
- 已完成:另含 T-220 至 T-234 ERP 契约、货运存储、采购需求生成、日期增量同步、Go
|
||||
直连协议、OCR 会话预检、稳定预检错误、安全诊断日志、直连 `FreightSource`、旧 Connector
|
||||
清理和受控本地凭证加载。
|
||||
- 进行中:T-234 将仅在显式 ERP 诊断模式记录本次提交的有效 OCR 验证码文本,以定位 ERP 的
|
||||
“图片验证码不正确”。
|
||||
- 进行中:无。
|
||||
- 下一步:使用 `start-backend.bat --erp-debug` 重启 API 后以受控单号导入一次,读取 OCR 文本、
|
||||
ERP 请求/响应摘要和货运预检 code;再确认 OCR 规则或服务准确率。
|
||||
|
||||
|
||||
+8
-5
@@ -4,7 +4,7 @@ title: 在受控 ERP 诊断模式输出 OCR 验证码文本
|
||||
phase: 2
|
||||
deps:
|
||||
- T-233
|
||||
status: PLANNED
|
||||
status: DONE
|
||||
created: 2026-07-29
|
||||
context_ref: 1e703ef
|
||||
work_branch: null
|
||||
@@ -35,10 +35,10 @@ endpoint 可达且已处理登录请求,当前无法确认本机 OCR 为该次
|
||||
|
||||
## 验收要点
|
||||
|
||||
- [ ] 当前验证码错误场景可在 ERP 登录请求前看到 `erp_ocr_result value=... length=...`。
|
||||
- [ ] diagnostics 关闭时不记录 OCR 文本;无效/OCR 失败不记录其原始输出或错误正文。
|
||||
- [ ] 验证码图片、ticket、Cookie、账号密码和登录 payload 不进入日志。
|
||||
- [ ] 标准 Go 验证和入口构建通过。
|
||||
- [x] 当前验证码错误场景可在 ERP 登录请求前看到 `erp_ocr_result value=... length=...`。
|
||||
- [x] diagnostics 关闭时不记录 OCR 文本;无效/OCR 失败不记录其原始输出或错误正文。
|
||||
- [x] 验证码图片、ticket、Cookie、账号密码和登录 payload 不进入日志。
|
||||
- [x] 标准 Go 验证和入口构建通过。
|
||||
|
||||
## 边界
|
||||
|
||||
@@ -49,3 +49,6 @@ endpoint 可达且已处理登录请求,当前无法确认本机 OCR 为该次
|
||||
|
||||
- 2026-07-29:创建任务。现场 ERP 返回“图片验证码不正确”,因此进入受控 OCR 文本观测;
|
||||
当前证据不足以断言是 OCR 准确率、验证码规则还是外部 ERP 协议变化。
|
||||
- 2026-07-29:在有效 OCR 识别后、ERP 登录前增加仅 diagnostics 可见的转义文本/长度日志;
|
||||
OCR failed/invalid 只记录 class。固定测试覆盖图片、凭证、无效文本和 OCR error 不泄露;
|
||||
标准 Go 测试、race、vet 及 api/migrate/authctl 构建均通过。
|
||||
|
||||
Reference in New Issue
Block a user