一套前后端分离的开源内容建站系统:后台是 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控制
| 分组 | 功能 |
|---|---|
| 系统管理 | 管理员、角色组、菜单权限(后端动态菜单)、站内信、公告、审计日志、附件库、导航、友情链接 |
| 内容 | 内容模型(模型 / 字段 / 数据 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
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/ 快照时,将最新一份复制过去替换即可。
修改 backend/src/main/resources/application.yml 的数据库连接,然后:
cd backend
mvn clean compile # 编译
mvn spring-boot:run # 运行默认监听 http://localhost:80,前台 http://localhost/;管理控制台是前端 Vue SPA(见下一步)。
cd frontend
pnpm install
pnpm dev:antd # 开发端口 5666vite.config.ts 已配置代理:/api/** → 后端 80 端口,/captcha 直连(验证码接口无 /api 前缀)。
| 命令 | 作用 |
|---|---|
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。提问题时请附上复现步骤、环境版本与日志片段。
MIT