From 76e725ca962c5d4f36c5b71e3b6824a80775c97d Mon Sep 17 00:00:00 2001 From: AskaEth Date: Wed, 9 Sep 2026 20:07:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=86=85=E9=83=A8=E7=BD=B2=E5=90=8D?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=BC=96=E8=AF=91=E6=9C=9F=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=EF=BC=88=E6=9E=84=E5=BB=BA=E6=9C=BA=E6=9C=AC=E5=9C=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E4=B8=8D=E8=BF=9B=E4=BB=93=E5=BA=93=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E5=85=B3=E4=BA=8E=E9=A1=B5=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ deploy/build.bat | 12 +++++++++++- server/internal/httpapi/handlers_system.go | 20 ++++++++++++++++++++ server/internal/httpapi/server.go | 1 + server/internal/version/version.go | 11 +++++++++++ web/src/admin/pages/AboutPage.vue | 20 ++++++++++++++++++++ 6 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 server/internal/httpapi/handlers_system.go create mode 100644 server/internal/version/version.go diff --git a/.gitignore b/.gitignore index 20e7026..d7739f1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ data/ rules/* !rules/README.md +# 内部署名(构建期注入,绝不进仓库) +deploy/signature.local.txt + # 开发文档(内部设计文档,不进仓库) devdocs/ diff --git a/deploy/build.bat b/deploy/build.bat index 2261e9c..ac8e27b 100644 --- a/deploy/build.bat +++ b/deploy/build.bat @@ -30,7 +30,17 @@ cd .. echo === [3/5] server build === cd server if not exist ..\dist mkdir ..\dist -go build -o ..\dist\scoreboard.exe . || exit /b 1 +REM 内部署名从构建机本地文件注入(deploy\signature.local.txt 已 gitignore,不进公开仓库); +REM 文件不存在时二进制里也是空的,源码永远不含真实署名。 +set "INTERNAL_SIGNATURE=" +if exist "..\deploy\signature.local.txt" set /p INTERNAL_SIGNATURE=<"..\deploy\signature.local.txt" +if defined INTERNAL_SIGNATURE ( + go build -ldflags "-X git.yeij.top/AskaEth/Scoreboard/server/internal/version.InternalSignature=%INTERNAL_SIGNATURE%" -o ..\dist\scoreboard.exe . || exit /b 1 + echo internal signature injected +) else ( + go build -o ..\dist\scoreboard.exe . || exit /b 1 + echo no internal signature +) cd .. echo === [4/5] console deps === diff --git a/server/internal/httpapi/handlers_system.go b/server/internal/httpapi/handlers_system.go new file mode 100644 index 0000000..ca63f8d --- /dev/null +++ b/server/internal/httpapi/handlers_system.go @@ -0,0 +1,20 @@ +package httpapi + +import ( + "net/http" + + "git.yeij.top/AskaEth/Scoreboard/server/internal/auth" + "git.yeij.top/AskaEth/Scoreboard/server/internal/version" +) + +// systemRoutes 挂载系统级只读信息。 +func (s *server) systemRoutes(mux *http.ServeMux) { + // 内部署名是编译期注入的,不暴露给匿名访客:至少要登录(recorder 及以上)才能读到 + mux.HandleFunc("GET /api/system/about", s.read(auth.RoleRecorder, s.systemAbout)) +} + +func (s *server) systemAbout(w http.ResponseWriter, r *http.Request) (any, error) { + return map[string]any{ + "internalSignature": version.InternalSignature, + }, nil +} diff --git a/server/internal/httpapi/server.go b/server/internal/httpapi/server.go index 17fa610..acab65a 100644 --- a/server/internal/httpapi/server.go +++ b/server/internal/httpapi/server.go @@ -76,6 +76,7 @@ func New(opts Options) http.Handler { s.routes(mux) s.exportRoutes(mux) s.rulesRoutes(mux) + s.systemRoutes(mux) if opts.WS != nil { mux.Handle("/ws", opts.WS) } diff --git a/server/internal/version/version.go b/server/internal/version/version.go new file mode 100644 index 0000000..090ed89 --- /dev/null +++ b/server/internal/version/version.go @@ -0,0 +1,11 @@ +// Package version 保存构建期注入的版本与署名信息。 +package version + +// Version 由构建脚本注入:-ldflags "-X .../version.Version=..." +var Version = "dev" + +// InternalSignature 是内部署名,**由构建机本地文件注入**,编译期写进二进制。 +// +// 源码里必须永远保持为空字符串:真实署名只存在于构建机的 deploy/signature.local.txt +// (该文件已 gitignore),因此公开仓库里既没有值、也改不动它。 +var InternalSignature = "" diff --git a/web/src/admin/pages/AboutPage.vue b/web/src/admin/pages/AboutPage.vue index 5aed96f..e3ae3b1 100644 --- a/web/src/admin/pages/AboutPage.vue +++ b/web/src/admin/pages/AboutPage.vue @@ -72,6 +72,9 @@ const port = computed(() => { const origin = computed(() => window.location.origin) +// 内部署名由构建期注入,只有登录后(recorder+)才拿得到;未注入时为空串 +const internalSignature = ref('') + onMounted(async () => { // 直接从 /api/health 取版本;不走 adminApi.health 的示例数据兜底,避免把 mock 当成真实版本 try { @@ -83,6 +86,16 @@ onMounted(async () => { versionChecked.value = true } + try { + const res = await fetch('/api/system/about', { credentials: 'include' }) + if (res.ok) { + const body = (await res.json()) as { ok: boolean; data?: { internalSignature?: string } } + internalSignature.value = body.data?.internalSignature ?? '' + } + } catch { + // 未登录 / 接口不可达都按「没有署名」处理,不打扰用户 + } + if (!data.loaded) void data.load() }) @@ -99,6 +112,13 @@ onMounted(async () => { +
+
+ 内部署名 + {{ internalSignature }} +
+
+
基本信息