从功能开发到服务器上线:校园学习协作平台项目复盘

关键词:Vue 3、TypeScript、Rust、Axum、MySQL、Redis、Docker、Caddy、HTTPS、全栈项目、部署排障

一、项目背景:为什么做这个平台

这个项目最初来自一个很直接的需求:在学院课程学习和项目实践中,信息经常散落在聊天群、网盘、个人文档和口头经验里。学生想了解一门课的难度、工作量、资料质量,或者想找同学一起做课程项目、比赛项目,往往需要反复询问。信息没有沉淀下来,后续同学也很难复用。

因此我做了一个“软件工程学院学习协作平台”,希望把课程经验、学习资料和项目招募统一放到一个系统中。它不是一个单纯的 CRUD 练习,而是一次从需求拆解、前后端开发、数据库建模、权限控制,到 Docker 部署、域名 HTTPS 配置和线上问题排查的完整工程实践。

从就业展示角度看,这个项目的重点不只是“页面能不能点”,而是我在项目中完整经历了一个 Web 应用从本地开发到公网可访问的过程,并解决了多个真实部署问题。

二、项目功能概览

平台主要分为用户端和管理端。

用户端包含课程列表、课程详情、课程评价、学习资料、项目招募和个人中心。用户可以按课程名称、课程代码、教师搜索课程,也可以对课程进行评分和评价。学习资料支持两种形式:文件资料和外部链接资料。项目招募模块支持用户发布项目、申请加入项目,发布者可以在个人中心处理收到的申请。

管理端包含后台首页、课程管理、内容管理、用户管理和项目管理。管理员可以查看系统统计数据,管理课程,搜索和处理学习资料,禁用或恢复用户,隐藏或关闭违规项目。后台还支持今日活跃人数和每日活跃情况统计,用来观察系统使用情况。

项目中还加入了一些更贴近真实业务的规则,例如:

  • 每个用户最多保留 5 个未关闭、未隐藏的招募。
  • 用户上传的文件资料总大小限制为 1 GiB。
  • 单个文件大小通过 MAX_UPLOAD_MB 配置。
  • 上传资料后立即展示,后台后续可以隐藏或删除。
  • 点赞可以取消,经验值会随点赞和取消点赞同步变化。
  • 管理员权限在后端校验,不能只依赖前端隐藏按钮。

这些规则让项目从简单的数据展示,逐步接近真实系统中的状态管理和权限边界问题。

三、技术选型与架构设计

前端使用 Vue 3、Vite、TypeScript、Pinia、Vue Router 和 Element Plus。Vue 3 适合快速构建单页应用,Element Plus 提供了表格、表单、分页、弹窗等常用组件,能快速搭建课程列表、后台管理、个人中心等页面。

后端使用 Rust、Axum、Tokio、SQLx、JWT、Redis 和 MySQL。选择 Rust + Axum 的原因是希望在项目中实践更强的类型约束和更清晰的错误处理。SQLx 用来访问 MySQL,Redis 用于邮箱验证码和发送频率限制,JWT 用于无状态登录认证。

生产环境通过 Docker Compose 编排四个核心服务:

  • MySQL:保存用户、课程、评价、资料、项目、申请等数据。
  • Redis:保存验证码和缓存类数据。
  • Rust 后端:提供 REST API。
  • 前端 Nginx:托管静态文件,并将 /api 请求转发到后端。

最终线上访问链路是:

1
2
3
4
5
浏览器
-> Caddy,负责 HTTPS 和证书
-> 127.0.0.1:8081,前端 Nginx
-> 127.0.0.1:8080,Rust 后端
-> MySQL / Redis

这个结构的好处是:前端、后端、数据库、缓存都可以容器化管理,对外只暴露 80 和 443,数据库和 Redis 只监听本机端口,安全边界更清晰。

四、从本地开发到线上部署

本地开发时,我使用 Docker 启动 MySQL 和 Redis,后端通过 cargo run --release 启动,前端通过 npm run dev 启动。这样调试速度较快,也方便查看后端日志和数据库状态。

上线时,我将项目推送到 GitHub,然后在阿里云 ECS 上拉取代码,使用 docker-compose.prod.yml 进行生产部署:

1
docker compose -f docker-compose.prod.yml up -d --build

部署完成后,通过下面命令检查服务状态:

1
2
docker compose -f docker-compose.prod.yml ps
docker logs --tail 100 campus-backend

HTTPS 使用 Caddy。域名解析到服务器公网 IP 后,Caddy 自动申请证书。最终采用 host network 模式运行 Caddy,反向代理到前端服务:

1
2
3
4
campus.chenh735blog.top {
encode zstd gzip
reverse_proxy 127.0.0.1:8081
}

最终验证命令:

1
curl -i https://campus.chenh735blog.top/api/courses?page_size=10

当返回 HTTP/2 200 时,说明 HTTPS、Caddy、前端 Nginx、后端 API 这一整条链路已经打通。

五、部署过程中遇到的问题与解决

1. Rust 后端构建版本不兼容

第一次在 Docker 中构建后端时,遇到了 Rust 依赖要求更高编译器版本的问题。错误信息指向部分依赖需要更新版本的 rustc

这个问题让我意识到:Dockerfile 里的基础镜像版本不是随便选的,后端依赖升级后,基础镜像也要匹配。最终我将构建镜像调整为较新的稳定 Rust 镜像,解决了 MSRV 不匹配的问题。

2. 服务器配置太低导致编译卡死

最开始使用 2 核 2 GiB 的服务器构建 Rust release 包,构建过程非常慢,甚至会导致 SSH 和 VS Code Remote 连接不稳定。

