LilChat项目复杂度+难点+亮点建议

如果我是面试官,我会优先看:你做过"真实产品级"的闭环(登录-聊天-消息一致性),以及你是否能把难点做对并讲清楚工程化取舍。你这个项目已经有"注册登录 + 聊天 + socket.io 实时推送"的雏形了,接下来最能"眼前一亮"的改进,我建议从下面这些方向挑 2-3 个重点做深(别全堆,做精更值钱)。

一眼能加分的技术亮点(推荐优先级从高到低)

  1. JWT 鉴权 + 受保护接口(注册/登录后给 access token)
  • 后端给 /api/messages/*、/api/auth/setAvatar 做鉴权中间件,未登录直接 401。
  • 前端用 token(最好是 httpOnly cookie)后,聊天接口不再依赖 localStorage 的明文用户对象。
  • 面试时你可以讲清楚:安全性提升、前后端怎么协同、如何防止越权访问。
  1. socket.io "房间化"与可靠投递(Rooms + ACK)
  • 现在是用 onlineUsers Map(userId -> socketId) 直推,容易有边界问题。
  • 升级成:按 chatRoomId 或 sortedUserPair 组成 room,发送时 io.to(room).emit(...)。
  • 加 ack 回执:发送方收到服务器确认才把消息标为 "已发送",失败就回滚/提示。
  • 这是实习面试里非常硬核的"实时一致性"点。
  1. 消息状态:已读/未读 + "对方正在输入..."(typing indicator)
  • 当接收方打开聊天页:把对应消息更新为 readAt/seenBy。
  • typing:前端每隔 1s 发送一次 typing 事件,后端转发给对方展示"对方正在输入"。
  • 这两个点能显著提升"产品感",面试官通常会追问你如何避免刷屏(节流/防抖)。
  1. 消息分页/历史加载(Infinite Scroll)
  • 别只拉全部消息:后端支持 GET /messages?roomId=&cursor= 或 page/limit。
  • 前端实现"向上滚动加载更早消息",并用时间戳索引(createdAt / updatedAt)优化查询。
  • 这是工程能力 + 性能优化的体现。
  1. 前端工程化:把 socket 和请求做成可复用 hook/状态管理
  • 把 socket 逻辑封装成 useChatSocket()(自动连、重连、事件绑定/解绑、toast 提示)。
  • 数据请求用 react-query(或至少封装 axios),并实现乐观更新。
  • 代码结构清晰后,你讲起来会特别像"能维护的同学"。
  1. TypeScript + 组件规范化(可选但很加分)
  • 把消息结构、用户结构、socket event payload 都类型化。
  • 组件层做统一的 UI(你现在用 styled-components + 组件化是好的方向)。
  1. 测试与持续集成(强烈建议至少做后端测试)
  • 用 jest + supertest 测:register/login、setAvatar、addMessage、getMessages。
  • GitHub Actions 做 CI:每次 push 自动跑测试和 lint。
  • 面试官会很在意"可验证性",这能让你从"写出能跑的项目"升级到"写出可交付的项目"。

二选一的"质变"路线(我建议你这样打磨)

  • 路线 A(实时一致性路线):JWT + socket rooms + ACK + 已读/typing + 分页
  • 路线 B(工程化路线):TypeScript + react-query + socket hook + 测试 + CI/CD(把可靠性讲清楚)

你现在项目最值得先修的"讲故事点"

面试时你可以坦诚地说你怎么发现问题并修复(这会显得你有 debug 能力):

  • 你现在 socket/消息那块已经有真实修复经历(非常好讲)
  • 建议把"你修复过哪些坑、为什么会出现、最后怎么验证"整理成面试可复述的 1-2 分钟故事

我会完全从零教你,不用任何前置知识,直接复制代码就能用,适配你的现有项目(Express + MongoDB + React)。

原理

  1. JWT = 登录成功后,后端给前端发一个加密令牌(token)
  2. httpOnly Cookie = 把 token 存在浏览器里,前端 JS 读不到,超级安全(不会被盗)
  3. 鉴权中间件 = 后端写一个 "门卫",访问 /api/messages/* /api/auth/setAvatar 必须带 token,否则直接 401 拒绝
  4. 改造后:前端不再用 localStorage 存明文用户,全部靠 token 认证

1.后端安装依赖

复制代码
npm install jsonwebtoken cookie-parser
  • jsonwebtoken:生成 / 验证 JWT
  • cookie-parser:让 Express 能读写 Cookie

2.后端主文件配置(server.js)

javascript 复制代码
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const cookieParser = require("cookie-parser"); // 加这个
require("dotenv").config();

const app = express();

// 中间件
app.use(express.json());
app.use(cookieParser()); // 加这个
app.use(
  cors({
    origin: "http://localhost:5173", // 你的前端地址
    credentials: true, // 必须开,才能传递 cookie
  })
);

// 数据库连接
mongoose.connect(process.env.MONGO_URI);

// 路由
app.use("/api/auth", require("./routes/authRoutes"));
app.use("/api/messages", require("./routes/messageRoutes"));

app.listen(5000, () => console.log("Server running on port 5000"));

3.创建 JWT 工具函数(生成 token)

新建 utils/generateToken.js

javascript 复制代码
const jwt = require("jsonwebtoken");

const generateToken = (userId, res) => {
  // 生成 JWT token,有效期 7 天
  const token = jwt.sign({ id: userId }, process.env.JWT_SECRET, {
    expiresIn: "7d",
  });

  // 把 token 存入 httpOnly cookie(最安全)
  res.cookie("jwt", token, {
    httpOnly: true, // 前端 JS 无法读取,防 XSS
    secure: process.env.NODE_ENV === "production", // 生产环境开启 HTTPS
    sameSite: "strict", // 防 CSRF
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7天
  });
};

module.exports = generateToken;

4.创建鉴权中间件(门卫)

新建 middleware/authMiddleware.js

javascript 复制代码
const jwt = require("jsonwebtoken");
const User = require("../models/userModel");

// 鉴权中间件:检查是否登录
const protectRoute = async (req, res, next) => {
  try {
    // 从 cookie 中获取 jwt
    const token = req.cookies.jwt;

    if (!token) {
      return res.status(401).json({ message: "未登录,请先登录" });
    }

    // 验证 token 是否有效
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // 把用户信息挂载到 req,后续接口可用
    const user = await User.findById(decoded.id).select("-password");
    req.user = user;

    next(); // 放行
  } catch (err) {
    res.status(401).json({ message: "无效的 token,登录已过期" });
  }
};

module.exports = protectRoute;

5.修改登录 / 注册接口(下发 token)

打开你的 authRoutes.js

1. 注册成功下发 token

javascript 复制代码
const generateToken = require("../utils/generateToken");

router.post("/register", async (req, res) => {
  try {
    const { username, email, password } = req.body;
    const user = await User.create({ username, email, password });

    // 👇 关键:生成 token 并写入 cookie
    generateToken(user._id, res);

    res.status(201).json({
      _id: user._id,
      username: user.username,
      email: user.email,
      isAvatarImageSet: user.isAvatarImageSet,
    });
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

2. 登录成功下发 token

javascript 复制代码
router.post("/login", async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = await User.findOne({ email });

    // 密码验证...

    // 👇 关键:生成 token 并写入 cookie
    generateToken(user._id, res);

    res.json({
      _id: user._id,
      username: user.username,
      email: user.email,
      isAvatarImageSet: user.isAvatarImageSet,
    });
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

6.给接口加保护(必须登录才能访问)

1. 保护 /api/auth/setAvatar

javascript 复制代码
const protectRoute = require("../middleware/authMiddleware");

// 👇 加中间件 protectRoute
router.post("/setAvatar", protectRoute, async (req, res) => {
  // 你的设置头像逻辑
  // 可以直接用 req.user._id 获取当前登录用户
});

2. 保护 /api/messages/* 所有接口

javascript 复制代码
const express = require("express");
const router = express.Router();
const protectRoute = require("../middleware/authMiddleware");

// 所有消息接口必须登录
router.use(protectRoute);

router.post("/", async (req, res) => { ... });
router.get("/:chatId", async (req, res) => { ... });

module.exports = router;

7.前端改造(axios 自动带 cookie)

你所有的 axios 请求都要加 withCredentials: true,这样 cookie 会自动发送给后端。

src/utils/APIRoutes.js 同级新建 axiosInstance.js

javascript 复制代码
import axios from "axios";

const axiosInstance = axios.create({
  baseURL: "http://localhost:5000",
  withCredentials: true, // 关键:自动携带 cookie
});

export default axiosInstance;

以后所有请求都用这个 axios

javascript 复制代码
// 以前
import axios from "axios";
// 现在
import axios from "../utils/axiosInstance";

8.前端不再依赖 localStorage

你现在的 Chat.jsx 是读 localStorage,改造后完全不用

前端获取当前用户 → 靠后端鉴权接口

新建接口 /api/auth/me

javascript 复制代码
router.get("/me", protectRoute, async (req, res) => {
  res.json(req.user); // 返回当前登录用户
});

前端使用

javascript 复制代码
useEffect(() => {
  const getUser = async () => {
    const { data } = await axios.get("/api/auth/me");
    setCurrentUser(data);
  };
  getUser();
}, []);

✅ 最终效果

  1. JWT 鉴权 完成
  2. httpOnly cookie 存储 token(超级安全)
  3. ✅ 受保护接口:/api/messages/*/api/auth/setAvatar
  4. ✅ 未登录直接返回 401
  5. ✅ 前端不再使用 localStorage 明文存用户
  6. ✅ 自动登录、自动过期、安全防攻击
相关推荐
Sheldon一蓑烟雨任平生3 小时前
WebSocket 聊天室项目(结合Socket.IO)
websocket·客户端·服务端·socket.io·聊天室
Highcharts.js17 小时前
Highcharts React v4 迁移指南(下):分步代码示例与常见问题解决
javascript·react.js·typescript·react·highcharts·代码示例·v4迁移
Highcharts.js1 天前
Highcharts React v4 迁移指南(上):核心变更解析与升级收益
前端·javascript·react.js·react·数据可视化·highcharts·v4迁移
hh随便起个名1 天前
useRef和useState对比
前端·javascript·react
吐个泡泡v3 天前
AI Agent 核心认知框架详解
react·cot·ai agent·认知框架
~无忧花开~3 天前
React组件与Props完全指南
开发语言·前端·react
yusheng_xyb3 天前
使用TypeScript与React构建高效用户界面
typescript·react·前端开发
胡少侠74 天前
ReAct Agent:手写 Thought-Action-Observe 循环,从工具调用到真正的 Agent
ai·agent·react·rag
胡西风_foxww5 天前
nextjs部署更新,Turbopack 和 Webpack 缓存冲突问题解决
缓存·webpack·react·nextjs·turbopack