diff --git a/package.json b/package.json index 636e872..cdbf4c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lanhu-admin", - "version": "1.0.0", + "version": "1.0.1", "private": true, "scripts": { "dev": "next dev -H 0.0.0.0 --turbopack", diff --git a/src/actions/auth.ts b/src/actions/auth.ts index 36d6e13..41919a3 100644 --- a/src/actions/auth.ts +++ b/src/actions/auth.ts @@ -1,7 +1,8 @@ "use server" import { cookies } from "next/headers" import type { ApiResponse } from "@/lib/api" -import { callByDevice } from "./base" +import type { User } from "@/models/user" +import { callByDevice, callByUser } from "./base" export type TokenResp = { access_token: string @@ -29,12 +30,12 @@ export async function login(params: { // 保存到 cookies const data = resp.data const cookieStore = await cookies() - cookieStore.set("auth_token", data.access_token, { + cookieStore.set("admin/auth_token", data.access_token, { httpOnly: true, sameSite: "strict", maxAge: Math.max(data.expires_in, 0), }) - cookieStore.set("auth_refresh", data.refresh_token, { + cookieStore.set("admin/auth_refresh", data.refresh_token, { httpOnly: true, sameSite: "strict", maxAge: Number.MAX_SAFE_INTEGER, @@ -46,10 +47,47 @@ export async function login(params: { } } +export async function logout() { + const cookieStore = await cookies() + + // 尝试删除后台会话 + const access_token = cookieStore.get("admin/auth_token")?.value + const refresh_token = cookieStore.get("admin/auth_refresh")?.value + if (access_token && refresh_token) { + await callByUser("/api/auth/revoke", { + access_token, + refresh_token, + }) + } + + // 删除 cookies + cookieStore.set("admin/auth_token", "", { + httpOnly: true, + sameSite: "strict", + maxAge: -1, + }) + cookieStore.set("admin/auth_refresh", "", { + httpOnly: true, + sameSite: "strict", + maxAge: -1, + }) + + return { + success: true, + data: undefined, + } +} + +export async function getProfile() { + return await callByUser("/api/auth/introspect") +} + export async function refreshAuth() { const cookie = await cookies() - const userRefresh = cookie.get("auth_refresh")?.value + const userRefresh = cookie.get("admin/auth_refresh")?.value + console.log(userRefresh, "userRefresh") + if (!userRefresh) { throw new Error("未授权访问") } @@ -63,7 +101,7 @@ export async function refreshAuth() { // 处理请求 if (!resp.success) { if (resp.status === 401) { - cookie.delete("auth_refresh") + cookie.delete("admin/auth_refresh") } throw new Error("未授权访问") } @@ -75,12 +113,12 @@ export async function refreshAuth() { const expiresIn = data.expires_in // 保存令牌到 cookies - cookie.set("auth_token", nextAccessToken, { + cookie.set("admin/auth_token", nextAccessToken, { httpOnly: true, sameSite: "strict", maxAge: Math.max(expiresIn, 0), }) - cookie.set("auth_refresh", nextRefreshToken, { + cookie.set("admin/auth_refresh", nextRefreshToken, { httpOnly: true, sameSite: "strict", maxAge: Number.MAX_SAFE_INTEGER, diff --git a/src/actions/base.ts b/src/actions/base.ts index b4264bc..1de21ad 100644 --- a/src/actions/base.ts +++ b/src/actions/base.ts @@ -80,7 +80,7 @@ const _callByUser = cache( ): Promise> => { // 获取用户令牌 const cookie = await cookies() - const token = cookie.get("auth_token")?.value + const token = cookie.get("admin/auth_token")?.value if (!token) { return { success: false, diff --git a/src/app/(root)/appbar.tsx b/src/app/(root)/appbar.tsx index 7e8e328..bcff5b2 100644 --- a/src/app/(root)/appbar.tsx +++ b/src/app/(root)/appbar.tsx @@ -12,18 +12,16 @@ import { } from "lucide-react" import Image from "next/image" import Link from "next/link" -import { usePathname } from "next/navigation" +import { usePathname, useRouter } from "next/navigation" import { useEffect, useRef, useState } from "react" +import { getProfile, logout } from "@/actions/auth" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" +import type { User } from "@/models/user" export default function Appbar() { - const [currentUser] = useState({ - name: "张三", - avatar: "/avatar.png", - role: "管理员", - }) - + const [currentUser, setCurrentUser] = useState() + const router = useRouter() const [showDropdown, setShowDropdown] = useState(false) const [showNotifications, setShowNotifications] = useState(false) const [notifications] = useState([ @@ -116,6 +114,35 @@ export default function Appbar() { const breadcrumbs = generateBreadcrumbs() const unreadCount = notifications.filter(n => !n.read).length + const doLogout = async () => { + const resp = await logout() + if (resp.success) { + router.replace("/") + router.refresh() + } + } + + useEffect(() => { + async function fetchUserProfile() { + try { + const resp = await getProfile() + console.log(resp, "resp") + + if (resp.success) { + setCurrentUser(resp.data) + } else { + console.error("获取用户信息失败:", resp.message) + if (resp.status === 401) { + router.replace("/login") + } + } + } catch (error) { + console.error("获取用户信息时出错:", error) + } + } + + fetchUserProfile() + }, [router]) return (
@@ -232,23 +259,45 @@ export default function Appbar() { aria-label="用户菜单" >
- 用户头像 { - const target = e.target as HTMLImageElement - target.style.display = "none" - target.parentElement!.innerHTML = currentUser.name.charAt(0) - }} - /> + {currentUser ? ( + currentUser.avatar ? ( + 用户头像 { + const target = e.target as HTMLImageElement + target.style.display = "none" + const parent = target.parentElement + if (parent && currentUser?.name) { + parent.textContent = currentUser.name + .charAt(0) + .toUpperCase() + } + }} + /> + ) : ( + // 如果没有头像,直接显示用户名首字母 + + {currentUser.name.charAt(0).toUpperCase()} + + ) + ) : ( + // 加载状态或用户信息为空时 + + )}
-

- {currentUser.name} -

-

{currentUser.role}

+ {currentUser && ( +
+

+ {currentUser.name} +

+

{currentUser.username}

+
+ )}
@@ -256,10 +305,15 @@ export default function Appbar() { {/* 用户下拉内容 */} {showDropdown && (
-
-

{currentUser.name}

-

{currentUser.role}

-
+ {currentUser && ( +
+

+ {currentUser.name} +

+ +

{currentUser.name}

+
+ )}
帮助中心
- -
- +
)} diff --git a/src/app/(root)/batch/page.tsx b/src/app/(root)/batch/page.tsx index 8e12496..67a51f2 100644 --- a/src/app/(root)/batch/page.tsx +++ b/src/app/(root)/batch/page.tsx @@ -9,7 +9,6 @@ export default function BatchPage() { const table = useDataTable((page, size) => getPageBatch({ page, size }), ) - console.log(table, "table") return ( Loading...}> diff --git a/src/app/(root)/billing/page.tsx b/src/app/(root)/billing/page.tsx index da79d3b..2ed1431 100644 --- a/src/app/(root)/billing/page.tsx +++ b/src/app/(root)/billing/page.tsx @@ -10,7 +10,6 @@ export default function BillingPage() { const table = useDataTable((page, size) => getPageBill({ page, size }), ) - console.log(table, "table") return ( @@ -24,7 +23,6 @@ export default function BillingPage() { accessorKey: "info", cell: ({ row }) => { const bill = row.original - console.log(bill, "bill") return (
diff --git a/src/app/(root)/channel/page.tsx b/src/app/(root)/channel/page.tsx index 986222c..99ea68f 100644 --- a/src/app/(root)/channel/page.tsx +++ b/src/app/(root)/channel/page.tsx @@ -49,7 +49,6 @@ export default function ChannelPage() { header: "认证方式", cell: ({ row }) => { const channel = row.original - console.log(channel, "channel") const hasWhitelist = channel.whitelists && channel.whitelists.trim() !== "" diff --git a/src/app/(root)/security/page.tsx b/src/app/(root)/security/page.tsx new file mode 100644 index 0000000..f42bf81 --- /dev/null +++ b/src/app/(root)/security/page.tsx @@ -0,0 +1,5 @@ +"use client" + +export default function SecurityPage() { + return
管理员页面待完善~
+} diff --git a/src/app/(root)/statistics/page.tsx b/src/app/(root)/statistics/page.tsx new file mode 100644 index 0000000..ca4cac9 --- /dev/null +++ b/src/app/(root)/statistics/page.tsx @@ -0,0 +1,5 @@ +"use client" + +export default function StatisticsPage() { + return
数据统计页面待完善~
+} diff --git a/src/app/(root)/trade/page.tsx b/src/app/(root)/trade/page.tsx index 84e0e82..c8577c3 100644 --- a/src/app/(root)/trade/page.tsx +++ b/src/app/(root)/trade/page.tsx @@ -10,7 +10,6 @@ export default function TradePage() { const table = useDataTable((page, size) => getPageTrade({ page, size }), ) - console.log(table, "table") return ( diff --git a/src/proxy.ts b/src/proxy.ts index 92b83bb..9f5e62b 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -19,22 +19,28 @@ export async function proxy(request: NextRequest) { // 刷新访问令牌 try { - const accessToken = request.cookies.get("auth_token") - const refreshToken = request.cookies.get("auth_refresh") + const accessToken = request.cookies.get("admin/auth_token") + const refreshToken = request.cookies.get("admin/auth_refresh") if (!accessToken && !!refreshToken) { console.log("💡 refresh token") const token = await refreshAuth() - request.cookies.set("auth_token", token.access_token) - request.cookies.set("auth_refresh", token.refresh_token) + request.cookies.set("admin/auth_token", token.access_token) + request.cookies.set("admin/auth_refresh", token.refresh_token) } } catch (e) { console.log("刷新访问令牌失败", request.url, (e as Error).message) } // 验证访问令牌 - const hasToken = !!request.cookies.get("auth_token") - const isToAdmin = request.nextUrl.pathname.startsWith("/admin") - if (!hasToken && isToAdmin) { + const hasToken = !!request.cookies.get("admin/auth_token") + // const isToAdmin = request.nextUrl.pathname.startsWith("/admin") + const protectedPaths = ["/", "/admin"] + const isProtectedPath = protectedPaths.some( + path => + request.nextUrl.pathname === path || + request.nextUrl.pathname.startsWith(`${path}/`), + ) + if (!hasToken && isProtectedPath) { return NextResponse.redirect( `${request.nextUrl.origin}/login?redirect=${request.nextUrl.pathname}`, )