这个项目是怎么搭起来的——架构、设计与新人上手指南

前言

这篇文章想做成一张地图:任何一个刚接触这个项目的初级开发,读完它之后应该能回答三个问题——代码放在哪、一个请求怎么从浏览器走到数据库、怎么把它在自己电脑上跑起来。

这个项目是一个个人全栈项目:一套 Spring Boot 后端,同时支撑管理后台、博客和主站三个前端,跑在家里的一台小服务器上,通过 frp 穿透到公网。它不大,但“五脏俱全”:登录权限、文件存储、定时任务、数据备份、AI 工具调用,你能在中小公司项目里见到的典型组件,这里都有。

不需要任何预备知识,遇到术语都会用大白话解释。如果你是完全的新人,建议从第九章「新人上手指南」开始动手,遇到不懂的概念再回到前面查。

目录: 一、先看一眼全景 二、技术栈与选型理由 三、代码结构导览 四、一个请求的生命周期 五、核心设计 六、MCP:让 AI 帮你写博客 七、部署与运维 八、未来规划 九、新人上手指南 结语

一、先看一眼全景

先建立第一印象:一套后端,多个前端,一个家。

flowchart TB user["访客浏览器"] cloud["腾讯云入口<br/>frps + nginx :80"] subgraph stack ["家里服务器 192.168.1.2 · docker compose"] gw["网关 nginx :8088<br/>按子域名分发"] app["Spring Boot 后端<br/>只听 127.0.0.1:8081"] mysql["MySQL 8.4"] redis["Redis"] blog["Astro 博客站 SSR"] nas["NAS 7.3T<br/>附件与备份"] end user -->|"http://62.234.67.14"| cloud cloud -->|"frp 隧道"| gw gw -->|"/prod-api/ 剥前缀"| app gw --> blog app --> mysql app --> redis app -->|"附件读写"| nas

三个关键词帮你记住它:

  1. 多入口,单后端:管理后台、博客、将来的主站,都是静态页面或 SSR 应用,共用同一套 /prod-api/ 接口。加一个新站点,只需要在网关加一段 nginx 配置,不用动后端。
  2. 家是机房:所有服务跑在家里一台 4 核 N6005、15G 内存的小机器上,用 Docker Compose 管理。没有 Kubernetes——对个人项目来说那是纯负担。
  3. 云只是门房:腾讯云那台服务器不做业务计算,只负责收公网请求,通过 frp 隧道转回家里。便宜,且家里 IP 变了也不怕。

二、技术栈与选型理由

新人最容易犯的错是"追新栈"。这个项目的选型逻辑很简单:主流、成熟、出问题搜得到答案。

层 技术 一句话理由
后端框架 Spring Boot 4 + Java 21 国内公司主流,生态最大,record/密封类这些新特性真的省代码
数据库 MySQL 8.4 关系型标配,Docker 官方镜像一把起
缓存 Redis 登录态、图形验证码、限流计数,都靠它
管理后台前端 Vue 3 + Element Plus 中后台经典组合,表单表格开箱即用
博客站 Astro(SSR 模式) 内容站要快、要 SEO,默认零 JS 输出正合适
部署 Docker Compose 单机五容器,compose up -d 一条命令拉起全部
网关 Nginx 静态资源、反向代理、按子域名分发,一个全能选手
公网入口 frp 家里没有公网 IP,内网穿透是标准解法
数据库迁移 Flyway 表结构变更写进 SQL 文件,跟代码一起版本化

还有两个"看不见但重要"的选择:

  • JSON 库用 Jackson 3(tools.jackson 包路径,不是老的 com.fasterxml)。Spring Boot 4 已经切换到 Jackson 3,跟着框架走最省心。
  • 代码风格继承 RuoYi:Controller 返回 TableDataInfo/AjaxResult,Service 接口 + Impl 两层,@PreAuthorize 管权限。熟悉这套的人看代码会像回家。

三、代码结构导览

打开仓库,先看 backend/——它是 Maven 多模块,一个模块一件事情:

