From 1982d304cae8b5195d08115a504d2a862f49c49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Wed, 27 May 2026 08:18:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=8A=A0=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/php.xml | 19 ++++++++++ README.md | 3 +- docs/COMMANDS.md | 8 +++-- internal/db/truncate.go | 19 ++++++++++ internal/hyfile/push-2026-05-22.log | 6 ++++ internal/logweb/server.go | 27 +++++++++++++++ internal/logweb/web/runs.html | 54 +++++++++++++++++++++++++++-- internal/logweb/web/static.css | 2 ++ log/transit/app-2026-05-22.log | 1 + 9 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 .idea/php.xml create mode 100644 internal/db/truncate.go diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..f324872 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 6e3af2c..06feef5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## 前置 -1. 云端配置 `HY_TRANSIT_API_TOKEN`,部署 `routes/hy.php` 与 `xk_hy_transit_cloud.sql`。 +1. 云端配置 `HY_TRANSIT_API_TOKEN`(与 Go `XK_API_TOKEN` 一致),可选 `HY_TRANSIT_PAYLOAD_ENCRYPT=true`(默认开启 result 传输加密),部署 `routes/hy.php` 与 `xk_hy_transit_cloud.sql`。 2. 本机执行 [`sql/hy_transit_schema.sql`](../sql/hy_transit_schema.sql)。 3. 内网部署 `xk-hy-forward-go`。 @@ -13,6 +13,7 @@ ```bash cp .env.example .env # 编辑 .env 填写 XK_API_TOKEN、HY_*、FORWARD_BASE_URL、FILE_UPLOAD_VIA_FORWARD、MYSQL_DSN +# XK_API_TOKEN 同时用于鉴权 Header 与解密云端 result(SHA256 派生 AES-128 密钥) ``` 程序启动时自动 `godotenv.Load()` 读取 `.env`。 diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 193ef98..2ea616d 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -2,7 +2,7 @@ ## 前置 -1. 复制环境配置:`cp .env.example .env`,填写 `XK_API_TOKEN`、`HY_*`、`FORWARD_BASE_URL`、`MYSQL_DSN`。 +1. 复制环境配置:`cp .env.example .env`,填写 `XK_API_TOKEN`、`HY_*`、`FORWARD_BASE_URL`、`MYSQL_DSN`。云端 `HY_TRANSIT_API_TOKEN` 须与 `XK_API_TOKEN` 一致;默认开启 `/api/hy/*` 响应 `result` 传输加密(`HY_TRANSIT_PAYLOAD_ENCRYPT`),Go 客户端自动解密。 2. 本机 MySQL 已执行 [`sql/hy_transit_schema.sql`](../../sql/hy_transit_schema.sql)。若库已存在且 Web 需展示**推送明文**,另执行 [`sql/hy_push_log_plain_migration.sql`](../../sql/hy_push_log_plain_migration.sql)。 3. 内网已启动 **xk-hy-forward-go**(`forward.exe` 或 `go run .`)。 4. 云端已部署 `xk_hy_transit_cloud.sql` 与 `routes/hy.php`。 @@ -47,10 +47,12 @@ go run ./cmd/transit |------|------|------| | 首页 | `/` | 导航 | | 文件日志 | `/logs` | app / pull / push | -| 同步流水 | `/runs` | 三表关联 + JSON 详情 | +| 同步流水 | `/runs` | 三表关联 + JSON 详情;可一键清空本机三表 | | 测试执行 | `/test` | POST 触发一次 sync | -环境变量:`LOG_WEB_ADDR`(默认 `127.0.0.1:8765`)、`LOG_WEB_ALLOW_TEST`(默认 `true`)。Cron 与 Web 测试共用互斥锁,不会并行执行两次 sync。 +API:`POST /api/runs/truncate` — 清空本机 `hy_push_log` / `hy_push_record` / `hy_sync_job`(需 MySQL 已连接;sync 运行中返回 409)。仅影响本机 `xk_hy_transit` 审计库。 + +环境变量:`LOG_WEB_ADDR`(默认 `127.0.0.1:8765`)、`LOG_WEB_ALLOW_TEST`(默认 `true`)。Cron 与 Web 测试共用互斥锁,不会并行执行两次 sync;截断与 sync 亦互斥。 --- diff --git a/internal/db/truncate.go b/internal/db/truncate.go new file mode 100644 index 0000000..58ba88b --- /dev/null +++ b/internal/db/truncate.go @@ -0,0 +1,19 @@ +package db + +// TruncateAuditTables 清空本机审计三表(不可恢复)。 +// 顺序:hy_push_log → hy_push_record → hy_sync_job。 +func (s *Store) TruncateAuditTables() error { + if err := s.Ping(); err != nil { + return err + } + for _, q := range []string{ + `TRUNCATE TABLE hy_push_log`, + `TRUNCATE TABLE hy_push_record`, + `TRUNCATE TABLE hy_sync_job`, + } { + if _, err := s.db.Exec(q); err != nil { + return err + } + } + return nil +} diff --git a/internal/hyfile/push-2026-05-22.log b/internal/hyfile/push-2026-05-22.log index b012a23..a77fc73 100644 --- a/internal/hyfile/push-2026-05-22.log +++ b/internal/hyfile/push-2026-05-22.log @@ -4,3 +4,9 @@ [push] 2026/05/22 08:57:01 chromedp: using browser C:\Program Files\Google\Chrome\Application\chrome.exe [push] 2026/05/22 08:57:01 chromedp: html_len=87 data_url_len=152 [push] 2026/05/22 08:57:02 chromedp: pdf bytes=5205 +[push] 2026/05/22 09:18:12 chromedp: using browser C:\Program Files\Google\Chrome\Application\chrome.exe +[push] 2026/05/22 09:18:12 chromedp: html_len=87 data_url_len=152 +[push] 2026/05/22 09:18:13 chromedp: pdf bytes=5205 +[push] 2026/05/22 09:18:22 chromedp: using browser C:\Program Files\Google\Chrome\Application\chrome.exe +[push] 2026/05/22 09:18:22 chromedp: html_len=87 data_url_len=152 +[push] 2026/05/22 09:18:23 chromedp: pdf bytes=5205 diff --git a/internal/logweb/server.go b/internal/logweb/server.go index 5e5071c..811f837 100644 --- a/internal/logweb/server.go +++ b/internal/logweb/server.go @@ -65,6 +65,7 @@ func (s *Server) routes() { s.mux.HandleFunc("/api/log", s.handleAPILog) s.mux.HandleFunc("/api/runs", s.handleAPIRuns) s.mux.HandleFunc("/api/runs/record", s.handleAPIRecord) + s.mux.HandleFunc("/api/runs/truncate", s.handleAPIRunsTruncate) s.mux.HandleFunc("/api/test/status", s.handleAPITestStatus) s.mux.HandleFunc("/api/test/sync", s.handleAPITestSync) @@ -97,6 +98,7 @@ func (s *Server) handleAPIMeta(w http.ResponseWriter, r *http.Request) { "mysqlOk": s.deps.MySQLOK, "mysqlError": s.deps.MySQLErr, "allowTest": s.deps.AllowTest && s.deps.Runner != nil, + "allowTruncate": s.deps.Store != nil && s.deps.MySQLOK, "hasRunner": s.deps.Runner != nil, "stepOptions": hy.StepSelectOptions(), "testStepOptions": hy.TestStepSelectOptions(), @@ -161,6 +163,31 @@ func (s *Server) handleAPIRecord(w http.ResponseWriter, r *http.Request) { writeJSON(w, detail) } +func (s *Server) handleAPIRunsTruncate(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + writeErr(w, 405, "POST only") + return + } + if s.deps.Store == nil || !s.deps.MySQLOK { + writeErr(w, 503, "MySQL 未连接") + return + } + if syncgate.IsRunning() { + writeErr(w, 409, "已有同步任务在执行中") + return + } + if err := s.deps.Store.TruncateAuditTables(); err != nil { + applog.Appf("web truncate audit tables failed: %v", err) + writeErr(w, 500, err.Error()) + return + } + applog.Appf("web truncate audit tables ok") + writeJSON(w, map[string]any{ + "ok": true, + "tables": []string{"hy_push_log", "hy_push_record", "hy_sync_job"}, + }) +} + func (s *Server) handleAPITestStatus(w http.ResponseWriter, r *http.Request) { writeJSON(w, enrichTestStatus(syncgate.GetStatus())) } diff --git a/internal/logweb/web/runs.html b/internal/logweb/web/runs.html index 3b377be..96d4485 100644 --- a/internal/logweb/web/runs.html +++ b/internal/logweb/web/runs.html @@ -17,6 +17,7 @@ +