排查后发现主要是内存不足。Rust release 编译本身比较吃内存,Docker 构建、Node 构建和系统进程叠加后,服务器很容易卡住。

解决方式有两个:

  • 临时增加 swap,缓解内存不足。
  • 将服务器升级到 2 核 4 GiB。

同时我使用 tmux 保持构建任务,即使 SSH 断开,服务器上的构建进程也能继续运行。

3. Docker 和 Cargo 依赖下载慢

服务器上拉取 Docker 镜像和下载 Rust crates 依赖时,经常出现速度慢或超时。

这个问题的解决思路是减少不必要构建,并配置更稳定的镜像源。比如只改前端时,不再重新构建后端,而是使用:

1
2
docker compose -f docker-compose.prod.yml build --no-deps frontend
docker compose -f docker-compose.prod.yml up -d --no-deps frontend

这让我对 Docker 构建缓存、服务依赖和增量部署有了更直接的理解。

4. SMTP 邮箱验证码发送失败

注册功能依赖邮箱验证码。部署后我遇到过验证码发送失败,日志里出现连接失败或网络不可达。

排查过程中,我分别检查了 SMTP 配置、服务器到 SMTP 服务的连通性、容器 DNS 解析和网络模式。最后发现容器默认网络环境下访问外部 SMTP 服务不够稳定,于是将后端容器改成 host network,让后端直接使用宿主机网络访问 SMTP。

这个问题让我意识到:线上问题不能只看代码,还要看网络、DNS、端口、安全策略和第三方服务。

5. Caddy 反向代理 502

上线 HTTPS 后,前端和后端本地访问都正常,但通过域名访问 API 返回 502。

我的排查顺序是:

1
2
3
curl -i http://127.0.0.1:8080/api/courses?page_size=10
curl -i http://127.0.0.1:8081/api/courses?page_size=10
curl -i https://campus.chenh735blog.top/api/courses?page_size=10

前两个返回 200,域名返回 502,说明问题不在业务代码,而在最外层 Caddy 代理。

根因是 Caddy 运行在容器网络中,访问不到宿主机绑定的 127.0.0.1:8081。最终我让 Caddy 使用 host network,并代理到 127.0.0.1:8081,问题解决。

这次经历让我更加理解“容器里的 localhost”和“宿主机 localhost”不是同一个东西。

6. favicon 缓存问题

我给网站添加图标时,发现文件已经更新,但浏览器还是显示旧图标。后来发现 favicon 的缓存非常顽固。

解决方式是给 favicon 地址加版本号,例如:

1
<link rel="icon" type="image/svg+xml" href="/site-icon.svg?v=1" />

再通过无痕窗口或强制刷新验证。这是一个小问题,但也提醒我:前端上线后,缓存策略同样会影响用户看到的效果。

六、我如何定位线上问题

这次部署让我形成了一个比较清晰的排障顺序:

  1. 先看容器状态:docker compose ps
  2. 再看后端日志:docker logs --tail 100 campus-backend
  3. 从内到外测试接口:后端端口、前端代理、HTTPS 域名
  4. 如果本地端口正常但域名失败,优先查 Caddy 或 Nginx
  5. 如果服务刚重启,先确认 MySQL 和 Redis 是否 healthy
  6. 如果构建卡住,检查内存、swap、CPU 和网络下载情况

比起直接猜问题,我更倾向于用 curl、日志和容器状态逐层缩小范围。这个习惯在实际开发和运维中非常重要。

七、测试与验收

部署完成后,我主要验证了以下功能:

  • 注册登录、邮箱验证码、管理员登录。
  • 课程搜索、分页、分类筛选和详情页。
  • 课程评价发布、点赞、取消点赞。
  • 文件资料上传、链接资料上传、资料下载。
  • 项目招募发布、申请加入、发布者处理申请。
  • 后台用户管理、内容管理、课程管理、项目管理。
  • 今日活跃人数统计。
  • HTTPS 域名访问。
  • Docker 重启后数据不丢失。

这些测试覆盖了普通用户、管理员、游客三类角色,也覆盖了前端、后端、数据库、缓存、文件上传和 HTTPS 访问链路。

八、项目收获

这个项目让我对“完成一个能上线的 Web 应用”有了更完整的认识。

在功能开发上,我学习了如何把需求拆成课程、资料、项目、用户、后台管理等模块,并处理不同角色之间的权限边界。

在后端开发上,我实践了 Rust Axum 的接口组织、SQLx 数据库访问、JWT 鉴权、Redis 验证码缓存、文件上传和错误处理。

在前端开发上,我使用 Vue 3 和 Element Plus 搭建了较完整的用户端和管理端页面,并处理分页、搜索、表单、弹窗、状态同步等交互。

在部署运维上,我真正经历了 Docker 构建、服务器资源不足、SMTP 网络问题、域名解析、HTTPS 证书、Caddy 502 等问题。这些经历让我意识到,工程能力不只是在本地把功能写出来,还包括把系统稳定地跑在服务器上,并能在出问题时定位原因。

九、后续优化方向

如果继续迭代,我会优先做这些事情:

  • 增加自动化测试,覆盖核心接口和权限边界。
  • 接入 CI/CD,让 GitHub 推送后自动构建和部署。
  • 将上传文件迁移到对象存储,减轻服务器磁盘压力。
  • 增加后台监控和错误告警。
  • 优化前端打包体积和首屏加载速度。
  • 增加更完整的搜索能力,例如按标签、课程学期、资料类型检索。
  • 完善审计日志,记录更多后台关键操作。

总体来说,这个项目是一次从“会写功能”到“能部署、能排障、能复盘”的完整练习。它让我更清楚地认识到,一个就业向项目最有价值的地方,不只是技术栈列表,而是背后解决问题的过程。