backend/
├── haichao-common/     公共地基:工具类、异常体系、通用领域对象
├── haichao-framework/  框架层:安全配置、拦截器、端口抽象、配置属性
├── haichao-system/     系统模块:用户、角色、菜单、字典、岗位
├── haichao-quartz/     定时任务:博客定时发布、附件清理都在这
├── haichao-generator/  代码生成器:建好表一键生成 CRUD 代码
├── haichao-blog/       博客业务:文章、分类、标签、附件、MCP 工具
├── haichao-admin/      启动模块:组装一切 + 全部 Controller
└── pom.xml             父 POM:统一管依赖版本

依赖方向是严格的,新人一定要建立这个意识:

haichao-admin ──▶ 各业务模块 ──▶ haichao-framework ──▶ haichao-common
  • common 不依赖任何模块,它是地基;
  • framework 只依赖 common,提供跨业务的基础设施;
  • system、blog 这些业务模块平级,互相不依赖;
  • admin 是组装车间,把所有人拉起来启动。

违反这条线会怎样? 比如 blog 模块如果直接 import system 的类,将来想拆服务就拆不出来了。项目里为此还引入了 port/ 目录做接口抽象(UserAuthPort、PermissionPort):业务模块需要"查用户"时面向接口编程,实现在 admin 侧注入。

推荐的新人阅读顺序:

  1. 仓库根目录的 CLAUDE.md(项目宪法,约定都在里面)
  2. haichao-framework 的 SecurityConfig.java——理解整个系统的门卫
  3. BlogPostController.java 从头读到尾——一个标准 Controller 的全貌
  4. 顺着它进 BlogPostServiceImpl——业务逻辑长什么样

四、一个请求的生命周期

架构不是背出来的,是跟出来的。我们跟着"发布一篇博客"这个请求走一遍全链路:

sequenceDiagram participant B as 浏览器 participant N as 网关 nginx participant S as Spring Security participant C as Controller participant V as Service participant M as Mapper participant D as MySQL B->>N: POST /prod-api/blog/admin/posts N->>S: 剥掉 /prod-api 前缀,转给 8081 S->>S: 校验 JWT 与权限串 blog:post:add S->>C: 放行 C->>V: BlogPostBo(参数已校验) V->>M: insertPost + 关联标签 M->>D: INSERT blog_post / blog_post_tag D-->>B: 200 + 新文章 ID

逐步解释每一站在干嘛,以及为什么需要它:

  1. 网关 Nginx:请求从公网进来先到它。它做三件事:终结 TLS(如有)、把 /prod-api/ 前缀剥掉转给后端、按域名决定这是哪个站点。后端只听 127.0.0.1:8081——外界根本摸不到它,这是第一道物理隔离。
  2. Spring Security:系统的门卫。它检查请求头里的 JWT 令牌,解析出"你是谁、你有什么权限",再比对方法上的 @PreAuthorize("hasPermi('blog:post:add')")。不满足?403,请求到此为止。
  3. Controller:只做三件事——收参数、校验参数、调 Service。它不应该出现业务判断,那是 Service 的职责。
  4. Service:真正的业务规则都住在这,而且标了 @Transactional:插文章、插标签关联,要么都成功,要么都回滚。半篇文章存进数据库是最难查的 bug。
  5. Mapper:MyBatis 的 SQL 映射,唯一允许说 SQL 的地方。
  6. MySQL:数据落库。

记住这条链,你就理解了 90% 的 Web 项目。这个项目的特别之处只在于:链条外面包了一层 Nginx 多站点分发和一层 MCP 协议适配(见第六章)。

五、核心设计

5.1 登录与权限:RBAC

权限模型是经典五张表:用户 → 用户角色关联 → 角色 → 角色菜单关联 → 菜单/权限。落到代码上是一行注解:

@PreAuthorize("hasPermi('blog:post:add')")
@PostMapping
public AjaxResult add(@RequestBody BlogPostBo bo) { ... }

登录态用 JWT + Redis 组合:JWT 令牌自带身份信息(无状态,网关层不查库),Redis 存"这个令牌还有效吗"(可失效、可踢人下线)。两者互补,比纯任一面都可靠。

