一、项目概述
1.1 项目简介
匿名论坛是一个支持用户注册登录和匿名发帖的社区平台,提供帖子发布、回复、点赞、收藏、私信、通知等完整社交功能,并配备管理后台进行内容审核和用户管理。
1.2 核心特性
- 双模式身份:支持注册用户和匿名访客
- 完整社交功能:发帖、回复、点赞、收藏、私信
- 楼中楼回复:支持嵌套回复和@提及
- 实时通知:新回复、点赞通知
- 管理后台:用户/内容管理、操作日志
- 安全防护:XSS过滤、速率限制、敏感词过滤
二、技术栈详解
2.1 前端技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| React | 18.3.1 | 核心框架,函数组件 + Hooks |
| TypeScript | – | 类型安全 |
| React Router DOM | latest | 客户端路由,SPA 导航 |
| Vite | 6.3.5 | 构建工具,HMR 热更新 |
| Tailwind CSS | – | 原子化 CSS 框架 |
| Radix UI | 多个组件 | 无障碍 UI 原语组件 |
| Axios | 1.13.2 | HTTP 客户端 |
| Sonner | 2.0.3 | Toast 通知 |
| Lucide React | 0.487.0 | 图标库 |
| React Markdown | latest | Markdown 渲染 |
| React Syntax Highlighter | latest | 代码高亮 |
| date-fns | latest | 日期处理 |
| next-themes | 0.4.6 | 主题切换(明/暗) |
2.2 后端技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Node.js | – | 运行时环境 |
| Express.js | 4.18.2 | Web 框架 |
| TypeScript | 5.3.3 | 类型安全 |
| MySQL2 | 3.6.5 | 数据库驱动(Promise API) |
| bcryptjs | 3.0.3 | 密码加密(bcrypt 算法) |
| UUID | 9.0.1 | 唯一标识符生成 |
| Helmet | 7.1.0 | HTTP 安全头 |
| CORS | 2.8.5 | 跨域资源共享 |
| express-rate-limit | 7.1.5 | 速率限制 |
| express-validator | 7.0.1 | 请求参数验证 |
| XSS | 1.0.14 | XSS 过滤 |
| Multer | 1.4.5-lts.1 | 文件上传 |
| Sharp | 0.33.1 | 图片压缩优化 |
| Winston | 3.11.0 | 日志框架 |
| winston-daily-rotate-file | 4.7.1 | 日志轮转 |
| Compression | 1.7.4 | Gzip 压缩 |
2.3 数据库
| 技术 | 版本 | 配置 |
|---|---|---|
| MySQL | 8.x | InnoDB 引擎 |
| 字符集 | utf8mb4 | 支持 emoji |
| 排序规则 | utf8mb4_unicode_ci | Unicode 排序 |
| 连接池 | 20 连接 | mysql2 连接池 |
2.4 部署环境
| 组件 | 配置 |
|---|---|
| 服务器 | 腾讯云 CVM (101.32.242.75) |
| 操作系统 | Linux |
| Web 服务器 | Nginx(反向代理 + 静态文件) |
| 进程管理 | PM2 |
| 域名 | forum.zcc609.online |
| 前端路径 | /home/anonymous-forum/frontend |
| 后端路径 | /home/anonymous-forum/backend |
| 后端端口 | 3001 |
三、项目结构详解
3.1 完整目录结构
匿名论坛/
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── .gitignore # Git 忽略规则
├── index.html # 入口 HTML
├── package.json # 前端依赖
├── package-lock.json # 依赖锁定
├── vite.config.ts # Vite 配置
├── nginx-forum.conf # Nginx 配置模板
├── deploy-to-server.sh # 部署脚本
├── server-start.sh # 服务启动脚本
├── 上传到服务器.sh # 上传脚本
├── 部署文档.md # 部署说明
│
├── src/ # 前端源码
│ ├── main.tsx # 应用入口
│ ├── App.tsx # 根组件 + 路由配置
│ ├── index.css # Tailwind 样式
│ │
│ ├── components/ # 通用组件 (25个)
│ │ ├── ForumHeader.tsx # 顶部导航栏
│ │ ├── ForumSidebar.tsx # 侧边栏(分类、热帖)
│ │ ├── MobileBottomNav.tsx # 移动端底部导航
│ │ ├── MobileSidebar.tsx # 移动端侧边栏
│ │ ├── PostCard.tsx # 帖子卡片
│ │ ├── PostCardSkeleton.tsx # 帖子骨架屏
│ │ ├── ReplyItem.tsx # 回复项(支持楼中楼)
│ │ ├── CreatePostDialog.tsx # 发帖对话框
│ │ ├── MarkdownEditor.tsx # Markdown 编辑器
│ │ ├── EmojiPicker.tsx # 表情选择器
│ │ ├── ImageUploader.tsx # 图片上传组件
│ │ ├── ImageViewer.tsx # 图片查看器
│ │ ├── ImageGallery.tsx # 图片画廊
│ │ ├── NotificationDropdown.tsx # 通知下拉
│ │ ├── MessageDropdown.tsx # 消息下拉
│ │ ├── UserCard.tsx # 用户信息卡片
│ │ ├── ThemeToggle.tsx # 主题切换
│ │ ├── SortSelector.tsx # 排序选择器
│ │ ├── EmptyState.tsx # 空状态
│ │ ├── LoadMoreIndicator.tsx # 加载更多指示器
│ │ ├── ErrorBoundary.tsx # 错误边界
│ │ ├── ProtectedRoute.tsx # 路由守卫
│ │ └── KeyboardShortcutsDialog.tsx # 快捷键帮助
│ │
│ ├── pages/ # 页面组件 (15个)
│ │ ├── PostListPage.tsx # 帖子列表(首页)
│ │ ├── PostDetailPage.tsx # 帖子详情
│ │ ├── SearchResultPage.tsx # 搜索结果
│ │ ├── LoginPage.tsx # 登录页
│ │ ├── RegisterPage.tsx # 注册页
│ │ ├── ProfilePage.tsx # 个人资料
│ │ ├── MyPostsPage.tsx # 我的帖子
│ │ ├── MyRepliesPage.tsx # 我的回复
│ │ ├── MyFavoritesPage.tsx # 我的收藏
│ │ ├── MyLikesPage.tsx # 我的点赞
│ │ ├── DraftsPage.tsx # 草稿箱
│ │ ├── Messages.tsx # 私信列表
│ │ ├── MessageConversation.tsx # 私信对话
│ │ ├── NotificationPage.tsx # 通知页面
│ │ └── admin/ # 管理后台
│ │ ├── AdminDashboard.tsx # 管理仪表盘
│ │ ├── AdminUsers.tsx # 用户管理
│ │ ├── AdminPosts.tsx # 帖子管理
│ │ └── AdminLogs.tsx # 操作日志
│ │
│ ├── hooks/ # 自定义 Hooks
│ │ ├── useInfiniteScroll.ts # 无限滚动
│ │ └── useKeyboardShortcuts.ts # 快捷键
│ │
│ ├── lib/ # 工具库
│ │ ├── api.ts # API 封装(帖子、回复、互动)
│ │ ├── apiClient.ts # Axios 实例配置
│ │ ├── auth.ts # 认证工具(登录状态管理)
│ │ ├── messageApi.ts # 私信 API
│ │ ├── notificationApi.ts # 通知 API
│ │ ├── storage.ts # 本地存储工具
│ │ ├── constants.ts # 常量定义
│ │ ├── utils.ts # 通用工具函数
│ │ ├── portal.ts # Portal 容器
│ │ └── mockData.ts # 模拟数据(开发用)
│ │
│ ├── types/ # 类型定义
│ │ ├── index.ts # 业务类型(Post, Reply 等)
│ │ └── auth.ts # 认证类型
│ │
│ └── styles/ # 样式
│ └── globals.css # 全局样式
│
├── backend/ # 后端源码
│ ├── .env # 开发环境变量
│ ├── .env.production # 生产环境变量
│ ├── package.json # 后端依赖
│ ├── tsconfig.json # TypeScript 配置
│ ├── backup-db.sh # 数据库备份脚本
│ ├── repair-db.sh # 数据库修复脚本
│ ├── reset-admin.sh # 管理员密码重置
│ │
│ ├── src/
│ │ ├── index.ts # 应用入口
│ │ │
│ │ ├── config/ # 配置
│ │ │ ├── database.ts # 数据库连接池
│ │ │ └── logger.ts # Winston 日志配置
│ │ │
│ │ ├── middleware/ # 中间件
│ │ │ ├── auth.ts # 认证中间件
│ │ │ │ ├── authenticate() # 强制登录验证
│ │ │ │ ├── optionalAuth() # 可选登录验证
│ │ │ │ └── requireAdmin() # 管理员权限验证
│ │ │ │
│ │ │ ├── security.ts # 安全中间件
│ │ │ │ ├── sanitizeInput() # XSS 过滤
│ │ │ │ ├── generalLimiter # 通用速率限制
│ │ │ │ ├── createLimiter # 创建内容限制
│ │ │ │ ├── uploadLimiter # 上传限制
│ │ │ │ ├── filterSensitiveWords() # 敏感词过滤
│ │ │ │ ├── logOperation() # 操作日志
│ │ │ │ ├── validateId() # ID 格式验证
│ │ │ │ └── errorHandler() # 全局错误处理
│ │ │ │
│ │ │ └── validation.ts # 参数验证
│ │ │ ├── validateCreatePost # 创建帖子验证
│ │ │ ├── validateCreateReply # 创建回复验证
│ │ │ ├── validateGetPosts # 获取帖子验证
│ │ │ ├── validateSearch # 搜索验证
│ │ │ ├── validateToggleLike # 点赞验证
│ │ │ ├── validateSendMessage # 私信验证
│ │ │ └── validateSaveDraft # 草稿验证
│ │ │
│ │ ├── controllers/ # 控制器
│ │ │ ├── authController.ts # 认证控制器
│ │ │ ├── postController.ts # 帖子控制器
│ │ │ ├── replyController.ts # 回复控制器
│ │ │ ├── userController.ts # 用户控制器
│ │ │ ├── interactionController.ts # 互动控制器
│ │ │ ├── uploadController.ts # 上传控制器
│ │ │ └── adminController.ts # 管理控制器
│ │ │
│ │ ├── routes/ # 路由
│ │ │ ├── index.ts # 路由汇总
│ │ │ ├── auth.ts # 认证路由
│ │ │ ├── posts.ts # 帖子路由
│ │ │ ├── replies.ts # 回复路由
│ │ │ ├── users.ts # 用户路由
│ │ │ ├── interactions.ts # 互动路由
│ │ │ ├── upload.ts # 上传路由
│ │ │ └── admin.ts # 管理路由
│ │ │
│ │ └── types/ # 类型定义
│ │ ├── auth.ts # 认证类型
│ │ └── express.d.ts # Express 类型扩展
│ │
│ ├── database/ # SQL 脚本
│ │ ├── schema.sql # 主表结构
│ │ ├── auth_migration.sql # 认证相关表
│ │ ├── add_login_fields.sql # 登录字段迁移
│ │ └── add_profile_fields.sql # 个人资料字段
│ │
│ ├── dist/ # 编译输出
│ ├── uploads/ # 上传文件存储
│ └── logs/ # 日志文件
│
├── build/ # 前端构建产物
│ ├── index.html
│ └── assets/
│ ├── index-*.css
│ └── index-*.js
│
└── scripts/ # 部署脚本
├── start-services.sh # 启动服务
└── stop-services.sh # 停止服务
四、功能模块详解
4.1 用户认证模块
功能列表
| 功能 | 接口 | 说明 |
|---|---|---|
| 注册 | POST /api/auth/register | 用户名+密码+邮箱(可选) |
| 登录 | POST /api/auth/login | 返回 Session Token |
| 登出 | POST /api/auth/logout | 删除会话 |
| 获取当前用户 | GET /api/auth/me | 返回用户信息 |
| 修改密码 | POST /api/auth/change-password | 需验证旧密码 |
| 匿名模式 | POST /api/auth/anonymous | 生成临时匿名ID |
认证流程
1. 用户注册
├── 验证用户名长度(3-20)、密码长度(>=6)
├── 检查用户名/邮箱唯一性
├── bcrypt 加密密码 (cost=10)
├── 生成 UUID 用户ID
├── 生成匿名昵称 "用户_${username}"
├── 插入 users 表
├── 创建 session (30天有效期)
└── 返回 token + 用户信息
2. 用户登录
├── 查询用户
├── 检查账号激活状态
├── bcrypt 比对密码
├── 更新登录信息 (login_count, last_login_at, last_login_ip)
├── 创建新 session
└── 返回 token + 用户信息
3. 请求鉴权 (authenticate 中间件)
├── 从 Authorization Header 提取 Bearer Token
├── 查询 sessions 表 (JOIN users)
├── 验证 token 有效性和过期时间
├── 检查账号激活状态
├── 注入 req.user, req.userId, req.anonymousName
└── 调用 next()
Session 表结构
sql
CREATE TABLE sessions (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
token VARCHAR(255) NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL,
user_agent VARCHAR(500),
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
4.2 帖子模块
功能列表
| 功能 | 接口 | 说明 |
|---|---|---|
| 获取帖子列表 | GET /api/posts | 支持分类、排序、分页 |
| 获取帖子详情 | GET /api/posts/:id | 自动增加浏览量 |
| 创建帖子 | POST /api/posts | 需登录 |
| 删除帖子 | DELETE /api/posts/:id | 作者或管理员 |
| 搜索帖子 | GET /api/posts/search | 全文搜索 |
| 热门帖子 | GET /api/posts/hot | 按热度算法排序 |
| 我的帖子 | GET /api/posts/user/my-posts | 当前用户的帖子 |
帖子分类
typescript
type PostCategory = 'tech' | 'life' | 'help' | 'other';
// 分类映射
const CATEGORIES = {
tech: '技术交流',
life: '生活杂谈',
help: '求助问答',
other: '其他'
};
排序方式
typescript
type SortType = 'latest' | 'most_replies' | 'recent_reply';
// 排序 SQL
switch (sort) {
case 'most_replies':
ORDER BY reply_count DESC, created_at DESC
case 'recent_reply':
ORDER BY COALESCE(last_reply_at, created_at) DESC
case 'latest':
default:
ORDER BY created_at DESC
}
热度算法
sql
hot_score = reply_count * 0.7 + like_count * 0.3
-- 取最近 24 小时内的帖子,按 hot_score 降序
4.3 回复模块
功能列表
| 功能 | 接口 | 说明 |
|---|---|---|
| 获取回复列表 | GET /api/replies/post/:postId | 按时间升序 |
| 创建回复 | POST /api/replies/post/:postId | 支持楼中楼 |
| 删除回复 | DELETE /api/replies/:id | 作者或管理员 |
| 我的回复 | GET /api/replies/user/my-replies | 包含帖子信息 |
楼中楼回复
typescript
interface Reply {
id: string;
post_id: string;
user_id: string;
content: string;
parent_reply_id: string | null; // 父回复ID
parent_floor_number: number | null; // 父回复楼层
parent_username: string | null; // 被回复用户名
created_at: Date;
}
回复通知逻辑
创建回复时:
├── 如果是楼中楼 (有 parent_reply_id)
│ └── 给被回复用户发送通知
└── 如果是直接回复
└── 给帖子作者发送通知
通知内容: "${回复者匿名名} 回复了你的${帖子/评论}"
4.4 互动模块
功能列表
| 功能 | 接口 | 说明 |
|---|---|---|
| 点赞/取消点赞 | POST /api/interactions/like/:id | type: post/reply |
| 收藏/取消收藏 | POST /api/interactions/favorite/:postId | 帖子收藏 |
| 获取收藏列表 | GET /api/interactions/favorites | 当前用户 |
| 获取点赞帖子 | GET /api/interactions/liked-posts | 当前用户 |
| 获取获赞数 | GET /api/interactions/user-likes | 收到的总赞数 |
| 发送私信 | POST /api/interactions/messages | 用户间私信 |
| 获取对话列表 | GET /api/interactions/conversations | 私信会话 |
| 获取对话详情 | GET /api/interactions/conversation/:userId | 与某用户的对话 |
| 未读消息数 | GET /api/interactions/unread-count | 未读私信数 |
点赞逻辑
点赞切换:
├── 查询是否已点赞
├── 如果已点赞
│ ├── 删除 likes 记录
│ └── 更新 posts.like_count - 1
└── 如果未点赞
├── 插入 likes 记录
├── 更新 posts.like_count + 1
└── 发送点赞通知给作者
4.5 通知模块
通知类型
sql
type ENUM('new_reply', 'like', 'system')
| 类型 | 触发场景 |
|---|---|
| new_reply | 帖子/回复被回复 |
| like | 帖子/回复被点赞 |
| system | 系统通知 |
通知表结构
sql
CREATE TABLE notifications (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
type ENUM('new_reply', 'like', 'system') NOT NULL,
title VARCHAR(100) NOT NULL,
content VARCHAR(500) NOT NULL,
post_id VARCHAR(36) NULL, -- 关联帖子(可点击跳转)
is_read TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
4.6 管理后台模块
功能列表
| 功能 | 接口 | 说明 |
|---|---|---|
| 获取用户列表 | GET /api/admin/users | 分页、搜索 |
| 启用/禁用用户 | PATCH /api/admin/users/:userId/status | 切换激活状态 |
| 删除用户 | DELETE /api/admin/users/:userId | 不能删除管理员 |
| 获取帖子列表 | GET /api/admin/posts | 分页、搜索、分类 |
| 删除帖子 | DELETE /api/admin/posts/:postId | 管理员删除 |
| 删除回复 | DELETE /api/admin/replies/:replyId | 管理员删除 |
| 获取操作日志 | GET /api/admin/logs | 管理员操作记录 |
操作日志记录
typescript
// 记录的操作类型
type AdminAction =
| 'enable_user' // 启用用户
| 'disable_user' // 禁用用户
| 'delete_user' // 删除用户
| 'delete_post' // 删除帖子
| 'delete_reply'; // 删除回复
// 日志表结构
CREATE TABLE admin_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
admin_id VARCHAR(36) NOT NULL,
action VARCHAR(50) NOT NULL,
target_type VARCHAR(50), -- user/post/reply
target_id VARCHAR(36),
description TEXT,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
4.7 文件上传模块
功能列表
| 功能 | 接口 | 说明 |
|---|---|---|
| 单图上传 | POST /api/upload/image | 返回图片 URL |
| 多图上传 | POST /api/upload/images | 批量上传 |
图片处理
typescript
// 使用 Sharp 进行图片优化
const processImage = async (file) => {
return sharp(file.buffer)
.resize(1920, 1080, { fit: 'inside', withoutEnlargement: true })
.jpeg({ quality: 80 })
.toBuffer();
};
// 文件命名: optimized_${uuid}.${ext}
// 存储路径: backend/uploads/
// 访问路径: /uploads/optimized_xxx.jpg
五、数据库设计
5.1 ER 图(文字描述)
┌──────────────┐
│ users │
│──────────────│
│ id (PK) │
│ username │
│ password_hash│
│ email │
│ anonymous_name│
│ role │
│ is_active │
└──────┬───────┘
│
┌───────────────────────────┼───────────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ posts │ │ sessions │ │ admin_logs │
│──────────────│ │──────────────│ │──────────────│
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ user_id (FK) │◄───────────│ user_id (FK) │ │ admin_id (FK)│
│ title │ │ token │ │ action │
│ content │ │ expires_at │ │ target_type │
│ category │ └──────────────┘ │ description │
│ reply_count │ └──────────────┘
│ like_count │
│ view_count │
└──────┬───────┘
│
├───────────────────────────┬───────────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ replies │ │ favorites │ │ images │
│──────────────│ │──────────────│ │──────────────│
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ post_id (FK) │ │ user_id (FK) │ │ related_type │
│ user_id (FK) │ │ post_id (FK) │ │ related_id │
│ content │ └──────────────┘ │ url │
│ parent_reply_id│ └──────────────┘
└──────┬───────┘
│
▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ likes │ │ messages │ │ notifications│
│──────────────│ │──────────────│ │──────────────│
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ user_id (FK) │ │ from_user_id │ │ user_id (FK) │
│ target_type │ │ to_user_id │ │ type │
│ target_id │ │ content │ │ title │
└──────────────┘ │ is_read │ │ post_id │
└──────────────┘ │ is_read │
└──────────────┘
5.2 核心表详细设计
users 表
sql
CREATE TABLE users (
id VARCHAR(36) PRIMARY KEY, -- UUID
username VARCHAR(50) UNIQUE, -- 用户名(可为空,匿名用户)
password_hash VARCHAR(255), -- bcrypt 加密密码
email VARCHAR(100) UNIQUE, -- 邮箱
phone VARCHAR(20), -- 手机号
avatar VARCHAR(500), -- 头像 URL
anonymous_name VARCHAR(50) NOT NULL, -- 显示名称
role ENUM('user', 'admin') DEFAULT 'user',
is_active BOOLEAN DEFAULT TRUE, -- 账号状态
login_count INT DEFAULT 0, -- 登录次数
last_login_at TIMESTAMP, -- 最后登录时间
last_login_ip VARCHAR(45), -- 最后登录 IP
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_username (username),
INDEX idx_email (email),
INDEX idx_role (role)
);
posts 表
sql
CREATE TABLE posts (
id VARCHAR(36) PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
category ENUM('tech', 'life', 'help', 'other') NOT NULL,
user_id VARCHAR(36) NOT NULL,
anonymous_name VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
reply_count INT DEFAULT 0, -- 冗余字段,提升查询性能
like_count INT DEFAULT 0, -- 冗余字段
view_count INT DEFAULT 0,
last_reply_at TIMESTAMP NULL, -- 最后回复时间
is_deleted TINYINT(1) DEFAULT 0, -- 软删除
INDEX idx_user_id (user_id),
INDEX idx_category (category),
INDEX idx_created_at (created_at),
INDEX idx_last_reply_at (last_reply_at),
FULLTEXT INDEX idx_search (title, content) -- 全文索引
);
replies 表
sql
CREATE TABLE replies (
id VARCHAR(36) PRIMARY KEY,
post_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
anonymous_name VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
parent_reply_id VARCHAR(36) NULL, -- 楼中楼父回复
parent_floor_number INT NULL, -- 父回复楼层号
parent_username VARCHAR(50) NULL, -- 被回复用户名
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
like_count INT DEFAULT 0,
is_deleted TINYINT(1) DEFAULT 0,
INDEX idx_post_id (post_id),
INDEX idx_user_id (user_id),
INDEX idx_parent_reply_id (parent_reply_id),
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE
);
sessions 表
sql
CREATE TABLE sessions (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
token VARCHAR(255) NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL,
user_agent VARCHAR(500),
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_token (token),
INDEX idx_user_id (user_id),
INDEX idx_expires_at (expires_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
六、API 接口详解
6.1 认证接口
POST /api/auth/register
Request:
{
"username": "testuser",
"password": "123456",
"email": "test@example.com" // 可选
}
Response:
{
"success": true,
"message": "注册成功",
"user": { id, username, email, anonymous_name, role, is_active, created_at },
"token": "uuid_timestamp_random"
}
POST /api/auth/login
Request:
{
"username": "testuser",
"password": "123456"
}
Response:
{
"success": true,
"message": "登录成功",
"user": { ... },
"token": "..."
}
POST /api/auth/logout
Headers: Authorization: Bearer <token>
Response:
{
"success": true,
"message": "登出成功"
}
GET /api/auth/me
Headers: Authorization: Bearer <token>
Response:
{
"success": true,
"user": { ... }
}
POST /api/auth/change-password
Headers: Authorization: Bearer <token>
Request:
{
"currentPassword": "oldpass",
"newPassword": "newpass"
}
Response:
{
"success": true,
"message": "密码修改成功"
}
6.2 帖子接口
GET /api/posts?category=tech&sort=latest&page=1&limit=20
Response:
{
"posts": [
{
"id": "uuid",
"title": "帖子标题",
"content": "帖子内容",
"category": "tech",
"user_id": "uuid",
"anonymous_name": "用户_xxx",
"user_avatar": null,
"created_at": "2025-01-01T00:00:00.000Z",
"reply_count": 5,
"like_count": 10,
"view_count": 100,
"is_liked": false,
"is_favorited": true,
"images": ["url1", "url2"]
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5
}
}
GET /api/posts/:id
Response:
{
"id": "uuid",
"title": "...",
"content": "...",
...
}
POST /api/posts
Headers: Authorization: Bearer <token>
Request:
{
"title": "帖子标题",
"content": "帖子内容",
"category": "tech",
"images": ["url1", "url2"]
}
Response:
{
"id": "uuid",
"title": "...",
...
}
DELETE /api/posts/:id
Headers: Authorization: Bearer <token>
Response:
{
"message": "删除成功"
}
GET /api/posts/search?q=关键词&page=1&limit=20
Response:
{
"posts": [...]
}
GET /api/posts/hot?hours=24&limit=5
Response:
[{ ... }, { ... }]
6.3 回复接口
GET /api/replies/post/:postId
Response:
[
{
"id": "uuid",
"post_id": "uuid",
"user_id": "uuid",
"anonymous_name": "用户_xxx",
"user_avatar": null,
"content": "回复内容",
"parent_reply_id": null,
"parent_floor_number": null,
"parent_username": null,
"created_at": "...",
"like_count": 5,
"is_liked": false,
"images": []
}
]
POST /api/replies/post/:postId
Headers: Authorization: Bearer <token>
Request:
{
"content": "回复内容",
"parentReplyId": "uuid", // 可选,楼中楼
"parentFloorNumber": 3, // 可选
"parentUsername": "用户_xxx", // 可选
"images": []
}
Response:
{
"id": "uuid",
...
}
DELETE /api/replies/:id
Headers: Authorization: Bearer <token>
Response:
{
"message": "删除成功"
}
6.4 互动接口
POST /api/interactions/like/:id
Headers: Authorization: Bearer <token>
Request:
{
"type": "post" // 或 "reply"
}
Response:
{
"liked": true,
"message": "点赞成功"
}
POST /api/interactions/favorite/:postId
Headers: Authorization: Bearer <token>
Response:
{
"favorited": true,
"message": "收藏成功"
}
GET /api/interactions/favorites
Headers: Authorization: Bearer <token>
Response:
[{ post1 }, { post2 }]
POST /api/interactions/messages
Headers: Authorization: Bearer <token>
Request:
{
"toUserId": "uuid",
"content": "私信内容"
}
Response:
{
"id": "uuid",
"from_user_id": "...",
"to_user_id": "...",
"content": "...",
...
}
GET /api/interactions/conversations
Headers: Authorization: Bearer <token>
Response:
[
{
"other_user_id": "uuid",
"other_user_name": "用户_xxx",
"other_user_avatar": null,
"last_message": "最后一条消息",
"last_message_time": "...",
"unread_count": 3
}
]
七、安全设计
7.1 认证安全
| 措施 | 实现 |
|---|---|
| 密码加密 | bcrypt (cost=10) |
| Token 格式 | UUID + 时间戳 + 随机串 |
| Token 存储 | 数据库 sessions 表 |
| Token 有效期 | 30 天 |
| 会话管理 | 修改密码后其他设备登出 |
7.2 请求安全
| 措施 | 实现 |
|---|---|
| XSS 防护 | xss 库过滤所有输入 |
| SQL 注入 | 参数化查询 (mysql2) |
| CORS | 限制 origin |
| HTTP 安全头 | Helmet 中间件 |
| 速率限制 | express-rate-limit |
7.3 速率限制配置
typescript
// 通用 API: 5000 次/分钟 (开发模式)
generalLimiter = rateLimit({
windowMs: 60 * 1000,
max: 5000
});
// 创建内容: 1000 次/分钟 (开发模式)
createLimiter = rateLimit({
windowMs: 60 * 1000,
max: 1000
});
// 文件上传: 10 次/分钟
uploadLimiter = rateLimit({
windowMs: 60 * 1000,
max: 10
});
7.4 敏感词过滤
typescript
// 敏感词等级
type SensitiveLevel = 'low' | 'medium' | 'high';
// 处理逻辑
if (level === 'high') {
// 拒绝发布
return res.status(400).json({ error: '内容包含敏感词' });
} else {
// 记录日志,允许发布
await logOperation(...);
}
八、日志系统
8.1 Winston 配置
typescript
// 日志级别: error, warn, info, debug
// 日志格式: [YYYY-MM-DD HH:mm:ss] LEVEL: message
// 输出目标:
// 1. 控制台 (带颜色)
// 2. error-YYYY-MM-DD.log (仅错误)
// 3. combined-YYYY-MM-DD.log (所有日志)
// 日志轮转:
// - 每日轮转
// - 单文件最大 20MB
// - 保留 14 天
8.2 操作日志
typescript
// 记录到 operation_logs 表
await logOperation(userId, action, targetType, targetId, req);
// 记录内容:
// - user_id: 操作用户
// - action: 操作类型 (create_post, delete_reply, like, etc.)
// - target_type: 目标类型 (post, reply, user)
// - target_id: 目标 ID
// - ip_address: 客户端 IP
// - user_agent: 浏览器信息
九、部署架构
9.1 Nginx 配置
nginx
server {
listen 80;
server_name forum.zcc609.online;
# 前端静态文件
root /home/anonymous-forum/frontend;
# SPA 路由
location / {
try_files $uri $uri/ /index.html;
}
# API 反向代理
location /api/ {
proxy_pass http://127.0.0.1:3001/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 图片代理
location /uploads/ {
proxy_pass http://127.0.0.1:3001/uploads/;
}
# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
9.2 PM2 进程管理
bash
# 启动
pm2 start dist/index.js --name anonymous-forum
# 查看日志
pm2 logs anonymous-forum
# 重启
pm2 restart anonymous-forum
# 停止
pm2 stop anonymous-forum
9.3 部署流程
bash
# 1. 本地构建前端
npm run build
# 2. 上传到服务器
scp -r build/* root@server:/home/anonymous-forum/frontend/
# 3. 上传后端代码
scp -r backend/* root@server:/home/anonymous-forum/backend/
# 4. 服务器编译后端
cd /home/anonymous-forum/backend
npm install
npm run build
# 5. 重启服务
pm2 restart anonymous-forum
十、前端路由结构
typescript
// App.tsx 路由配置
<Routes>
{/* 认证页面 */}
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
{/* 管理后台 (需要管理员权限) */}
<Route path="/admin/*" element={
<ProtectedRoute requireAuth adminOnly>
<AdminDashboard />
</ProtectedRoute>
} />
{/* 私信对话页面 */}
<Route path="/messages/:userId" element={<MessageConversation />} />
{/* 主布局页面 (需要登录) */}
<Route element={<MainLayout />}>
<Route path="/" element={<PostListPage />} />
<Route path="/post/:id" element={<PostDetailPage />} />
<Route path="/search" element={<SearchResultPage />} />
<Route path="/my-replies" element={<MyRepliesPage />} />
<Route path="/my-posts" element={<MyPostsPage />} />
<Route path="/my-favorites" element={<MyFavoritesPage />} />
<Route path="/my-likes" element={<MyLikesPage />} />
<Route path="/notifications" element={<NotificationPage />} />
<Route path="/drafts" element={<DraftsPage />} />
<Route path="/messages" element={<Messages />} />
<Route path="/profile" element={<ProfilePage />} />
</Route>
{/* 404 重定向 */}
<Route path="*" element={<Navigate to="/" />} />
</Routes>
以上是项目的完整详细信息,涵盖技术栈、项目结构、功能模块、数据库设计、API 接口、安全设计、日志系统和部署架构。