同步任务

@@ -37,11 +38,41 @@ const anchorFromUrl = qs('anchor_date'); if (anchorFromUrl) document.getElementById('anchor').value = anchorFromUrl; +const truncateBtn = document.getElementById('truncate'); +let pageMeta = null; + +function updateTruncateButton(meta, syncRunning) { + if (!truncateBtn) return; + if (!meta || !meta.allowTruncate) { + truncateBtn.disabled = true; + truncateBtn.title = 'MySQL 未连接,无法清空'; + return; + } + if (syncRunning) { + truncateBtn.disabled = true; + truncateBtn.title = '同步任务执行中,请稍后再试'; + return; + } + truncateBtn.disabled = false; + truncateBtn.title = '清空本机 hy_push_log / hy_push_record / hy_sync_job(不可恢复)'; +} + (async () => { - const m = await loadMeta(); - if (m && m.stepOptions) fillStepSelect(document.getElementById('step'), m.stepOptions); + pageMeta = await loadMeta(); + if (pageMeta && pageMeta.stepOptions) fillStepSelect(document.getElementById('step'), pageMeta.stepOptions); + updateTruncateButton(pageMeta, false); })(); +async function refreshSyncGate() { + try { + const s = await api('/api/test/status'); + updateTruncateButton(pageMeta, !!(s && s.running)); + } catch (_) { + /* ignore */ + } +} +setInterval(refreshSyncGate, 2000); + async function load() { const anchor = document.getElementById('anchor').value; const step = document.getElementById('step').value; @@ -83,7 +114,26 @@ async function load() { } document.getElementById('load').onclick = () => load().catch(e => alert(e.message)); + +truncateBtn.onclick = async () => { + const tables = 'hy_push_log、hy_push_record、hy_sync_job'; + if (!confirm('将清空本机审计库三表(' + tables + '),不可恢复。\n仅影响本机 xk_hy_transit,不动云端数据。\n\n确定继续?')) return; + const typed = prompt('请输入 TRUNCATE 以确认清空:'); + if (typed !== 'TRUNCATE') { + if (typed !== null) alert('已取消:确认词不正确'); + return; + } + try { + await api('/api/runs/truncate', { method: 'POST' }); + alert('三表已清空'); + await load(); + } catch (e) { + alert(e.message); + } +}; + load().catch(e => alert(e.message)); +refreshSyncGate(); diff --git a/internal/logweb/web/static.css b/internal/logweb/web/static.css index e33ab25..2c46f24 100644 --- a/internal/logweb/web/static.css +++ b/internal/logweb/web/static.css @@ -66,6 +66,8 @@ button { background: #21262d; } button.primary { background: #238636; border-color: #2ea043; } +button.danger { background: #da3633; border-color: var(--err); } +button.danger:hover:not(:disabled) { filter: brightness(1.08); } button:disabled { opacity: 0.5; cursor: not-allowed; } table { width: 100%; diff --git a/log/transit/app-2026-05-22.log b/log/transit/app-2026-05-22.log index 709c7d4..03e60ac 100644 --- a/log/transit/app-2026-05-22.log +++ b/log/transit/app-2026-05-22.log @@ -51,3 +51,4 @@ [app] 2026/05/22 09:04:48 web test sync done step=all date=2026-05-22 ======== END web-test step=all date=2026-05-22 ok=true ======== [app] 2026/05/22 09:12:46 file upload via forward: http://127.0.0.1:16001/mng/file/auth/upload (cloud fileUploadUrl ignored) +[app] 2026/05/22 09:19:37 file upload via forward: http://127.0.0.1:16001/mng/file/auth/upload (cloud fileUploadUrl ignored)