校园墙是一个可自行部署的校园留言墙 SPA,采用 React 前端、Node/Express 后端和 PostgreSQL 18 数据库,并提供完整的用户、内容与管理功能。
- 前端:React 19、React Router 7、Tailwind CSS 4、Vite
- 后端:Node.js 20+、Express、multer、sharp、cookie-parser
- 数据库:PostgreSQL 18,留言正文使用 JSONB 保存原有数据结构
- 媒体处理:图片使用 sharp,视频处理依赖系统
ffmpeg - 本地数据库:Docker Compose
postgres:18
- 游客和学生账号均可匿名发帖、评论、上传图片/音视频与附件。
- 登录用户可选择展示昵称,学号只用于身份绑定和后台管理。
- 登录用户可收藏留言,并在“个人中心 -> 我的收藏”跨设备查看。
- 登录用户可在“我的发布”查看全部本人内容,包括匿名留言,并编辑文字、标签、匿名状态或安全删除自己的留言。
- 登录用户可在“我的评论”跨留言查看本人评论、回复引用与附件,并安全删除自己发表的评论。
- 评论支持按楼层回复并显示可信引用摘要;被回复者会收到独立回复通知。
- 留言和评论都可单独举报;提交后会生成独立追踪码,可公开查询待处理状态、处置结果和管理员公开说明。后台按留言聚合显示上下文,并可保留内容,或将违规评论、整条留言移入回收站。
- 帮助反馈会生成独立追踪码;提交者可在公开页面查询处理状态和管理员回复,后台可筛选、回复、备注、流转和结单。
- 留言收到评论或回复时会生成账号通知,导航栏显示未读数量;通知支持单条删除、全部已读和清空。
- 用户可自行修改头像、昵称、个人简介、性别和密码;修改或管理员重置密码后,其他设备的旧会话自动失效。
- 发帖文字、标签和匿名选项会自动保存为浏览器草稿,发布成功后自动清除。
- 留言支持移动端系统分享;不支持系统分享时自动复制详情链接。
- 可发布 2-6 项单选投票,支持截止时间、实时结果和跨刷新防重复投票。
- 点赞与点踩使用 PostgreSQL 反应记录,同一账号或访客只能保留一种状态,登录用户可跨设备恢复。
- 管理员可将优质留言置顶或设为精华,置顶内容在墙页优先展示,精华内容优先进入热门。
- 管理员可填写原因下架留言并随时恢复;下架内容不会出现在公开列表、详情、分区和热门中,作者仍可查看原因。
- 管理员可选择开启“发帖需要审核后公开”。开启后新留言和编辑过的公开留言进入待审核状态,作者仍可在“我的发布”查看;后台支持队列筛选、单条审核和批量通过、退回、下架、恢复。
- 后台提供独立评论管理队列,可搜索、筛选、单条或批量下架/恢复评论;被下架评论不会计入公开评论数和热门权重,回复中的原文引用会自动脱敏,登录作者仍可查看原因并收到通知。
- 留言与评论删除统一进入后台内容回收站;恢复会还原删除前的公开/待审/下架状态,只有回收站内容才能被彻底删除,附件会保留到最终清理。
- 发布者主动删除被举报内容时,对应待处理举报会自动归档,并向追踪码查询页返回“相关内容已由发布者删除”。
- 管理员写操作同步写入 PostgreSQL 结构化审计时间线,可按管理员、动作、对象和关键词检索;旧
admin_log.json仍保留兼容视图。 - 登录、发帖/评论、点赞/投票、上传、帮助/举报使用独立频率限制,降低撞库和刷接口风险。
- 管理后台支持运营统计、留言、账号、应用、公告、举报、日志和平台安全设置。
- 管理员账号支持后台新增、停用、重置密码和细粒度权限分配;密码仅保存 scrypt 哈希,改密、重置或停用会立即注销旧会话。
- 管理员可在平台设置中控制全站发帖/评论、游客发帖/评论和发帖预审,维护暂停说明、社区公约和敏感词;策略由后端强制执行。
- 学生登录验证码可在
/admin/settings配置 Turnstile 或 reCAPTCHA;服务端密钥加密保存且不会返回浏览器。
要求:
- Node.js 20+
- Docker Desktop,或一个可用的外部 PostgreSQL 18
- 系统可调用
ffmpeg,用于视频上传转码和预览生成
首次本地启动:
npm install
npm run db:up
npm run db:wait
npm run db:migrate
npm run dev打开:
日常本地开发也可以用一条命令启动 PostgreSQL、后端和前端:
npm run dev:local注意:dev:local 不会自动执行历史 SQLite 数据迁移。第一次接入旧数据时,仍需要先运行一次 npm run db:migrate。
| 命令 | 说明 |
|---|---|
npm run dev |
同时启动 Node 后端和 Vite 前端 |
npm run dev:local |
先启动本地 PostgreSQL,再启动前后端 |
npm run dev:frontend |
仅启动前端 |
npm run dev:backend |
仅启动后端 |
npm run build |
构建 React 前端 |
npm run start:backend |
以生产方式启动 Node 后端 |
npm run admin:reset-password -- <用户名> |
在服务器终端恢复管理员密码或创建恢复账号 |
npm run db:up |
启动 Docker PostgreSQL 18 |
npm run db:wait |
等待 PostgreSQL 可连接 |
npm run db:migrate |
从旧 SQLite 导入留言到 PostgreSQL |
npm run db:down |
停止 compose 服务 |
campuswall-react/
├── backend/ # Node/Express API 服务
│ ├── src/ # 后端源码
│ ├── scripts/ # PostgreSQL 等待和 SQLite 迁移脚本
│ ├── static/ # 运行时上传、缩略图和公告目录(数据不入 Git)
│ ├── help/ # 运行时帮助与举报目录(数据不入 Git)
│ └── package.json
├── frontend/ # React + Vite 前端
│ ├── public/ # favicon 和旧静态资源
│ ├── src/ # React 页面、组件、样式和 API 封装
│ └── package.json
├── compose.yml # 本地 PostgreSQL 18
└── package.json # 根目录统一脚本
- 默认站点名称为“校园墙”,可在部署时按需定制。
- 通用 favicon 位于
frontend/public/favicon.svg。 - 主色迁移自旧站 CSS:蓝色
#2A5CAA,橙色#FF7F3E。 - React 主应用主题变量集中在
frontend/src/styles.css;旧静态页面配色保留在frontend/public/static/css。
后端默认配置见 backend/.env.example。本地默认值可直接配合 compose.yml 使用;生产环境建议复制为 backend/.env 并至少修改:
SECRET_KEY=replace-with-a-long-random-secret
DATABASE_URL=
PGHOST=127.0.0.1
PGPORT=5432
PGDATABASE=campus_wall
PGUSER=campus_wall
PGPASSWORD=replace-with-your-production-db-password
PGSSL=false
ALLOWED_ORIGINS=https://your-domain.example
SESSION_COOKIE_SECURE=true
CAPTCHA_PROVIDER=none
CAPTCHA_ENABLED=false
CAPTCHA_SITE_KEY=
CAPTCHA_SECRET_KEY=
CAPTCHA_TIMEOUT_MS=8000
MAX_USER_IMPORT_ROWS=5000
MAX_AVATAR_SIZE=5242880
MAX_APP_ICON_SIZE=5242880如果你更喜欢一行连接串,也可以直接设置 DATABASE_URL,后端会优先使用它:
DATABASE_URL=<PostgreSQL connection URL>本地 compose 默认数据库参数为:
host=localhost port=5432 database=campus_wall user=campus_wall
- 运行时消息数据库是 PostgreSQL 18。
- 普通用户账号保存在 PostgreSQL
users表中,学号username唯一且不可修改,密码使用 Nodecrypto.scrypt加盐哈希保存。 - 登录用户发帖默认匿名;选择非匿名时,公开页面只展示发布时的昵称快照,不展示学号或真实姓名。
- 管理员可以在后台通过 Excel 导入账号、编辑资料、停用账号、禁言/解禁和重置密码。
backend/static/messages/messages.db只作为一次性迁移来源和备份保留,后端运行时不再读取 SQLite。messages.data使用 JSONB 保存原留言 JSON,评论、附件、标签、点赞和点踩等字段不拆表。- 留言状态使用
moderation_status=pending|visible|hidden|deleted与review_status=pending|approved。旧数据缺少状态字段时按“公开但待复核”兼容读取;旧manage_message.json审核列表首次启动时会迁入 PostgreSQL 并保留备份记录。 - 关闭发帖预审时,仍在
pending的留言会自动转为公开但待复核;开启预审不会追溯隐藏已有公开留言。 - 投票内容保存在消息 JSONB 中,投票身份记录保存在
poll_votes(message_id, voter_key),数据库唯一键负责拦截重复投票。 - 分区索引使用
partitions(tag, message_id),启动和迁移时会根据消息标签补齐。 - 上传文件保存在
backend/static/uploads。 - 缩略图保存在
backend/static/tiny_files。 - 应用广场数据保存在 PostgreSQL
apps表中,上传图标保存在backend/static/apps/icons。 - 公告、反馈工单、举报和旧管理员文本日志继续使用原 JSON 文件;结构化管理员审计记录保存在 PostgreSQL
admin_audit_events,反馈工单保留公开回复、内部备注和处理时间线。
部署和备份时需要保留:
- PostgreSQL 数据库,或本地 compose volume
campus_wall_postgres_data backend/static/uploadsbackend/static/tiny_filesbackend/static/apps/iconsbackend/static/notice.jsonbackend/help/*.jsonbackend/managers.json(管理员密码哈希、状态、权限和会话版本)backend/manage_message.jsonbackend/admin_log.json
这些目录和文件包含账号、用户内容或运行状态,已由 .gitignore 排除。公共仓库的干净克隆首次启动时会自动创建所需目录和空公告文件;首次使用管理后台前,请运行 npm run admin:reset-password -- <用户名> 创建管理员。生产备份应单独加密保存上述运行数据,不要提交到 Git。
前端仍通过 /api、/static、/health 访问后端。Vite 开发代理指向 http://localhost:5412。
主要公开接口:
GET /healthGET /api/get_messagesPOST /api/get_hot_messagesPOST /api/get_message_details/:idPOST /api/get_message_partitions/:idPOST /api/get_tagsPOST /api/get_partition_messagesPOST /api/noticePOST /api/appsGET /api/community/configPOST /api/wall/submitPOST /api/wall/like/:idPOST /api/wall/dislike/:idPOST /api/wall/comment/:idPOST /api/wall/poll/:id/votePOST /api/chunked_uploadPOST /api/merge_chunksPOST /api/direct_uploadPOST /api/help/formGET /api/help/status/:ticketIdGET /api/help/report/status/:reportIdPOST /api/help/report/:idPOST /api/help/report/:messageId/comment/:commentIdPOST /api/user/loginPOST /api/user/logoutGET /api/user/mePUT /api/user/me/profilePOST /api/user/me/avatarGET /api/user/me/messagesPUT /api/user/me/messages/:messageIdDELETE /api/user/me/messages/:messageIdGET /api/user/me/commentsDELETE /api/user/me/comments/:messageId/:commentIdGET /api/user/me/notificationsDELETE /api/user/me/notifications/:notificationIdDELETE /api/user/me/notificationsGET /api/user/:id/profileGET /api/user/:id/messagesGET /api/user/:id/avatarGET /user/:id/avatar
管理员接口保留 /api/admin/... 路径。登录后使用签名 admin_session cookie,不再把管理员密码写入浏览器存储。
管理员账号接口:
GET /api/admin/managersPOST /api/admin/managersPUT /api/admin/managers/:usernamePOST /api/admin/managers/:username/reset_passwordPOST /api/admin/managers/me/password
留言管理扩展接口:
POST /api/admin/messages/:id/moderation,JSON 字段支持pinned、featured、hidden和hidden_reasonPOST /api/admin/messages/:id/review,JSON 字段action支持approve和returnPOST /api/admin/messages/bulk-moderation,最多处理 100 条留言,action支持approve、return、hide和restoreGET /api/admin/api/messages,支持status=pending|approved|visible|hidden|awaiting_publication|all、搜索和分页GET /api/admin/comments,支持status=all|visible|hidden、搜索和分页POST /api/admin/comments/:messageId/:commentId/moderation,JSON 字段支持hidden和hidden_reasonPOST /api/admin/comments/bulk-moderation,最多处理 100 条评论,action支持hide和restore
内容回收站与审计接口:
GET /api/admin/trash,支持type=all|message|comment、搜索和分页POST /api/admin/trash/messages/:messageId/restoreDELETE /api/admin/trash/messages/:messageId,JSON 必须包含confirm: "PURGE"POST /api/admin/trash/comments/:messageId/:commentId/restoreDELETE /api/admin/trash/comments/:messageId/:commentId,JSON 必须包含confirm: "PURGE"POST /api/admin/trash/bulk,action支持restore和purge,最多处理 100 项GET /api/admin/audit,支持q、actor、action、target_type和分页
举报处置接口:
GET /api/admin/reportGET /api/admin/reports/historyPOST /api/admin/reports/:messageId/:reportId/resolve,JSON 字段action支持dismiss、delete_comment和delete_message,可选public_reply向举报人公开处理说明
反馈工单接口:
GET /api/admin/feedback,支持page、page_size、q、status和categoryPUT /api/admin/feedback/:ticketId,JSON 字段支持status、public_reply和internal_note
新增用户管理接口包括:
GET /api/admin/usersGET /api/admin/users/statsPOST /api/admin/users/importPUT /api/admin/users/:idPOST /api/admin/users/:id/mutePOST /api/admin/users/:id/unmutePOST /api/admin/users/:id/disablePOST /api/admin/users/:id/reset_password
新增应用广场管理接口包括:
GET /api/admin/appsGET /api/admin/apps/statsPOST /api/admin/appsPUT /api/admin/apps/:idPOST /api/admin/apps/:id/hidePOST /api/admin/apps/:id/restoreDELETE /api/admin/apps/:id
平台运营设置接口:
GET /api/admin/settings/communityPUT /api/admin/settings/community- 公开
GET /api/community/config不返回敏感词列表
普通用户登录后写入签名 user_session httpOnly cookie。CAPTCHA_PROVIDER=none 时后端跳过人机验证;登录页不向未登录用户展示验证码配置,后续接入 Turnstile/reCAPTCHA 时应由后台配置并扩展后端验证码适配层。
- React 页面使用路由级懒加载,首页不会一次性加载所有公开页和管理后台代码。
- 富文本清洗工具 DOMPurify 按需加载,只有渲染公告或应用 HTML 内容时才请求相关 chunk。
- 前端 API 层使用原生
fetch,避免把 Axios 打进首屏主包。 - Bootstrap Icons 只打包当前 React 页面实际使用的图标类,并只输出现代浏览器使用的
woff2字体。 - Umami 统计脚本不再写在 HTML 首屏里,生产环境会在页面渲染后空闲加载。
- Vite 开发环境如果出现依赖预构建缓存失效,可以按“开发排错”里的方法重建缓存。
- Express 启用
compression,压缩 JSON、文本等可压缩响应。 - 上传文件和缩略图使用 7 天
immutable缓存头,减少重复访问时的网络开销。 - 应用静态资源使用短缓存,公告 JSON 保持
no-cache,避免公告更新不及时。
如果浏览器空白,并在控制台看到类似:
GET /node_modules/.vite/deps/dompurify.js?... 504 (Outdated Optimize Dep)
这是 Vite 依赖预构建缓存过期,不是业务代码错误。处理方式:
# 先停止正在运行的前端 dev 服务
Remove-Item -Recurse -Force frontend/node_modules/.vite, frontend/node_modules/.vite-temp
npm --workspace frontend run dev -- --force如果后端也需要一起重新启动:
npm --workspace backend run dev重启后在浏览器按 Ctrl + F5 强制刷新,或打开带时间戳的新地址,例如 http://localhost:5173/?fresh=1。
- 生产环境必须修改
SECRET_KEY和默认管理员密码。 - 首次启动会把旧
managers.json中的明文密码自动迁移为 scrypt 哈希;之后请在/admin/managers修改密码。无法登录时可运行npm run admin:reset-password -- <用户名>恢复。 - 生产环境请配置
ALLOWED_ORIGINS,避免跨站请求写入。 - HTTPS 部署时设置
SESSION_COOKIE_SECURE=true。 CAPTCHA_PROVIDER=none为默认关闭状态。可在管理后台“平台设置”中启用 Cloudflare Turnstile 或 Google reCAPTCHA,也可使用环境变量作为初始配置。- 验证码服务端密钥使用
SECRET_KEY派生密钥加密后存入 PostgreSQL;更换SECRET_KEY前应先关闭或重新配置验证码。 - 静态文件、上传文件和缩略图接口会限制在后端
static目录内,避免路径穿越。 - 上传大小、文本长度、标签数量、评论附件数量等限制都在
backend/.env.example中可配置。 RATE_LIMIT_LOGIN、RATE_LIMIT_WRITE、RATE_LIMIT_INTERACTION、RATE_LIMIT_UPLOAD、RATE_LIMIT_FEEDBACK可分别调整各类接口限额。- 视频处理依赖外部
ffmpeg,服务端没有ffmpeg时视频转码和预览会失败。 - 不建议把
backend/.env、生产管理员账号文件或生产日志提交到仓库。
常用检查:
npm run build
npm --workspace backend run check
npm audit --omit=dev --registry=https://registry.npmjs.org功能回归重点:
- 首页公告和热门留言加载
- 墙页搜索、筛选、加载更多、发布、上传、互斥点赞/点踩、评论、投票及结果恢复
- 预审开关关闭时新留言立即公开但进入待复核队列;开启时新留言和编辑留言仅作者与管理员可见
- 管理员单条/批量通过、退回、下架、恢复,状态变化通知作者,待审核内容不会泄漏到列表、详情、分区、热门、收藏或互动接口
- 评论单条/批量下架与恢复、作者通知、隐藏引用脱敏、公开评论计数和热度过滤
- 作者自删、后台删除和举报删除进入回收站;恢复后状态还原,彻底删除仅接受回收站内容,附件引用在最终清理前保留
- 操作审计可按管理员和对象检索,失败请求和普通读取不会写入审计记录
- 管理员置顶/取消置顶、精华/取消精华、下架/恢复及前台过滤排序
- 留言详情、分区页、应用页、帮助、留言/评论举报、追踪码查询和后台公开处理说明流程
- 帮助反馈提交、追踪码查询、后台公开回复、内部备注、状态流转和结单
- 管理后台应用新增、编辑、下架、恢复和彻底删除
- 社区公约展示、全局/游客互动开关、暂停说明和敏感词后端拦截
- 普通用户 Excel 导入、登录、退出、修改头像/昵称/个人简介/性别
- 登录用户默认匿名发帖,关闭匿名时公开展示昵称
- 禁言用户不能发帖和评论,游客发帖仍可用
- 用户页只展示非匿名公开留言,不暴露学号
- 管理员登录、校验、退出、改密、账号停用、权限分配、旧会话失效、仪表盘统计、审核、回收站、操作审计、举报上下文、处置与历史检索、日志、公告、用户和验证码配置管理