由于import { cookies } from 'next/headers' 的在client端无法设置cookie,所有需要安装js-cookie在client端设置cookie,目的是在中间件中取到cookie里的token
1.登录时存储token
我把登录逻辑放在zustand了,路径也是vue写习惯了,照着vue来的。
1.1 cookie方法封装
// D:\study\next-js-learning\src\utils\cookie.ts
import Cookies from "js-cookie";
export async function createCookie({
name,
value,
}: {
name: string;
value: string;
}) {
Cookies.set(
name,
value,
// path: "/",
);
}
export async function deleteCookie(name: string) {
return Cookies.remove(name);
}
export async function getCookie(name: string) {
return Cookies.get(name);
}
1.2 localstorage方法封装
// D:\study\next-js-learning\src\utils\storage.ts
'use client'
// localStorage封装
const localStorage = typeof window !== `undefined` ? window.localStorage : null
const localStorageUtil = {
/**
* 设置 localStorage
* @param key
* @param value
*/
set(key: string, value: any) {
localStorage?.setItem(key, JSON.stringify(value));
},
/**
* 获取 localStorage
* @param key
*/
get(key: string) {
const value = localStorage?.getItem(key);
if (value) {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}
return null;
},
/**
* 删除 localStorage
* @param key
*/
remove(key: string) {
localStorage?.removeItem(key);
},
/**
* 清空 localStorage
*/
clear() {
localStorage?.clear();
},
};
export default localStorageUtil;
1.3 登录逻辑封装
// D:\study\next-js-learning\src\store\modules\user.ts
"use client";
import { create } from 'zustand'
import type { userInfo as IUserInfo } from "@/api/user/type";
import { userInfo } from "@/api/user";
import { login } from "@/api/user/login";
import storage from "@/utils/storage";
import { IUserLogin } from "@/api/user/type";
import { createCookie, deleteCookie } from "@/utils/cookie";
export const useUserStore = create<{
userInfo: IUserInfo;
token: string | null;
setToken: (token: string) => void;
setUser: (userInfo: IUserInfo) => void;
logout: () => void;
getUserInfo: () => void;
userLogin: (data: IUserLogin) => void;
isDark: boolean;
setTheme: (isDark: boolean) => void;
}>()((set, get) => ({
userInfo: storage.get("userInfo") || ({} as IUserInfo),
token: "",
isDark: storage.get("isDark") || false,
setToken: (token) => {
set({ token });
storage.set("token", token);
createCookie({ name: "token", value: token });
},
setUser: (userInfo: IUserInfo) => set({ userInfo }),
logout: () => {
deleteCookie("token");
storage.remove("token");
storage.remove("userInfo");
set({ userInfo: {} as IUserInfo, token: "" });
location.href = "/login";
},
userLogin: async (values: IUserLogin) => {
// 设置主题
const isDark = storage.get("isDark");
if (isDark) {
get().setTheme(isDark);
}
try {
deleteCookie("token");
storage.remove("token");
storage.remove("userInfo");
set({ userInfo: {} as IUserInfo, token: "" });
const res = await login(values);
console.log("userLogin try😊===》");
get().setToken(res.data);
await get().getUserInfo();
return res;
} catch (error) {
console.log("userLogin catch😊===》", error);
throw error;
}
},
getUserInfo: async () => {
console.log("getUserInfo😊===》");
const res = await userInfo();
storage.set("userInfo", res.data);
const { username, id } = res.data!;
createCookie({ name: "userInfo", value: JSON.stringify({ username, id }) });
set({ userInfo: res.data });
return res;
},
setTheme: (isDark) => {
set({ isDark });
storage.set("isDark", isDark);
},
}));
2.中间件
登录逻辑之中,把token存在了cookie里面,在中间件这里就能拿到了。
这里的处理逻辑就类似vue项目中的permission.ts了。
没token就跳到登录,有token就允许跳转。
访问白名单地址就放行,访问个别需要权限的页面再判断权限
权限在登录时可以存到cookie,也可以在middleware.ts中,通过fetch请求接口获取。
这里没法用axios,只能用fetch
// D:\study\next-js-learning\src\middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// import { userInfo as userInfoAPI } from "@/api/user";
// This function can be marked `async` if using `await` inside
// const test = (token) => {
// return new Promise((resolve, reject) => {
// fetch('http://127.0.0.1:3000/api/v1/user/profile',{
// headers: {
// 'Authorization': `Bearer ${token}`
// }
// }).then((res) => {
// res.json().then((data) => {
// console.log('--🚀🚀🚀🚀🚀------middleware.ts---注释所在行数11----😊===》', data)
// resolve(data);
// })
// }).catch((err) => {
// console.log('--🚀🚀🚀🚀🚀------middleware.ts---注释所在行数12----😊===》', err)
// reject(err);
// });
// });
// }
export async function middleware(request: NextRequest) {
/**
* 中间件-权限控制
* 0 如果跳转路径是白名单,继续执行。
* 1. 获取cookie中的token,如果没有token,跳转到登录页
* 2. 如果有token,获取用户权限,如果没有权限,跳转到403页面。
* 3. 如果有权限,继续执行。
* 4. 如果是登录页,跳转到首页。
*/
const { pathname } = request.nextUrl;
const whiteList = ["/login", "/403", "/404", "/500"];
const isWhite = whiteList.includes(pathname);
if (isWhite) {
return NextResponse.next();
} else {
const token = request.cookies.get("token" as any)?.value;
// const userInfo = request.cookies.get("userInfo" as any)?.value;
// console.log('--🚀🚀🚀🚀🚀------middleware.ts---注释所在行数37----😊===》', token)
// if (userInfo) {
// console.log('--🚀🚀🚀🚀🚀--middleware.ts---注释所在行数25----😊===》', JSON.parse(userInfo as any));
// }
// if(true){
// await test(token);
// }
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
} else {
return NextResponse.next();
}
}
}
// See "Matching Paths" below to learn more
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};