"use client";
import { useState, useEffect } from "react";
// 引入 authStore 和 AuthState,用于管理认证状态
import { authStore, AuthState } from "@/lib/authStore";
// 定义 useAuth 自定义钩子,返回认证状态和方法
export function useAuth() {
const [state, setState] = useState<AuthState>(authStore.getState());
// 使用 useEffect 订阅 authStore 的状态变化
useEffect(() => {
const unsubscribe = authStore.subscribe(setState);
return unsubscribe;
}, []);
// 返回认证状态和方法
return {
...state,
login: authStore.login.bind(authStore), //authStore.login 方法,绑定到 authStore 上下文,用于处理登录。
logout: authStore.logout.bind(authStore), //authStore.logout 方法,绑定到 authStore 上下文,用于处理退出登录。
getAuthHeader: authStore.getAuthHeader.bind(authStore), //authStore.getAuthHeader 方法,绑定到 authStore 上下文,可能用于生成认证请求头(如 Authorization: Bearer token)。
};
}
// 文件位置:src/lib/api.ts
// 管理认证 token(从 localStorage 获取,设置 Authorization 头)。
// 定义通用 fetcher 函数(getFetcher、postFetcher、getMessageUpdateFetcher),处理 GET/POST 请求、错误和 JSON 解析。
// 封装具体 API 接口(api 对象),如获取/创建/删除房间、发送/获取消息,支持认证和类型安全。
// 引入 API 响应类型,用于定义各种接口返回数据的结构
import {
RoomListRes, //(房间列表响应)
RoomMessageListRes, //(房间消息列表响应)
RoomAddRes, //(创建房间响应)
RoomMessageGetUpdateRes, //(消息更新响应)
ApiResponse, //(通用 API 响应类型)
} from "@/types/api";
// 认证状态管理 - 简单版本,避免循环依赖
// authToken 像一个“临时身份证”,用来证明用户已登录
let authToken: string | null = null;
// 设置认证 token
// 定义了一个导出函数 setAuthToken,接收一个 token 参数(字符串或 null),将它赋值给全局的 authToken。
export function setAuthToken(token: string | null) {
authToken = token;
}
// 获取认证头
function getAuthHeaders(): Record<string, string> {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
// 从 localStorage 获取 token(客户端)
if (typeof window !== "undefined") {
const token = localStorage.getItem("chatroom_token");
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
}
return headers;
}
// GET请求的fetcher函数
// 定义了一个导出的常量函数 getFetcher,用于发送 GET 请求(获取数据),接收 url 参数(字符串)。使用 fetch API 发送请求,传入 getAuthHeaders() 生成的头。
export const getFetcher = async (url: string) => {
const response = await fetch(url, {
headers: getAuthHeaders(),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result: ApiResponse = await response.json();
if (result.code !== 0) {
throw new Error(result.message || "请求失败");
}
return result.data;
};
// POST请求的fetcher函数
export const postFetcher = async (url: string, { arg }: { arg: any }) => {
const response = await fetch(url, {
method: "POST",
headers: getAuthHeaders(),
body: JSON.stringify(arg),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result: ApiResponse = await response.json();
if (result.code !== 0) {
throw new Error(result.message || "请求失败");
}
return result.data;
};
// 专门用于获取消息更新的fetcher
export const getMessageUpdateFetcher = async (url: string) => {
const response = await fetch(url, {
headers: getAuthHeaders(),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result: ApiResponse<RoomMessageGetUpdateRes> = await response.json();
if (result.code !== 0) {
throw new Error(result.message || "获取消息更新失败");
}
// 确保数据存在,否则抛出错误
if (!result.data) {
throw new Error("服务器返回的数据为空");
}
return result.data;
};
// API接口封装函数 - 更新为使用认证
export const api = {
// 获取房间列表
getRoomList: async (): Promise<RoomListRes> => {
return getFetcher("/api/room/list");
},
// 创建房间 - 不再需要传递 user 参数
createRoom: async (roomName: string): Promise<RoomAddRes> => {
return postFetcher("/api/room/add", { arg: { roomName } });
},
// 删除房间 - 不再需要传递 user 参数
deleteRoom: async (roomId: number): Promise<void> => {
return postFetcher("/api/room/delete", { arg: { roomId } });
},
// 获取房间消息列表
getRoomMessages: async (roomId: number): Promise<RoomMessageListRes> => {
return getFetcher(`/api/room/message/list?roomId=${roomId}`);
},
// 发送消息 - 不再需要传递 sender 参数
sendMessage: async (roomId: number, content: string): Promise<void> => {
return postFetcher("/api/message/add", { arg: { roomId, content } });
},
// 获取消息更新
getMessageUpdate: async (
roomId: number,
sinceMessageId: number
): Promise<RoomMessageGetUpdateRes> => {
return getMessageUpdateFetcher(
`/api/room/message/getUpdate?roomId=${roomId}&sinceMessageId=${sinceMessageId}`
);
},
};
// 用户认证系统,使用 JWT 进行身份验证,并使用 bcrypt 加密和验证密码。
// jsonwebtoken 是一个用于处理 JSON Web Token(JWT)的库
import jwt from "jsonwebtoken";
// bcryptjs 是一个用于加密和解密密码的库
import bcrypt from "bcryptjs";
// 这里导入了我们自己定义的类型 JWTPayload 和 AuthenticatedUser,用于描述用户的 JWT 载荷(payload)和已认证的用户信息。
import { JWTPayload, AuthenticatedUser } from "@/types/auth";
const JWT_SECRET =
process.env.JWT_SECRET || "your-secret-key-change-in-production";
const JWT_EXPIRES_IN = "7d";
// 生成 JWT token
export function generateToken(user: AuthenticatedUser): string {
return jwt.sign(
{
userId: user.userId,
username: user.username,
},
JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);
}
// 验证 JWT token
export function verifyToken(token: string): JWTPayload | null {
try {
return jwt.verify(token, JWT_SECRET) as JWTPayload;
} catch (error) {
return null;
}
}
// 密码加密
// 这是一个异步函数,用于加密用户的密码。它接受一个明文的 password 作为参数,并返回加密后的密码。
export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 12);
}
// 密码验证
// 这个函数用于验证用户输入的密码是否与存储在数据库中的加密密码(hashedPassword)相匹配。它返回一个布尔值(true 或 false)。
export async function verifyPassword(
password: string,
hashedPassword: string
): Promise<boolean> {
return bcrypt.compare(password, hashedPassword);
}
// 从请求头中提取用户信息
export function extractUserFromRequest(
request: Request
): AuthenticatedUser | null {
const authHeader = request.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return null;
}
const token = authHeader.substring(7);
const payload = verifyToken(token);
if (!payload) {
return null;
}
return {
userId: payload.userId,
username: payload.username,
};
}
// 这段代码实现了一个用于管理用户认证状态的 AuthStore 类,它通过 localStorage 持久化存储认证信息,并提供了登录、登出、状态订阅等功能
"use client";
import { AuthResponse } from "@/types/auth";
const TOKEN_KEY = "chatroom_token";
const USER_KEY = "chatroom_user";
// AuthState 是认证状态的接口定义
export interface AuthState {
isAuthenticated: boolean; //表示用户是否已认证。
user: {
userId: number;
username: string;
email?: string;
} | null;
token: string | null;
}
class AuthStore {
//state 保存了当前的认证状态,初始值表示用户未认证,user 和 token 都是 null。
private state: AuthState = {
isAuthenticated: false,
user: null,
token: null,
};
// listeners 是一个函数数组,每个函数都会在认证状态发生变化时被调用,用于通知状态变化
private listeners: ((state: AuthState) => void)[] = [];
constructor() {
// 从localStorage恢复状态
if (typeof window !== "undefined") {
this.loadFromStorage();
}
}
// 订阅状态变化
subscribe(listener: (state: AuthState) => void) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter((l) => l !== listener);
};
}
// 通知所有订阅者
private notify() {
this.listeners.forEach((listener) => listener(this.state));
}
// 获取当前状态
getState(): AuthState {
return { ...this.state };
}
// 登录
login(authResponse: AuthResponse) {
this.state = {
isAuthenticated: true,
user: authResponse.user,
token: authResponse.token,
};
this.saveToStorage();
this.notify();
}
// 登出
logout() {
this.state = {
isAuthenticated: false,
user: null,
token: null,
};
this.clearStorage();
this.notify();
}
// 获取认证头
getAuthHeader(): Record<string, string> {
if (!this.state.token) {
return {};
}
return {
Authorization: `Bearer ${this.state.token}`,
};
}
// 保存到本地存储
// saveToStorage 方法将当前的认证信息(token 和 user)保存到 localStorage 中。在保存之前,首先检查是否在浏览器环境中。
private saveToStorage() {
if (typeof window === "undefined") return;
localStorage.setItem(TOKEN_KEY, this.state.token || "");
localStorage.setItem(USER_KEY, JSON.stringify(this.state.user));
}
// 从本地存储加载
// loadFromStorage 方法从 localStorage 中获取保存的认证信息。如果存在有效的 token 和用户信息,它会将这些信息加载到 state 中。若加载过程中出错(例如数据损坏),则清除存储。
private loadFromStorage() {
if (typeof window === "undefined") return;
const token = localStorage.getItem(TOKEN_KEY);
const userStr = localStorage.getItem(USER_KEY);
if (token && userStr) {
try {
const user = JSON.parse(userStr);
this.state = {
isAuthenticated: true,
user,
token,
};
} catch (error) {
console.error("Failed to load auth state from storage:", error);
this.clearStorage();
}
}
}
// 清除本地存储
private clearStorage() {
if (typeof window === "undefined") return;
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
}
}
export const authStore = new AuthStore();
This diff is collapsed.
// 文件位置:src/types/api.ts
// API接口参数和响应类型定义
// === 创建房间 ===
export interface RoomAddArgs {
user: string;
roomName: string;
}
export interface RoomAddRes {
roomId: number;
}
// === 获取房间列表 ===
export interface RoomListRes {
rooms: import('./index').RoomPreviewInfo[];
}
// === 删除房间 ===
export interface RoomDeleteArgs {
user: string;
roomId: number;
}
// === 添加消息 ===
export interface MessageAddArgs {
roomId: number;
content: string;
sender: string;
}
// === 获取房间消息列表 ===
export interface RoomMessageListArgs {
roomId: number;
}
export interface RoomMessageListRes {
messages: import('./index').Message[];
}
// === 获取消息更新 ===
export interface RoomMessageGetUpdateArgs {
roomId: number;
sinceMessageId: number;
}
export interface RoomMessageGetUpdateRes {
messages: import('./index').Message[];
}
// === 通用响应格式 ===
export interface ApiResponse<T = any> {
code: number;
data?: T;
message?: string;
}
// 用户认证相关类型
// 这个接口表示用户的 登录凭据,即用户用来登录的 用户名 和 密码。
export interface UserCredentials {
username: string;
password: string;
}
//这个接口表示 注册时 传递的参数,它继承了 UserCredentials 接口(意味着注册时也需要用户名和密码),并且新增了一个可选的 电子邮件 字段。
export interface RegisterArgs extends UserCredentials {
email?: string;
}
export interface LoginArgs extends UserCredentials {}
// 这个接口表示 认证响应,即用户成功登录后,后端返回的用户信息和 JWT token。它包含了用户的基本信息和一个 JWT token,该 token 用于后续的身份验证。
export interface AuthResponse {
user: {
userId: number;
username: string;
email?: string;
};
token: string;
}
// 这个接口表示 JWT payload,即存储在 JWT token 中的用户信息。JWT token 是一个自包含的令牌,包含用户认证所需的最小信息,这些信息是经过加密后可以验证的。
export interface JWTPayload {
userId: number; // 用户的唯一 ID,类型为 number
username: string; // 用户的用户名,类型为 string。
iat: number; //表示 token 颁发的时间戳,类型为 number(单位是秒)。
exp: number; //表示 token 过期的时间戳,类型为 number(单位是秒)。
}
// 认证用户信息 (用于API中间件)
// 这个接口表示 已认证用户,即认证过程完成后,系统中表示用户身份的最简信息
export interface AuthenticatedUser {
userId: number;
username: string;
}
// src/types/index.ts
export interface Message {
messageId: number; // 消息 id
roomId: number; // 房间 id
sender: string; // 发送人的 username
content: string; // 消息内容
time: number; // 消息发送时间戳
}
export interface RoomPreviewInfo {
roomId: number;
roomName: string;
// UPDATE 20230814: 这里增加了 null 的可能性
lastMessage: Message | null;
}
// 额外添加的有用类型
export interface User {
userId: number;
username: string;
isOnline?: boolean;
}
export interface ChatRoomData {
roomId: number;
roomName: string;
messages: Message[];
participants: User[];
}
// 导出API类型
export * from './api';
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}