一、项目简介
ChestnutCMS(栗子内容管理系统)是一款前后端分离的企业级内容管理系统,项目基于 RuoYi-Vue 重构,集成 SaToken 权限认证与 XXL-JOB 分布式任务调度。
核心能力:
- 🏢 站群管理 --- 一套系统管理多个站点,站点间资源隔离、模板独立
- 📄 多通道静态化 --- 一份内容同时发布 PC/H5/JSON/XML 等多种格式
- 🔍 全文检索 --- ElasticSearch + IK 分词,支持扩展词库和搜索日志
- 🧩 元数据模型扩展 --- 动态扩展站点、栏目、内容字段,无需改表
- 🌍 多语言国际化 --- 菜单、模板、内容均支持多语言
- 🔐 细粒度权限 --- 基于角色+用户的菜单/按钮/站点/栏目权限体系
开源协议: Apache-2.0,免费商用(需保留版权声明)
当前版本: v1.5.9
二、技术栈
| 技术 | 版本 | 说明 |
|---|---|---|
| Spring Boot | 3.4.5 | 基础框架,要求 JDK 17+ |
| MyBatis Plus | 3.5.12 | ORM 框架,简化 CRUD |
| FreeMarker | 2.3.32 | 模板引擎,页面静态化核心 |
| Sa-Token | 1.43.0 | 权限认证,替代 Spring Security |
| XXL-JOB | 2.4.0 | 分布式任务调度 |
| Redisson | 3.46.0 | 分布式锁 |
| ElasticSearch | - | 全文检索(可选,可降级到数据库查询) |
| Redis | 7.x | 缓存 + 消息队列 |
| MySQL | 8.0+ | 主数据库(也支持 PostgreSQL、Oracle、达梦等) |
| Flyway | 10.20.1 | 数据库版本管理 |
| Vue2 | - | 后台管理前端 |
开发环境:
JDK 17
MySQL 8.0.32
Redis 7.2.4
Node 20.19.4
Maven 3.8
三、系统架构
ChestnutCMS 采用经典的前后端分离架构:
┌─────────────────────────────────────────────────┐
│ 前端 (Vue2) │
│ chestnut-ui / npm run dev │
└──────────────────────┬──────────────────────────┘
│ HTTP API
┌──────────────────────▼──────────────────────────┐
│ 后端 (SpringBoot3) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 站点管理 │ │ 内容管理 │ │ 模板引擎(FM) │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 权限认证 │ │ 搜索服务 │ │ 任务调度 │ │
│ │ Sa-Token │ │ ES/DB │ │ XXL-JOB │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└───────┬──────────────┬──────────────┬───────────┘
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌────▼────┐
│ MySQL │ │ Redis │ │ ES │
└─────────┘ └───────────┘ └─────────┘
后端模块结构:
ChestnutCMS
├── chestnut-admin # 启动模块 + 系统管理
├── chestnut-cms # CMS 核心业务
│ ├── cms-site # 站点管理
│ ├── cms-catalog # 栏目管理
│ ├── cms-content # 内容管理
│ ├── cms-template # 模板管理
│ ├── cms-search # 搜索服务
│ ├── cms-publish # 静态化发布
│ └── ...
├── chestnut-common # 公共模块
└── chestnut-ui # Vue2 前端
网站静态资源目录结构:
wwwroot_release/
├── {站点目录}/ # 站点动态资源(用户上传)
│ └── resources/
│ ├── image/
│ ├── video/
│ ├── audio/
│ └── file/
├── {站点目录}_pc/ # PC 发布通道
│ ├── img/ js/ css/
│ └── template/ # FreeMarker 模板
│ ├── index.template.html
│ ├── list.template.html
│ ├── detail.template.html
│ └── ...
└── {站点目录}_h5/ # H5 发布通道(可选)
└── template/
└── ...
关键概念:站点目录 与 发布通道目录 是分开的。一个站点可以有多个发布通道(如
xxx_pc、xxx_h5),每个通道有独立的模板和静态化输出。
四、快速启动
4.1 后端
# 1. 克隆项目
git clone https://gitee.com/liweiyi/ChestnutCMS.git
# 2. 创建数据库
create database `chestnut_cms` charset utf8mb4;
# 3. 修改配置 (application-dev.yml)
# - 关闭 XXL-JOB:xxl.job.enable = false(本地开发不需要)
# - 配置 Redis 地址和密码
# - 配置 MySQL 数据源
# 4. 初始化数据库(二选一)
# 方式一:开启 Flyway 自动迁移
spring.flyway.enabled = true
# 方式二:手动按版本顺序执行 db/migration/mysql/ 下的 SQL 文件
# 5. 启动应用
# 运行 ChestnutApplication.main()
4.2 前端
cd chestnut-ui
npm install --registry=https://registry.npmmirror.com
npm run dev
# 访问 http://localhost:80,默认账号 admin/admin
4.3 重要配置
网站静态资源默认路径是项目同级的 wwwroot_release 目录,可通过配置修改:
chestnut:
cms:
resourceRoot: E:/dev/workspace/wwwroot_release/
五、核心功能模块详解
5.1 站点管理
支持多站点(站群),每个站点独立配置:
- 站点路径(如
csld6,影响静态资源目录和 URL 前缀) - 发布通道(PC/H5 各自独立模板和输出目录)
- 扩展模型(动态追加自定义字段)
- 图片水印、标题查重等扩展配置
5.2 栏目管理
- 普通栏目:有模板、有内容列表
- 链接栏目:直接跳转到指定 URL
- 栏目支持树形结构(父子级)
- 栏目可单独配置扩展模型,优先级高于站点级
5.3 内容管理
支持多种内容类型:
| 类型 | 说明 |
|---|---|
| 文章(article) | 富文本/Markdown,最常用 |
| 图片集 | 图集类内容 |
| 音视频集 | 多媒体内容 |
| 页面部件 | 动态区块、广告位 |
内容状态流转:草稿(0) → 待审核(10) → 已发布(30) → 已下线(40)
5.4 模板与静态化
这是 CMS 最核心的部分。ChestnutCMS 使用 FreeMarker 模板引擎,通过自定义标签(Directive)实现内容渲染:
常用模板指令:
| 指令 | 用途 | 关键参数 |
|---|---|---|
@cms_include |
引入公共模板片段 | file |
@cms_content |
查询内容列表 | catalogalias / catalogid |
@cms_catalog |
查询栏目信息 | alias / id |
模板中可用的变量:
| 变量 | 说明 |
|---|---|
${Site} |
当前站点信息 |
${Catalog} |
当前栏目信息 |
${Content} |
当前内容信息 |
${Prefix} |
站点路径前缀,如 /csld6_pc/ |
${ApiPrefix} |
API 路径前缀(预览模式用) |
${IsPreview} |
是否预览模式 |
5.5 全文检索
默认使用 ElasticSearch + IK 分词,提供:
- 标题 + 正文全文检索
- 自定义扩展词/停用词
- 搜索日志记录与统计
- 热词管理
ES 未启动时的降级方案:
系统支持降级到数据库查询(searchByDB 方法),仅按标题 LIKE 匹配,功能有限但保证了基本可用:
private R searchByDB(Long siteId, String publishPipeCode, String query,
Boolean onlyTitle, String contentType,
Integer page, Boolean preview) {
LambdaQueryWrapper<CmsContent> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CmsContent::getSiteId, siteId);
if (StringUtils.isNotEmpty(query)) {
// CmsContent 没有正文字段,降级时只能按标题搜
wrapper.like(CmsContent::getTitle, query);
}
wrapper.eq(CmsContent::getStatus, "30"); // 只搜已发布
wrapper.orderByDesc(CmsContent::getPublishDate);
// ... 分页查询 + 转 VO
}
关闭 ES 配置:
spring:
elasticsearch:
enable: false
management:
health:
elasticsearch:
enabled: false
六、模板开发实战
以下内容来自实际项目经验,踩过的坑比文档更有价值。
6.1 FreeMarker 语法避坑
坑1:@cms_include 必须用开闭标签,不能自闭合
<!-- ❌ 错误 -->
<@cms_include file="header.template.html" />
<!-- ✅ 正确 -->
<@cms_include file="header.template.html"></@cms_include>
坑2:@cms_content 的栏目参数用 catalogalias 而非 alias
<!-- ❌ 错误 -->
<@cms_content alias="news" ...>
<!-- ✅ 正确 -->
<@cms_content catalogalias="news" ...>
<!-- 或者用 ID -->
<@cms_content catalogid="807368491110469" ...>
坑3:模板变量用 Catalog 而非 CurrentCatalog
<!-- ❌ 错误 -->
${CurrentCatalog.name}
<!-- ✅ 正确 -->
${Catalog.name}
坑4:空值保护
FreeMarker 遇到 null 会报错,必须加空值保护:
<!-- ✅ 安全写法 -->
<#if Catalog??>${Catalog.name}</#if>
${(Catalog.name)!''}
6.2 模板文件组织
一个完整的站点模板包通常包含:
{站点路径}_pc/template/
├── header.template.html # 公共头部
├── footer.template.html # 公共底部
├── index.template.html # 首页
├── list.template.html # 列表页(通用)
├── detail.template.html # 详情页(通用)
├── search.template.html # 搜索页
├── special-list.template.html # 特殊栏目列表页
└── special-detail.template.html # 特殊栏目详情页
6.3 搜索页模板开发
搜索页是典型的动态模板(不走静态化),需要前后端配合:
核心流程:
- 前端通过 URL 参数获取搜索词
q - AJAX 调用
api/cms/search/query接口 - 渲染结果列表,支持分页
关键词高亮实现:
function highlightKeyword(text, keyword) {
if (!text || !keyword) return text || '';
var escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
var reg = new RegExp('(' + escaped + ')', 'gi');
return text.replace(reg, '<span class="search-highlight">$1</span>');
}
// 在渲染结果时使用
'<a href="' + link + '">' + highlightKeyword(row.title, q) + '</a>'
搜索结果跳转详情页:
var link = row.link;
if (!link || link === 'undefined') {
<#if IsPreview>
// 预览模式:走 API 预览接口
link = row.contentId ? '${ApiPrefix}cms/preview/content/' + row.contentId : '#';
var token = getAuthToken();
if (link !== '#' && token) {
link += '?pp=pc&Authorization=' + encodeURIComponent('Bearer ' + token);
}
<#else>
// 正式模式:走静态化页面
link = row.contentId ? '${Prefix}content/' + row.contentId + '.html' : '#';
</#if>
}
⚠️ 注意:预览模式和正式模式的链接逻辑必须区分。预览走 API 动态渲染,正式走静态化 HTML 文件。
6.4 主题包导入导出
ChestnutCMS 支持站点主题包(ZIP)的一键导入导出,方便模板复用和迁移。
ZIP 包正确结构:
theme.zip
├── wwwroot/
│ └── {站点路径}_pc/
│ ├── template/ # FreeMarker 模板
│ │ ├── index.template.html
│ │ └── ...
│ ├── css/
│ ├── js/
│ └── img/
└── db/
├── cms_site.json # 站点配置
├── cms_catalog.json # 栏目配置
└── cms_content.json # 示例内容
常见导入失败原因:
- ZIP 顶层多了嵌套文件夹(如
lzdxuexiao/wwwroot/...),CMS 找不到正确的wwwroot/路径 - 站点路径不匹配(ZIP 中是
lzdxuexiao,但 CMS 站点路径是csld9) - 缺少
wwwroot/{站点}_pc/template/目录 db/中的 JSON 格式不对,特别是publishPipeProps需要嵌套格式
七、搜索功能深度解析
7.1 ES 模式(默认)
系统默认使用 ElasticSearch + IK 分词器实现全文检索:
- 内容发布时自动创建/更新 ES 索引
- 支持标题 + 正文全文检索
- 自定义扩展词/停用词
- 搜索日志记录和热词统计
7.2 数据库降级模式
当 ES 未部署或不可用时,系统可降级到数据库 LIKE 查询:
限制:
CmsContent表没有正文字段,只能按标题匹配- 不支持分词,只能模糊匹配
- 性能随数据量增长下降
启用降级:
spring:
elasticsearch:
enable: false
系统会自动检测 ES 是否可用,不可用时走 searchByDB 方法。
7.3 搜索结果补充 summary 字段
降级模式下,searchByDB 默认不返回 summary 字段,需要手动补充:
vo.setSummary(content.getSummary());
同时确保 ESContentVO 中有 summary 属性,前端模板通过 row.summary 渲染。
7.4 search_log 表自增问题
如果搜索日志插入报错 log_id 没有默认值,需要修改表:
ALTER TABLE search_log MODIFY COLUMN log_id BIGINT NOT NULL AUTO_INCREMENT;
八、权限体系
ChestnutCMS 实现了基于角色 + 用户的细粒度授权:
用户 → 角色 → 菜单权限(页面 + 按钮)
→ 站点权限(可管理哪些站点)
→ 栏目权限(可管理哪些栏目)
- 同一角色可以管理多个站点
- 不同栏目可以分配给不同角色
- 用户可以独立配置权限,不继承角色
Sa-Token 配置要点:
sa-token:
token-name: Authorization
token-prefix: Bearer
timeout: 2592000 # 30天
is-concurrent: true # 允许并发登录
token-style: uuid
九、数据库兼容性
ChestnutCMS 支持多种数据库:
| 数据库 | 状态 |
|---|---|
| MySQL | ✅ 主力支持 |
| PostgreSQL | ✅ 支持 |
| Oracle | ✅ 支持 |
| SQL Server | ✅ 支持 |
| 达梦 | ✅ 支持 |
| 神州通用 | ✅ 支持 |
| 人大金仓 | ✅ 支持 |
| SQLite | ✅ 支持 |
通过 MyBatis Plus 的动态数据源 + Flyway 的多数据库迁移脚本实现兼容。
十、常见问题与踩坑总结
Q1:静态化后页面 404?
检查发布通道目录是否正确,路径格式为 {站点路径}_{通道编码},如 csld6_pc。静态文件需要 Nginx 代理到 wwwroot_release 目录。
Q2:模板修改后不生效?
模板修改后需要重新静态化(全站或增量),动态模板(如搜索页)则立即生效。
导入的模板需要特定的架构才可以正常导入。
Q3:Flyway 迁移报错?
首次启动建议关闭 Flyway,手动按版本顺序执行 SQL 文件。确认数据库版本正确后,再开启 Flyway 做后续版本管理。
Q4:ES 启动失败导致系统无法启动?
在 application.yml 中关闭 ES:
spring:
elasticsearch:
enable: false
management:
health:
elasticsearch:
enabled: false
Q5:MyBatis Plus 泛型错误?
集合类型必须声明具体泛型:
// ❌ 报错
List list = new ArrayList();
Map map = new HashMap();
// ✅ 正确
List<String> list = new ArrayList<>();
Map<String, ContentDynamicDataVO> map = new HashMap<>();
Q6:上传资源 404?
检查 chestnut.cms.resourceRoot 配置,确保路径指向正确的 wwwroot_release 目录,且 Nginx 配置了静态资源代理。
十一、适用场景
| 场景 | 适合程度 | 说明 |
|---|---|---|
| 企业官网 | ⭐⭐⭐⭐⭐ | 核心场景,站群 + 静态化完美适配 |
| 高校院系网站 | ⭐⭐⭐⭐⭐ | 多栏目 + 多内容类型 + 权限隔离 |
| 政府门户 | ⭐⭐⭐⭐ | 静态化安全合规,支持国产数据库 |
| 资讯媒体 | ⭐⭐⭐⭐ | ES 全文检索 + 热词统计 |
| 电商平台 | ⭐⭐⭐ | CMS 部分可用,但缺乏电商核心功能 |
| 社区论坛 | ⭐⭐ | 评论系统基础,社区功能需二次开发 |
十二、总结
ChestnutCMS 在国产 Java CMS 中属于完成度较高的作品,基于 RuoYi 生态让上手门槛低,FreeMarker 模板体系灵活且静态化对 SEO 友好。站群管理、多通道发布、元数据扩展等企业级特性让它在中大型项目中也有用武之地。
优点:
- 基于 RuoYi,熟悉若依的开发者零成本上手
- 站群 + 多通道 + 静态化,企业级 CMS 核心能力齐全
- FreeMarker 模板体系成熟,自定义指令丰富
- Apache-2.0 协议免费商用
- 国产数据库支持全面
不足:
- 前端仍为 Vue2,未跟进 Vue3
- 文档相对分散,部分 API 需要读源码才能理解
- ES 降级方案功能有限
- 社区规模较小,遇到问题主要靠官方 QQ 群
- 后端基本不需要二次开发,主要功能实现在前端(用他特有的前端组件)
推荐指数: ⭐⭐⭐⭐(4/5)
适合需要站群管理、内容静态化的企业官网 / 高校 / 政府项目,不适合需要复杂交互和电商逻辑的场景。
参考链接: