FlyCms
一套前后端分离的开源内容建站系统:后台是 Vue 3 单页控制台,前台是 Freemarker 模板站。
核心技术 = 自定义模型引擎:文章、图片、下载、问答、链接……系统的一切内容模块都由后台在线建模生成(建模 → 自动建表 → 字段配置 → 模板渲染),不做第二套硬编码实现。目标是万能系统——用户只靠后台设置,就能生成任何想要的程序模块。历史上硬编码的内容模块正在按规划逐个退役并迁入模型引擎(见 docs/01-规划/主流CMS对标与全项目优化开发方案.md §9.4 阶段 U)。
后端 346 个 Java 文件 / 25 个业务模块 / 21 个 REST 控制器
前端 Vue 3 + TypeScript + Ant Design Vue,73 个页面组件
内容生产:fly_model(模型注册)→ fly_model_field(字段元数据)→ fly_cmodel_{code}(动态数据表)→ 通用管理页 + cmodel 模板通道
一、它现在是什么
FlyCms 最早是一个仿知乎的问答社区程序。经过持续重构,现在的内核是一套五面分离的 CMS 底座:
| 面 |
解决什么 |
对应能力 |
| 内容面 |
内容从哪来、长什么样 |
自定义模型引擎(唯一内容生产方式):建模 + 字段(含关联引用 relate/relates、URL 型图片 image_url/file_url、附件、地区等 19 种类型)+ 动态建表 + 通用数据管理;存量硬编码模块(文章/问答/分享/话题)退役中,评论/收藏收敛为平台能力 |
| 结构面 |
内容挂在哪、URL 怎么走 |
统一栏目树(列表 / 单页 / 外链 / 聚合,聚合可跨模型混排) |
| 呈现面 |
页面怎么渲染 |
模板中心(在线编辑 / 版本快照 / 一键回滚 / 皮肤导入导出)、碎片位与推荐位、模板标签手册 |
| 交互面 |
访客怎么参与 |
表单系统(可视化配字段)、站内信、公告、收藏、关注与 Feed、WebSocket 推送 |
| 运营面 |
站点怎么管 |
SEO(sitemap / robots)、投稿与评论审核、敏感词过滤、审计日志、定时任务、菜单 / 角色 / 权限 |
二、技术栈
后端
| 组件 |
版本 / 选型 |
说明 |
| 运行时 |
JDK 24 |
maven-compiler-plugin 开启 proc:full 以支持 Lombok |
| 框架 |
Spring Boot 4.1.1 |
Jakarta 命名空间 |
| ORM |
MyBatis(mybatis-spring-boot-starter 4.1.0) |
XML 与 Mapper 同目录打包 |
| 模板 |
Freemarker |
前台站点渲染,开启 ALLOWS_NOTHING_RESOLVER 沙箱 |
| 缓存 |
Caffeine |
已替代原 EhCache 2 |
| 连接池 |
Druid 1.2.28 |
Boot 4 下由 DruidConfig 手动装配 |
| 调度 |
Quartz 2.3.2 |
后台可启停任务、查看执行日志 |
| 数据库 |
MySQL 8(utf8mb4) |
SQL 仅适配 MySQL |
| 检索 |
Lucene 7.4 |
当前为技术中立接缝:原 Solr 已移除,SearchService 保留空实现,规划接入 Elasticsearch |
| 其它 |
Hutool 5.8、jsoup 1.17、BCrypt、Log4j2、阿里云短信 SDK |
验证码、XSS 清洗、密码散列 |
前端
| 组件 |
版本 |
| Vue |
3.5.41 |
| TypeScript |
6.0 |
| Vite |
8.3 |
| Ant Design Vue |
4.2 |
| 状态 / 路由 |
Pinia 4、Vue Router 5 |
| 基座 |
Vue Vben Admin 5(@vben/* 内部包) |
| 工程 |
pnpm 11 workspace + turbo,Node ^22.18 || ^24.12 |
后台是 SPA(apps/web-antd),通过 /api/** 与后端通信;前台仍是服务端渲染的 Freemarker 模板站,两者互不干扰。
三、功能清单
前台
- 栏目:列表 / 单页 / 外链 / 聚合四种形态,目录名即 URL,支持无限层级与移动改挂
- 内容模型:后台建模后自动生成数据表,前台按模型
code 路由,列表自带分页条
- 问答:发布问题、悬赏积分邀请回答、答案列表与统计
- 文章:分类无限级、话题聚合、评论、顶踩、收藏
- 分享:分享资源地址赚取积分,支持积分兑换
- 表单:
<@fly_form code="..."> 输出表单,提交走 POST /api/form/submit/{code}
- 用户:注册(手机 / 邮箱)、登录验证码与失败次数限制、密码找回、手机 / 邮箱绑定、邀请注册奖励、积分充值
- 互动:关注、粉丝、Feed 动态、收藏、站内信、WebSocket 实时推送
- SEO:
/sitemap.xml、/robots.txt 由配置键 fly_sitemap_status / fly_sitemap_limit / fly_robots 控制
后台(Vue 控制台)
| 分组 |
功能 |
| 系统管理 |
管理员、角色组、菜单权限(后端动态菜单)、站内信、公告、审计日志、附件库、导航、友情链接 |
| 内容 |
内容模型(模型 / 字段 / 数据 CRUD)、栏目、文章、评论审核、投稿审核(先审后发开关 + 批量通过 / 驳回) |
| 呈现 |
模板中心(文件树编辑、版本快照与回滚、皮肤导入导出、标签手册)、碎片位 / 推荐位 |
| 交互 |
表单管理(表单 + 字段 + 数据 + CSV 导出) |
| 运营 |
网站配置、定时任务、积分规则、订单、敏感词过滤、用户管理 |
| 统计 |
仪表盘 |
平台能力
- 动态菜单与权限:菜单、角色、权限点全部数据驱动,
actionKey 同时用于前端按钮显隐(accessCodes)和后端 403 兜底
- 模板版本化:每次保存自动快照,改坏模板可一键回滚
- 定时发布:
TimingPublishJob + publish_time 字段,status=4 为待定时发布
- 审计日志:
AdminLogInterceptor 记录后台操作,含敏感字段脱敏
- 定时任务:后台启停、Cron 配置、执行日志
四、安全设计
| 风险 |
处理 |
| 模板 RCE |
Freemarker TemplateClassResolver.ALLOWS_NOTHING_RESOLVER 沙箱 |
| 上传 getshell |
UploadSafeUtil.safeImage:扩展名 + MIME + 文件头魔数三重白名单,强制重命名丢弃原始文件名 |
| SQL 注入 |
ORDER BY 走 OrderbyUtils.check 白名单;动态建表 / 列名走 SqlSafeUtil.safeColumnName / safeNumber |
| XSS |
提交内容用 jsoup Safelist.none() 清洗(表单模块),页面输出 HTML 转义 |
| 越权 |
Session 认证 + requireAdmin()(401)/ requirePermission()(403)双重校验 |
| 爆破 |
登录验证码 + 失败次数限制;密码 BCrypt 散列 |
| 权限放大 |
表单数据走统一表 + JSON,不做动态建表,避免 DDL 权限外溢 |
五、目录结构
FlyCms/
├── backend/ # Java 后端
│ ├── src/main/java/com/flycms/
│ │ ├── config/ # MVC、CORS、WebSocket、Freemarker 沙箱、Druid
│ │ ├── core/ # 实体、工具类、全局标签、上传安全
│ │ ├── interceptor/ # 登录拦截、审计日志
│ │ ├── module/ # 26 个业务模块(model / channel / template / form ...)
│ │ └── web/
│ │ ├── api/ # /api/** REST(后台 SPA 用)
│ │ ├── front/ # 前台页面控制器
│ │ └── tags/ # Freemarker 自定义标签
│ ├── src/main/resources/
│ │ ├── application.yml
│ │ └── i18n/ # 国际化
├── frontend/ # pnpm workspace
│ ├── apps/web-antd/ # 后台控制台(Vue 3)
│ ├── packages/@core/ # 基础能力包
│ └── internal/ # 构建配置
├── sql/ # 数据库全量备份快照(最新一份,含结构+数据)
└── docs/ # 开发规划、模型手册、改造方案、审查报告、运维配置
六、快速开始
环境要求
- JDK 24、Maven 3.6+
- MySQL 8(字符集
utf8mb4)
- Node
^22.18 || ^24.12、pnpm ≥ 11
1. 数据库
sql/ 目录只保留一份全量备份快照(表结构 + 全部数据一体,含所有已上线功能与权限节点),无需再按阶段执行增量脚本:
mysql -u root -p -e "CREATE DATABASE flycms DEFAULT CHARACTER SET utf8mb4;"
mysql -u root -p --default-character-set=utf8mb4 flycms < sql/flycms_20260928_093335.sql
⚠️ 必须加 --default-character-set=utf8mb4:mysql 客户端在某些终端环境默认 latin1 连接,中文数据会真实乱码(写入即损坏,不是显示问题)。
备份快照由 MySQL 5.7.44 导出,兼容 MySQL 8 导入。导入后默认管理员:flycms / admin123(请登录后立即改密)。
备份与更新快照:系统内置纯 Java 全库备份(不依赖 mysqldump),后台「系统工具 → 数据库备份」或直接调用:
curl -X POST http://localhost/api/system/tools/db/backup # 需管理员会话
备份文件生成于 backend/backup/flycms_*.sql;需要刷新 sql/ 快照时,将最新一份复制过去替换即可。
2. 后端
修改 backend/src/main/resources/application.yml 的数据库连接,然后:
cd backend
mvn clean compile # 编译
mvn spring-boot:run # 运行
默认监听 http://localhost:80,前台 http://localhost/;管理控制台是前端 Vue SPA(见下一步)。
3. 前端控制台
cd frontend
pnpm install
pnpm dev:antd # 开发端口 5666
vite.config.ts 已配置代理:/api/** → 后端 80 端口,/captcha 直连(验证码接口无 /api 前缀)。
4. 常用命令
| 命令 |
作用 |
mvn -o compile |
后端编译校验 |
pnpm dev:antd |
启动后台开发服务 |
pnpm build:antd |
构建后台 |
pnpm check:type |
前端类型检查(vue-tsc) |
pnpm check |
循环依赖 + 依赖 + 类型 + 拼写全量检查 |
七、打包部署
cd backend
mvn clean package # 产出 target/FlyCms.jar
java -jar target/FlyCms.jar --spring.profiles.active=prod > FlyCms.log 2>&1 &
- 配置外置:复制
application.yml 为 application-prod.yml 后修改(务必改掉默认数据库口令)
- 静态资源与上传目录在
application.yml 的 spring.web.resources.static-locations:./views/static/、./uploadfiles/
- 模板目录:
./views/templates/
- 停止:
ps -ef | grep FlyCms.jar | grep -v grep | cut -c 9-15 | xargs kill -s 9;看日志:tail -200f FlyCms.log
短信 SDK 使用官方坐标 com.aliyun,Maven 可直接解析,无需手动安装 jar。
Solr 不再需要——检索模块已改为技术中立接缝,不接 ES 也能正常跑(搜索返回空结果)。
八、前台模板与标签
模板放在 views/templates/{主题}/,后台「模板中心」可在线编辑、保存即生成版本快照。
<@fly_page_model model="loupan" rows="10">
<#list dataList as row>
<a href="/loupan/${row.shortUrl}.html">${row.title}</a>
</#list>
${pageHtml!''}
</@fly_page_model>
<@fly_block key="home_focus">
<#list block.items as it><img src="${it.image}"></#list>
</@fly_block>
<@fly_form code="feedback">
<form action="/api/form/submit/${form.formCode}" method="post">
<#list form.fields as f>
<input name="${f.fieldCode}" placeholder="${f.placeholder!''}">
</#list>
<#if form.needCaptcha == 1><img src="/captcha"><input name="captcha"></#if>
</form>
</@fly_form>
<@fly_userpower groupName="技术专家组">
<a href="/ucenter/article/add">发布文章</a>
</@fly_userpower>
完整标签清单见后台「模板中心 → 标签手册」,或 TagManualService.java。
九、开发文档
| 文档 |
内容 |
docs/01-规划/主流CMS对标与全项目优化开发方案.md |
平台级复评 + K–R/U 阶段施工图;§9.4 阶段 U = 旧模块退役与统一生产总纲 |
docs/01-规划/自定义模型能力缺口与内容模块生产方案.md |
引擎缺口清单(E1–E10)与「每个存量模块如何用自定义模型生产出来」的逐模块对照 |
docs/01-规划/内容体系收敛与模型命名改造开发方案.md |
表名 / 主键策略的 ADR 与迁移步骤(P/D 批次已落地) |
docs/全智能CMS对标分析与开发规划.md |
阶段 A~J 的整体演进规划与设计决策 |
docs/02-手册/自定义模型系统开发手册.md |
内容模型建模、字段类型、数据表生成规则 |
docs/03-报告/2026-09-24-后端代码审查报告.md |
后端审查报告与已修 Bug 清单 |
docs/03-报告/2026-09-24-规划漏开发补齐报告.md |
规划对标后的漏开发补齐记录 |
docs/02-手册/前端权限接入开发手册.md |
前端权限码与按钮显隐约定 |
docs/04-运维/nginx-upload-security.conf |
上传目录的 Nginx 加固示例 |
十、已知边界
- 检索:
SearchService 目前是空实现(方法签名保留),站内搜索返回空结果;接入 Elasticsearch 时替换实现类即可,调用方无需改动
- 数据库:SQL 仅适配 MySQL,切库需自行改写 Mapper
- 配置文件:
application.yml 内含明文数据库口令与 devtools.enabled: true,生产环境务必外置并关闭
- 上传目录:
Const.UPLOAD_PATH 是相对路径 ./uploadfiles,部署换工作目录会导致上传丢失,建议配合 docs/nginx-upload-security.conf 做目录约束
十一、贡献
欢迎提 issue 与 PR。提问题时请附上复现步骤、环境版本与日志片段。
License
MIT
评论