diff --git a/Client/src/App.tsx b/Client/src/App.tsx
index ce9a42f..781f676 100644
--- a/Client/src/App.tsx
+++ b/Client/src/App.tsx
@@ -4,49 +4,51 @@ import { LoginPage } from './components/LoginPage'
import { BlogPage } from './components/BlogPage'
import { StartPage } from './components/StartPage'
import { useAuth } from './hooks/useAuth'
+import { checkGate } from './api/gate'
function App() {
// 登录态从 localStorage/sessionStorage 恢复:已登录直接进博客页。
const auth = useAuth()
- // 未登录默认进导航页;点齿轮切到登录页。
+ // 是否显示登录页。入口 hash 是否合法由后端判定,故初值为 false,等校验通过再开。
const [showLogin, setShowLogin] = useState(false)
const signOut = auth.signOut
- // 进登录页:往浏览器历史压一条记录,便于返回键退回。
- const openLogin = () => {
- window.history.pushState({ page: 'login' }, '')
- setShowLogin(true)
- }
-
- // “返回等于登出”:退回导航页时清掉登录态。
- // 浏览器返回键 / 登录页返回 / 博客页登出 都汇到这里。
+ // 监听地址栏 hash:把 `#` 后那串发给后端校验,命中合法开发者才放行到登录页。
+ // 合法 hash 列表只存在后端,前端不写死,扒 JS 也看不到合法值。
useEffect(() => {
- const onPop = () => {
- signOut()
- setShowLogin(false)
+ let alive = true
+ const check = async () => {
+ const hash = window.location.hash.replace(/^#/, '')
+ const ok = hash ? await checkGate(hash) : false
+ // 防竞态:异步返回时若组件已卸载或 hash 又变了,丢弃这次结果。
+ if (alive && hash === window.location.hash.replace(/^#/, '')) {
+ setShowLogin(ok)
+ }
}
- window.addEventListener('popstate', onPop)
- return () => window.removeEventListener('popstate', onPop)
- }, [signOut])
+ check()
+ window.addEventListener('hashchange', check)
+ return () => {
+ alive = false
+ window.removeEventListener('hashchange', check)
+ }
+ }, [])
- // 博客页登出:若是从登录流程进来的(历史里有 login 记录),走 history.back()
- // 让浏览器历史保持干净;否则(如记住登录直接进博客)直接回首页。
- const signOutFromBlog = () => {
- if (window.history.state?.page === 'login') {
- window.history.back()
- } else {
- signOut()
- setShowLogin(false)
+ // 回到导航页:清掉登录态,并抹掉地址栏里的密钥(防止留在历史里被看到)。
+ const goStart = () => {
+ signOut()
+ setShowLogin(false)
+ if (window.location.hash) {
+ history.replaceState(null, '', window.location.pathname + window.location.search)
}
}
let view
if (auth.username) {
- view =
+ view =
} else if (showLogin) {
- view = window.history.back()} />
+ view =
} else {
- view =
+ view =
}
return (
diff --git a/Client/src/api/gate.ts b/Client/src/api/gate.ts
new file mode 100644
index 0000000..69fefbf
--- /dev/null
+++ b/Client/src/api/gate.ts
@@ -0,0 +1,24 @@
+// 入口门校验。把 URL `#` 后那串 hash 发给后端,问“是不是合法开发者的入口”。
+// 后端基址默认本机 8080,可用 VITE_API_BASE 覆盖(与 auth/useSystemStats 一致)。
+const API_BASE =
+ (import.meta.env.VITE_API_BASE as string | undefined) ?? 'http://localhost:8080'
+
+/**
+ * 校验入口 hash。命中合法开发者返回 true,否则 false。
+ * 后端不可达 / 非 200 一律按“不放行”处理(返回 false),不暴露登录页。
+ */
+export async function checkGate(hash: string): Promise {
+ if (!hash) return false
+ try {
+ const res = await fetch(`${API_BASE}/api/web/gate`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ hash }),
+ })
+ if (!res.ok) return false
+ const data = (await res.json()) as { ok?: boolean }
+ return data.ok === true
+ } catch {
+ return false
+ }
+}
diff --git a/Client/src/components/StartPage.tsx b/Client/src/components/StartPage.tsx
index c4142e6..509f141 100644
--- a/Client/src/components/StartPage.tsx
+++ b/Client/src/components/StartPage.tsx
@@ -5,7 +5,6 @@ import { useWeather } from '../hooks/useWeather'
import { Greeting } from './start/Greeting'
import { SearchBar } from './start/SearchBar'
import { QuickLinks } from './start/QuickLinks'
-import { SettingsGear } from './start/SettingsGear'
import { WeatherFx } from './start/WeatherFx'
// 背景图:放 src/assets/startpage/ 下。按昼夜选 light / dark;
@@ -53,12 +52,7 @@ function isThunder(icon?: string): boolean {
return icon ? ['302', '303', '304'].includes(icon) : false
}
-interface StartPageProps {
- /** 点右下齿轮:切到登录页 */
- onOpenSettings: () => void
-}
-
-export function StartPage({ onOpenSettings }: StartPageProps) {
+export function StartPage() {
const wx = useWeather()
// 每分钟一拍,让昼夜随时间自动翻(即便天气没刷新)。
@@ -132,8 +126,6 @@ export function StartPage({ onOpenSettings }: StartPageProps) {
-
-
)
}
diff --git a/Client/src/components/start/SettingsGear.tsx b/Client/src/components/start/SettingsGear.tsx
deleted file mode 100644
index b26c568..0000000
--- a/Client/src/components/start/SettingsGear.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { motion } from 'framer-motion'
-
-interface SettingsGearProps {
- onClick: () => void
-}
-
-// 右下角齿轮:悬停放大并旋转,点击跳转登录页。
-export function SettingsGear({ onClick }: SettingsGearProps) {
- return (
-
-
-
- )
-}
diff --git a/Server/.gitignore b/Server/.gitignore
index ea8c4bf..8069243 100644
--- a/Server/.gitignore
+++ b/Server/.gitignore
@@ -1 +1,4 @@
/target
+
+# 开发者入口 hash 列表(秘密,单独部署,勿提交)
+/dev_gate.txt