新人对权限体系只需记住:权限是一串字符串(如 blog:post:add),数据库里配置给角色,注解里声明给接口。加新功能时先去菜单管理里加权限数据,再写注解。

5.2 博客模块:两扇门

博客模块的设计哲学是公开读和管理写彻底分离:

  • /blog/pub/**——匿名可读。文章列表、详情、附件,放行在安全配置里,但只读。
  • /blog/admin/**——必须登录 + 权限。写操作全在这里。

这个边界换来两个好性质:公开面可以被 CDN 缓存、被搜索引擎随便爬;写面可以收紧到最小权限。附件寻址也用了这个思路——公开附件用 UUID 文件名而不是自增 ID,防止别人遍历 id=1,2,3... 把草稿和已删除文章的附件全扒走。

还有一个实战踩出来的坑值得新人知道:MySQL 容器必须显式设 TZ: Asia/Shanghai。镜像默认 UTC,和 JVM 差 8 小时,所有用 now() 比较时间的逻辑(比如"定时发布时间到了就发布")都会静默失效——不报错,就是不准。这类问题Debug起来最费劲,所以写在 CLAUDE.md 里。

5.3 配置外置:代码里零秘密

数据库密码、JWT 密钥、令牌,全部走环境变量,集中在 deploy/setenv.env(gitignored)。代码和配置彻底分离,意味着:仓库可以公开,服务器上改密码不用重新打包。所有项目自有配置统一 HAICHAO_ 前缀,一眼可辨。

六、MCP:让 AI 帮你写博客

这是最新加入的能力,也是本文的发布方式——这篇文章就是通过 MCP 工具创建的。

MCP(Model Context Protocol)是 Anthropic 主导的开放协议,让 AI 客户端(Claude Desktop、各类 Agent)能调用外部系统的工具。可以把它理解成"AI 世界的 USB 接口":以前 AI 只能聊天,现在它能查你的数据库、建你的工单、发你的文章。

我们把博客能力包成了 9 个 MCP 工具:

工具 作用
blog_search_posts 关键词检索文章(只回摘要,省上下文)
blog_get_post 按 slug/ID 读全文
blog_list_posts / blog_list_categories / blog_list_tags / blog_list_attachments 盘点家底,拿到写文章要用的 ID
blog_create_post 新建文章(默认草稿)
blog_update_post 局部更新(只改显式传入的字段)
blog_set_post_status 发布/转草稿/下线——高危动作,独立权限

设计上坚持了三个原则:

  1. 不另起炉灶:MCP 客户端用 Bearer 令牌认证,令牌映射到一个真实账号,然后走系统原有的 RBAC。没有"MCP 专属权限体系"这种东西要维护。
  2. 默认关闭:haichao.mcp.enabled=false 时所有相关 Bean 都不装配,端点不存在。用 HAICHAO_MCP_TOKEN 环境变量注入,空令牌条目惰性不可匹配(fail-safe)。
  3. 写操作防误伤:创建默认进草稿箱;发布是单独的高危工具;局部更新用 merge 语义——数组字段不传表示"沿用现有的",传空数组才表示"清空"。AI 再聪明也可能误解意图,接口层要把错误关在笼子里。

对个人使用的价值场景:写新文章时让 AI 先检索自己的旧文做内链推荐——这是只有在你自己的博客上才成立的功能,别处买不到。

七、部署与运维

本地开发用 docker compose up -d,生产在 192.168.1.2 上同样一套。五个容器:

mysql(数据)  redis(缓存)  app(后端)  nginx(80 端口,admin 站点)  blog(Astro SSR)

运维上已经固定下来的几件事,新人接手前必须知道:

  • 发版流程:本地改代码 → 传服务器(tar + scp,因为服务器不是 git 仓库)→ docker compose build app → up -d → 验证。改 nginx 配置要 restart 而不是 reload——宿主机覆盖文件会换 inode,reload 读不到新配置(这个坑踩过)。
  • 备份:每天凌晨 3:30,mysqldump 单事务快照压缩后存到 NAS,保留 14 份。备份任务本身也纳入了监控——正在做的数据看板会把"昨天备份成功了吗"放到第一屏。
  • NAS 挂载:附件和备份落在 NAS(CIFS),systemd 管开机自挂。NAS 离线时 Docker 栈不受影响,但附件会静默写进本地空目录——排查附件丢失先查挂载状态。

公网链路回顾一下:访客 → 腾讯云 80 → frps 隧道 → 家里网关 8088 → 后端/各前端。新增子站点只改网关 nginx,frp、云上、DNS 全不动——这是刻意的设计,入口永远只有一个。

八、未来规划

规划不是愿望清单,每一条都基于"已有的能力刚好能复用":

  1. 数字花园:文章间加 [[双链]],发布时解析成关系表,文章页显示"引用本文的其他文章",外加一个力导向关系图谱和一个"随便看看"随机漫游按钮。博客会从"时间线存档"变成"会长东西的网"。工程量一个周末。
  2. 数据看板:一页看全博客阅读量、服务器 CPU/磁盘、备份是否成功、frp 隧道是否存活。个人项目最怕"死了没人知道"。
  3. AI 摘要周报:周日自动汇总本周文章变动,AI 写成摘要邮件发给自己。SMTP 迟早要配,一发多收。
  4. 评论/留言板:先上 twikoo(半天),想练手再自己写,正好复用现有的敏感词过滤和审核队列思路。
  5. 博客与相册打通:NAS 上跑 Immich,家里和公网都能看照片,主站加 /photos 页。7.3T 空间闲着也是闲着。

明确不做的:自建网盘(Immich 已覆盖,再套一层是纯维护负担)、自建邮件服务器(IP 无反解,发信进垃圾箱,收信还要常年开 25 端口)。

九、新人上手指南

9.1 本地跑起来

前置条件:JDK 21、Maven 3.9+、Node 20+、Docker(跑 MySQL 和 Redis 最省事)。

# 1. 起依赖(MySQL + Redis)
docker compose up -d mysql redis

# 2. 建库并导入种子数据(Flyway 会自动建表)
#    在 mysql 容器里执行 backend/sql/ 下的脚本

# 3. 启动后端(IDEA 直接跑 HaichaoApplication 也行)
mvn spring-boot:run -pl haichao-admin -am

# 4. 启动管理后台前端
cd frontend && npm install && npm run dev

# 5. 启动博客站(Astro)
cd blog && npm install && npm run dev

后端默认 8081 端口,前端 dev server 会自动代理 /prod-api/。默认账号 admin,密码见种子数据(生产环境已改,本地无所谓)。

9.2 写代码前必须知道的五条家规

  1. 分层不越界:Controller 不写业务,Service 不碰 HTTP,Mapper 不说业务话。依赖方向只能 admin → 业务 → framework → common。
  2. 异常走两条线:ServiceException(业务异常)和 BaseException(用户异常)各有归宿,全局异常处理器会转成对应文案。不要随便 throw new RuntimeException。
  3. CSS 网格单列退化必须写 minmax(0, 1fr):裸 1fr 会被内容最小宽度顶爆,手机上整页变桌面缩略图——这个坑的修复记录在 blog/global.css 里。
  4. 测试要分层:纯逻辑写 Mockito 单测;要真数据库的加 @Tag("integration")(CI 会排除,本地没库也能编译通过)。
  5. 提交前看 CLAUDE.md:所有踩过的坑都沉淀在里面,它比任何人口头传授都可靠。

9.3 推荐第一个练手任务

去给博客文章加一个"预计阅读时长"字段:数据库加列(Flyway V7 迁移脚本)→ BO/VO 加字段 → Service 计算(正文按 400 字/分钟)→ 列表接口返回 → 前端显示。改动小,但完整走一遍"迁移→后端→前端→发布"全流程,比读三天代码都管用。

结语

这个项目最值得学的不是任何单个技术点,而是它的组织方式:一套后端支撑多个前端、秘密全部外置、运维动作脚本化、每个坑都有文字沉淀。这些习惯比框架版本重要得多——框架会过时,习惯不会。

下一步,去把第九章的练手任务做掉。遇到问题?先查 CLAUDE.md,再查 docs/ 目录,最后来问。欢迎上车。