Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
887ff2f07c | ||
|
|
c85293fd1d |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lanhu-admin",
|
"name": "lanhu-admin",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -H 0.0.0.0 --turbopack",
|
"dev": "next dev -H 0.0.0.0 --turbopack",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server"
|
"use server"
|
||||||
import { cookies } from "next/headers"
|
import { cookies } from "next/headers"
|
||||||
import type { ApiResponse } from "@/lib/api"
|
import type { ApiResponse } from "@/lib/api"
|
||||||
import { callByDevice } from "./base"
|
import type { User } from "@/models/user"
|
||||||
|
import { callByDevice, callByUser } from "./base"
|
||||||
|
|
||||||
export type TokenResp = {
|
export type TokenResp = {
|
||||||
access_token: string
|
access_token: string
|
||||||
@@ -29,12 +30,12 @@ export async function login(params: {
|
|||||||
// 保存到 cookies
|
// 保存到 cookies
|
||||||
const data = resp.data
|
const data = resp.data
|
||||||
const cookieStore = await cookies()
|
const cookieStore = await cookies()
|
||||||
cookieStore.set("auth_token", data.access_token, {
|
cookieStore.set("admin/auth_token", data.access_token, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
maxAge: Math.max(data.expires_in, 0),
|
maxAge: Math.max(data.expires_in, 0),
|
||||||
})
|
})
|
||||||
cookieStore.set("auth_refresh", data.refresh_token, {
|
cookieStore.set("admin/auth_refresh", data.refresh_token, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
maxAge: Number.MAX_SAFE_INTEGER,
|
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<User>("/api/auth/introspect")
|
||||||
|
}
|
||||||
|
|
||||||
export async function refreshAuth() {
|
export async function refreshAuth() {
|
||||||
const cookie = await cookies()
|
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) {
|
if (!userRefresh) {
|
||||||
throw new Error("未授权访问")
|
throw new Error("未授权访问")
|
||||||
}
|
}
|
||||||
@@ -63,7 +101,7 @@ export async function refreshAuth() {
|
|||||||
// 处理请求
|
// 处理请求
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
if (resp.status === 401) {
|
if (resp.status === 401) {
|
||||||
cookie.delete("auth_refresh")
|
cookie.delete("admin/auth_refresh")
|
||||||
}
|
}
|
||||||
throw new Error("未授权访问")
|
throw new Error("未授权访问")
|
||||||
}
|
}
|
||||||
@@ -75,12 +113,12 @@ export async function refreshAuth() {
|
|||||||
const expiresIn = data.expires_in
|
const expiresIn = data.expires_in
|
||||||
|
|
||||||
// 保存令牌到 cookies
|
// 保存令牌到 cookies
|
||||||
cookie.set("auth_token", nextAccessToken, {
|
cookie.set("admin/auth_token", nextAccessToken, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
maxAge: Math.max(expiresIn, 0),
|
maxAge: Math.max(expiresIn, 0),
|
||||||
})
|
})
|
||||||
cookie.set("auth_refresh", nextRefreshToken, {
|
cookie.set("admin/auth_refresh", nextRefreshToken, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
maxAge: Number.MAX_SAFE_INTEGER,
|
maxAge: Number.MAX_SAFE_INTEGER,
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const _callByUser = cache(
|
|||||||
): Promise<ApiResponse<R>> => {
|
): Promise<ApiResponse<R>> => {
|
||||||
// 获取用户令牌
|
// 获取用户令牌
|
||||||
const cookie = await cookies()
|
const cookie = await cookies()
|
||||||
const token = cookie.get("auth_token")?.value
|
const token = cookie.get("admin/auth_token")?.value
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -12,18 +12,16 @@ import {
|
|||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname, useRouter } from "next/navigation"
|
||||||
import { useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react"
|
||||||
|
import { getProfile, logout } from "@/actions/auth"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
|
import type { User } from "@/models/user"
|
||||||
|
|
||||||
export default function Appbar() {
|
export default function Appbar() {
|
||||||
const [currentUser] = useState({
|
const [currentUser, setCurrentUser] = useState<User>()
|
||||||
name: "张三",
|
const router = useRouter()
|
||||||
avatar: "/avatar.png",
|
|
||||||
role: "管理员",
|
|
||||||
})
|
|
||||||
|
|
||||||
const [showDropdown, setShowDropdown] = useState(false)
|
const [showDropdown, setShowDropdown] = useState(false)
|
||||||
const [showNotifications, setShowNotifications] = useState(false)
|
const [showNotifications, setShowNotifications] = useState(false)
|
||||||
const [notifications] = useState([
|
const [notifications] = useState([
|
||||||
@@ -116,6 +114,35 @@ export default function Appbar() {
|
|||||||
|
|
||||||
const breadcrumbs = generateBreadcrumbs()
|
const breadcrumbs = generateBreadcrumbs()
|
||||||
const unreadCount = notifications.filter(n => !n.read).length
|
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 (
|
return (
|
||||||
<header className="bg-white h-16 border-b border-gray-200 flex items-center justify-between px-6">
|
<header className="bg-white h-16 border-b border-gray-200 flex items-center justify-between px-6">
|
||||||
@@ -232,23 +259,45 @@ export default function Appbar() {
|
|||||||
aria-label="用户菜单"
|
aria-label="用户菜单"
|
||||||
>
|
>
|
||||||
<div className="h-8 w-8 rounded-full bg-blue-100 text-blue-800 flex items-center justify-center overflow-hidden border-2 border-white shadow-sm">
|
<div className="h-8 w-8 rounded-full bg-blue-100 text-blue-800 flex items-center justify-center overflow-hidden border-2 border-white shadow-sm">
|
||||||
<Image
|
{currentUser ? (
|
||||||
src={currentUser.avatar}
|
currentUser.avatar ? (
|
||||||
alt="用户头像"
|
<Image
|
||||||
width={32}
|
src={currentUser.avatar}
|
||||||
height={32}
|
alt="用户头像"
|
||||||
onError={e => {
|
width={32}
|
||||||
const target = e.target as HTMLImageElement
|
height={32}
|
||||||
target.style.display = "none"
|
className="h-full w-full object-cover"
|
||||||
target.parentElement!.innerHTML = currentUser.name.charAt(0)
|
onError={e => {
|
||||||
}}
|
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()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
// 如果没有头像,直接显示用户名首字母
|
||||||
|
<span className="text-sm font-semibold">
|
||||||
|
{currentUser.name.charAt(0).toUpperCase()}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
// 加载状态或用户信息为空时
|
||||||
|
<UserIcon size={18} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden md:block text-left">
|
<div className="hidden md:block text-left">
|
||||||
<p className="text-sm font-medium text-gray-800">
|
{currentUser && (
|
||||||
{currentUser.name}
|
<div>
|
||||||
</p>
|
<p className="text-sm font-medium text-gray-800">
|
||||||
<p className="text-xs text-gray-500">{currentUser.role}</p>
|
{currentUser.name}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-gray-500">{currentUser.username}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ChevronDownIcon />
|
<ChevronDownIcon />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -256,10 +305,15 @@ export default function Appbar() {
|
|||||||
{/* 用户下拉内容 */}
|
{/* 用户下拉内容 */}
|
||||||
{showDropdown && (
|
{showDropdown && (
|
||||||
<div className="absolute right-0 mt-2 w-56 bg-white rounded-lg shadow-lg py-2 z-20 border border-gray-200">
|
<div className="absolute right-0 mt-2 w-56 bg-white rounded-lg shadow-lg py-2 z-20 border border-gray-200">
|
||||||
<div className="px-4 py-2 border-b border-gray-100 md:hidden">
|
{currentUser && (
|
||||||
<p className="font-medium text-gray-800">{currentUser.name}</p>
|
<div className="px-4 py-2 border-b border-gray-100 md:hidden">
|
||||||
<p className="text-xs text-gray-500">{currentUser.role}</p>
|
<p className="font-medium text-gray-800">
|
||||||
</div>
|
{currentUser.name}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-xs text-gray-500">{currentUser.name}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
<Link
|
<Link
|
||||||
@@ -284,15 +338,15 @@ export default function Appbar() {
|
|||||||
<span className="pl-3">帮助中心</span>
|
<span className="pl-3">帮助中心</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="border-t border-gray-100 pt-1">
|
||||||
<div className="border-t border-gray-100 mt-1">
|
<Button
|
||||||
<Link
|
variant="ghost"
|
||||||
href="/login"
|
onClick={doLogout}
|
||||||
className="flex items-center px-4 py-2 text-sm text-red-600 hover:bg-gray-100"
|
className="flex items-center justify-start px-4 py-2 w-full text-sm text-red-600 hover:text-red-700 hover:bg-gray-100 font-normal"
|
||||||
>
|
>
|
||||||
<LogOutIcon size={18} />
|
<LogOutIcon size={18} className="ml-2" />
|
||||||
<span className="pl-3">退出登录</span>
|
退出登录
|
||||||
</Link>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export default function BatchPage() {
|
|||||||
const table = useDataTable<Batch>((page, size) =>
|
const table = useDataTable<Batch>((page, size) =>
|
||||||
getPageBatch({ page, size }),
|
getPageBatch({ page, size }),
|
||||||
)
|
)
|
||||||
console.log(table, "table")
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export default function BillingPage() {
|
|||||||
const table = useDataTable<Billing>((page, size) =>
|
const table = useDataTable<Billing>((page, size) =>
|
||||||
getPageBill({ page, size }),
|
getPageBill({ page, size }),
|
||||||
)
|
)
|
||||||
console.log(table, "table")
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
@@ -24,7 +23,6 @@ export default function BillingPage() {
|
|||||||
accessorKey: "info",
|
accessorKey: "info",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const bill = row.original
|
const bill = row.original
|
||||||
console.log(bill, "bill")
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ export default function ChannelPage() {
|
|||||||
header: "认证方式",
|
header: "认证方式",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const channel = row.original
|
const channel = row.original
|
||||||
console.log(channel, "channel")
|
|
||||||
|
|
||||||
const hasWhitelist =
|
const hasWhitelist =
|
||||||
channel.whitelists && channel.whitelists.trim() !== ""
|
channel.whitelists && channel.whitelists.trim() !== ""
|
||||||
|
|||||||
5
src/app/(root)/security/page.tsx
Normal file
5
src/app/(root)/security/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
export default function SecurityPage() {
|
||||||
|
return <div>管理员页面待完善~</div>
|
||||||
|
}
|
||||||
5
src/app/(root)/statistics/page.tsx
Normal file
5
src/app/(root)/statistics/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
export default function StatisticsPage() {
|
||||||
|
return <div>数据统计页面待完善~</div>
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ export default function TradePage() {
|
|||||||
const table = useDataTable<Trade>((page, size) =>
|
const table = useDataTable<Trade>((page, size) =>
|
||||||
getPageTrade({ page, size }),
|
getPageTrade({ page, size }),
|
||||||
)
|
)
|
||||||
console.log(table, "table")
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { User } from "@/models/user"
|
|||||||
|
|
||||||
export default function UserPage() {
|
export default function UserPage() {
|
||||||
const table = useDataTable<User>((page, size) => getPageUsers({ page, size }))
|
const table = useDataTable<User>((page, size) => getPageUsers({ page, size }))
|
||||||
const bind = useFetch((id: number) => bindAdmin({ id }), {
|
const bind = useFetch(table, (id: number) => bindAdmin({ id }), {
|
||||||
done: "用户已认领",
|
done: "用户已认领",
|
||||||
fail: "用户认领失败",
|
fail: "用户认领失败",
|
||||||
})
|
})
|
||||||
@@ -73,7 +73,10 @@ export default function UserPage() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ header: "联系方式", accessorKey: "contact_wechat" },
|
{ header: "联系方式", accessorKey: "contact_wechat" },
|
||||||
{ header: "管理员", accessorKey: "admin_id" },
|
{
|
||||||
|
header: "管理员",
|
||||||
|
cell: ({ row }) => row.original.admin?.name,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: "最后登录时间",
|
header: "最后登录时间",
|
||||||
accessorKey: "last_login",
|
accessorKey: "last_login",
|
||||||
@@ -92,9 +95,9 @@ export default function UserPage() {
|
|||||||
<Button
|
<Button
|
||||||
size={"sm"}
|
size={"sm"}
|
||||||
onClick={() => bind(ctx.row.original.id)}
|
onClick={() => bind(ctx.row.original.id)}
|
||||||
disabled={ctx.row.original.admin_id !== null}
|
disabled={!!ctx.row.original.admin_id}
|
||||||
>
|
>
|
||||||
{ctx.row.original.admin_id !== null ? "已认领" : "认领"}
|
{ctx.row.original.admin_id ? "已认领" : "认领"}
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ export function useDataTable<T>(
|
|||||||
}, [refresh, page, size])
|
}, [refresh, page, size])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status,
|
|
||||||
data,
|
data,
|
||||||
|
status,
|
||||||
|
setStatus,
|
||||||
pagination: {
|
pagination: {
|
||||||
page,
|
page,
|
||||||
size,
|
size,
|
||||||
@@ -60,5 +61,6 @@ export function useDataTable<T>(
|
|||||||
onPageChange,
|
onPageChange,
|
||||||
onSizeChange,
|
onSizeChange,
|
||||||
},
|
},
|
||||||
|
refresh,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from "react"
|
} from "react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
import type { useDataTable } from "@/components/data-table"
|
||||||
import type { ApiResponse } from "@/lib/api"
|
import type { ApiResponse } from "@/lib/api"
|
||||||
|
|
||||||
export function useStatus() {
|
export function useStatus() {
|
||||||
@@ -12,31 +13,39 @@ export function useStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useFetch<TArgs extends unknown[], TResult>(
|
export function useFetch<TArgs extends unknown[], TResult>(
|
||||||
|
table: ReturnType<typeof useDataTable>,
|
||||||
fetchData: (...args: TArgs) => Promise<ApiResponse<TResult>>,
|
fetchData: (...args: TArgs) => Promise<ApiResponse<TResult>>,
|
||||||
messages: {
|
messages: {
|
||||||
done?: string
|
done?: string
|
||||||
fail?: string
|
fail?: string
|
||||||
},
|
},
|
||||||
setStatus?: Dispatch<SetStateAction<"load" | "fail" | "done">>,
|
|
||||||
) {
|
) {
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async (...args: TArgs) => {
|
async (...args: TArgs) => {
|
||||||
try {
|
try {
|
||||||
setStatus?.("load")
|
table.setStatus?.("load")
|
||||||
const resp = await fetchData(...args)
|
const resp = await fetchData(...args)
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
throw new Error(resp.message)
|
throw new Error(resp.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus?.("done")
|
table.setStatus?.("done")
|
||||||
|
table.refresh(table.pagination.page, table.pagination.size)
|
||||||
toast.success(messages.done || "获取数据成功")
|
toast.success(messages.done || "获取数据成功")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setStatus?.("fail")
|
table.setStatus?.("fail")
|
||||||
toast.error(messages.fail || "获取数据失败", {
|
toast.error(messages.fail || "获取数据失败", {
|
||||||
description: (e as Error).message || "未知错误",
|
description: (e as Error).message || "未知错误",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[fetchData, setStatus, messages],
|
[
|
||||||
|
fetchData,
|
||||||
|
table.setStatus,
|
||||||
|
table.pagination.page,
|
||||||
|
table.pagination.size,
|
||||||
|
table.refresh,
|
||||||
|
messages,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export type User = {
|
export type User = {
|
||||||
id: number
|
id: number
|
||||||
admin_id: number
|
admin_id?: number
|
||||||
|
admin?: Admin
|
||||||
phone: string
|
phone: string
|
||||||
has_password: boolean
|
has_password: boolean
|
||||||
username: string
|
username: string
|
||||||
@@ -20,3 +21,7 @@ export type User = {
|
|||||||
created_at: Date
|
created_at: Date
|
||||||
updated_at: Date
|
updated_at: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Admin = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|||||||
20
src/proxy.ts
20
src/proxy.ts
@@ -19,22 +19,28 @@ export async function proxy(request: NextRequest) {
|
|||||||
|
|
||||||
// 刷新访问令牌
|
// 刷新访问令牌
|
||||||
try {
|
try {
|
||||||
const accessToken = request.cookies.get("auth_token")
|
const accessToken = request.cookies.get("admin/auth_token")
|
||||||
const refreshToken = request.cookies.get("auth_refresh")
|
const refreshToken = request.cookies.get("admin/auth_refresh")
|
||||||
if (!accessToken && !!refreshToken) {
|
if (!accessToken && !!refreshToken) {
|
||||||
console.log("💡 refresh token")
|
console.log("💡 refresh token")
|
||||||
const token = await refreshAuth()
|
const token = await refreshAuth()
|
||||||
request.cookies.set("auth_token", token.access_token)
|
request.cookies.set("admin/auth_token", token.access_token)
|
||||||
request.cookies.set("auth_refresh", token.refresh_token)
|
request.cookies.set("admin/auth_refresh", token.refresh_token)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("刷新访问令牌失败", request.url, (e as Error).message)
|
console.log("刷新访问令牌失败", request.url, (e as Error).message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证访问令牌
|
// 验证访问令牌
|
||||||
const hasToken = !!request.cookies.get("auth_token")
|
const hasToken = !!request.cookies.get("admin/auth_token")
|
||||||
const isToAdmin = request.nextUrl.pathname.startsWith("/admin")
|
// const isToAdmin = request.nextUrl.pathname.startsWith("/admin")
|
||||||
if (!hasToken && isToAdmin) {
|
const protectedPaths = ["/", "/admin"]
|
||||||
|
const isProtectedPath = protectedPaths.some(
|
||||||
|
path =>
|
||||||
|
request.nextUrl.pathname === path ||
|
||||||
|
request.nextUrl.pathname.startsWith(`${path}/`),
|
||||||
|
)
|
||||||
|
if (!hasToken && isProtectedPath) {
|
||||||
return NextResponse.redirect(
|
return NextResponse.redirect(
|
||||||
`${request.nextUrl.origin}/login?redirect=${request.nextUrl.pathname}`,
|
`${request.nextUrl.origin}/login?redirect=${request.nextUrl.pathname}`,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user