fix(t232): log freight import diagnostics
This commit is contained in:
@@ -306,7 +306,9 @@ func buildRouter(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webHandler, err := webui.NewHandler(webService, renderer)
|
||||
webHandler, err := webui.NewHandler(webService, renderer, func(event string) {
|
||||
log.Print(event)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -35,18 +35,30 @@ const (
|
||||
type Handler struct {
|
||||
service Service
|
||||
renderer *Renderer
|
||||
logEvent EventLogger
|
||||
}
|
||||
|
||||
func NewHandler(service Service, renderer *Renderer) (*Handler, error) {
|
||||
type EventLogger func(string)
|
||||
|
||||
func NewHandler(
|
||||
service Service,
|
||||
renderer *Renderer,
|
||||
loggers ...EventLogger,
|
||||
) (*Handler, error) {
|
||||
if service == nil {
|
||||
return nil, errors.New("admin web service is required")
|
||||
}
|
||||
if renderer == nil {
|
||||
return nil, errors.New("admin web renderer is required")
|
||||
}
|
||||
logEvent := EventLogger(func(string) {})
|
||||
if len(loggers) > 0 && loggers[0] != nil {
|
||||
logEvent = loggers[0]
|
||||
}
|
||||
return &Handler{
|
||||
service: service,
|
||||
renderer: renderer,
|
||||
logEvent: logEvent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -412,7 +424,9 @@ func (h *Handler) CreateFreightImport(ctx *gin.Context) {
|
||||
if err != nil {
|
||||
token, _ := csrfToken(ctx)
|
||||
code, title, message := freightImportError(err)
|
||||
h.render(ctx, serviceErrorStatus(err), "freight-import", freightImportPage{
|
||||
status := serviceErrorStatus(err)
|
||||
h.logFreightImportFailure(code, status)
|
||||
h.render(ctx, status, "freight-import", freightImportPage{
|
||||
Page: pageView{
|
||||
Title: "导入 ERP 货运",
|
||||
FreightCurrent: true,
|
||||
@@ -432,6 +446,17 @@ func (h *Handler) CreateFreightImport(ctx *gin.Context) {
|
||||
ctx.Redirect(http.StatusSeeOther, "/freight/import?sync="+pathEscape(run.ID))
|
||||
}
|
||||
|
||||
func (h *Handler) logFreightImportFailure(code string, status int) {
|
||||
if code == "" {
|
||||
h.logEvent("freight_import_failed status=" + strconv.Itoa(status))
|
||||
return
|
||||
}
|
||||
h.logEvent(
|
||||
"freight_import_preflight_failed code=" + code +
|
||||
" status=" + strconv.Itoa(status),
|
||||
)
|
||||
}
|
||||
|
||||
func freightImportError(err error) (string, string, string) {
|
||||
switch {
|
||||
case errors.Is(err, ErrOCRServiceInvalid):
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -1122,7 +1123,10 @@ func TestFreightImportShowsStablePreflightErrorDialog(t *testing.T) {
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
service := &fakeFreightService{fakeService: &fakeService{}, err: testCase.err}
|
||||
router := newTestRouter(t, service)
|
||||
var event string
|
||||
router := newTestRouterWithLogger(t, service, func(value string) {
|
||||
event = value
|
||||
})
|
||||
page := performRequest(t, router, http.MethodGet, "/freight/import", nil, "")
|
||||
if page.Code != http.StatusOK {
|
||||
t.Fatalf("import page status = %d", page.Code)
|
||||
@@ -1147,10 +1151,44 @@ func TestFreightImportShowsStablePreflightErrorDialog(t *testing.T) {
|
||||
service.createInput.OrderNumber != "ORDER-123" {
|
||||
t.Fatalf("dialog response/input = %d / %s / %+v", response.Code, response.Body, service.createInput)
|
||||
}
|
||||
wantEvent := "freight_import_preflight_failed code=" +
|
||||
testCase.code + " status=" + strconv.Itoa(testCase.status)
|
||||
if event != wantEvent || strings.Contains(event, "private") {
|
||||
t.Fatalf("event = %q, want %q", event, wantEvent)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreightImportLogsUnknownFailureWithoutRawError(t *testing.T) {
|
||||
service := &fakeFreightService{
|
||||
fakeService: &fakeService{},
|
||||
err: errors.New("private ERP response with cookie and password"),
|
||||
}
|
||||
var event string
|
||||
router := newTestRouterWithLogger(t, service, func(value string) {
|
||||
event = value
|
||||
})
|
||||
page := performRequest(t, router, http.MethodGet, "/freight/import", nil, "")
|
||||
cookie := csrfCookie(t, page)
|
||||
values := url.Values{
|
||||
"csrf_token": {cookie.Value},
|
||||
"idempotency_key": {mustToken(t)},
|
||||
"mode": {"ORDER_NUMBER"},
|
||||
"order_number": {"ORDER-123"},
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodPost, "/freight/import", strings.NewReader(values.Encode()))
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
request.AddCookie(cookie)
|
||||
response := httptest.NewRecorder()
|
||||
router.ServeHTTP(response, request)
|
||||
if response.Code != http.StatusInternalServerError ||
|
||||
event != "freight_import_failed status=500" ||
|
||||
strings.Contains(event, "private") {
|
||||
t.Fatalf("response/event = %d / %q", response.Code, event)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreightDetailCreatesProcurementTaskWithCSRF(t *testing.T) {
|
||||
const itemID = "00000000-0000-4000-8000-000000000002"
|
||||
service := &fakeProcurementService{
|
||||
@@ -1431,13 +1469,21 @@ func (service *fakeService) AuthorizeOrder(
|
||||
}
|
||||
|
||||
func newTestRouter(t *testing.T, service Service) http.Handler {
|
||||
return newTestRouterWithLogger(t, service, nil)
|
||||
}
|
||||
|
||||
func newTestRouterWithLogger(
|
||||
t *testing.T,
|
||||
service Service,
|
||||
logEvent EventLogger,
|
||||
) http.Handler {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
renderer, err := NewRenderer()
|
||||
if err != nil {
|
||||
t.Fatalf("NewRenderer() error = %v", err)
|
||||
}
|
||||
handler, err := NewHandler(service, renderer)
|
||||
handler, err := NewHandler(service, renderer, logEvent)
|
||||
if err != nil {
|
||||
t.Fatalf("NewHandler() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-07-29
|
||||
- 阶段:T-232 计划补充 ERP/OCR 导入预检的安全诊断日志
|
||||
- 阶段:T-232 已记录 ERP/OCR 导入预检的安全诊断日志
|
||||
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-219
|
||||
均按文档提交、实现提交的顺序纳入历史
|
||||
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码
|
||||
@@ -204,9 +204,9 @@
|
||||
- 已完成:另含 T-220 至 T-231 ERP 契约、货运存储、采购需求生成、日期增量同步、Go
|
||||
直连协议、OCR 会话预检、稳定预检错误、直连 `FreightSource`、旧 Connector 清理和受控本地
|
||||
凭证加载。
|
||||
- 进行中:T-232 将把货运导入的稳定预检 code 和 HTTP 状态写入 API 终端;不记录
|
||||
单号、凭证、Cookie、验证码、OCR 文本或 ERP 原文。
|
||||
- 下一步:确认开放 API、OCR/ERP 数据使用权限,并以受控单号执行一次不记录订单内容的 smoke。
|
||||
- 进行中:无。
|
||||
- 下一步:重启 API 后以受控单号导入一次,读取终端的货运预检稳定 code;再确认开放 API、
|
||||
OCR/ERP 数据使用权限并完成不记录订单内容的 smoke。
|
||||
|
||||
## 当前可运行内容
|
||||
|
||||
|
||||
+8
-5
@@ -4,7 +4,7 @@ title: 记录 ERP 货运导入预检的安全诊断日志
|
||||
phase: 2
|
||||
deps:
|
||||
- T-231
|
||||
status: PLANNED
|
||||
status: DONE
|
||||
created: 2026-07-29
|
||||
context_ref: 5ed27af
|
||||
work_branch: null
|
||||
@@ -33,11 +33,11 @@ OCR 服务、ERP 连通性还是协议错误;直接记录底层 error 又可
|
||||
|
||||
## 验收要点
|
||||
|
||||
- [ ] `/freight/import` 的五类稳定预检错误在 API 终端均有可搜索的 code 和 HTTP 状态。
|
||||
- [ ] 页面和 JSON 响应继续只返回匿名公开提示,日志不包含单号、凭证、Cookie、验证码、
|
||||
- [x] `/freight/import` 的五类稳定预检错误在 API 终端均有可搜索的 code 和 HTTP 状态。
|
||||
- [x] 页面和 JSON 响应继续只返回匿名公开提示,日志不包含单号、凭证、Cookie、验证码、
|
||||
OCR 文本或 ERP 原始正文。
|
||||
- [ ] 历史 `NewHandler` 调用保持兼容;日志记录器缺省为安全 no-op。
|
||||
- [ ] `go test ./...`、`go test -race ./...`、`go vet ./...` 和三个 Go 入口构建通过。
|
||||
- [x] 历史 `NewHandler` 调用保持兼容;日志记录器缺省为安全 no-op。
|
||||
- [x] `go test ./...`、`go test -race ./...`、`go vet ./...` 和三个 Go 入口构建通过。
|
||||
|
||||
## 边界
|
||||
|
||||
@@ -48,3 +48,6 @@ OCR 服务、ERP 连通性还是协议错误;直接记录底层 error 又可
|
||||
|
||||
- 2026-07-29:创建任务。现场检查时本机 `8080` 无监听进程,无法回放先前 `503`;确认
|
||||
T-231 的 SSR 分支会渲染稳定 code,但未向 API 终端写入任何诊断事件。
|
||||
- 2026-07-29:API 入口向 Web handler 注入标准日志;五类稳定预检错误记录固定事件、code
|
||||
和 status,未知失败也只记录固定事件/status。`go test ./...`、`go test -race ./...`、
|
||||
`go vet ./...` 及 api/migrate/authctl 构建均通过。
|
||||
|
||||
Reference in New Issue
Block a user