ChestnutCMS 栗子内容管理系统:从入门到模板开发实战

一、项目简介

ChestnutCMS(栗子内容管理系统)是一款前后端分离的企业级内容管理系统,项目基于 RuoYi-Vue 重构,集成 SaToken 权限认证与 XXL-JOB 分布式任务调度。

核心能力:

  • 🏢 站群管理 --- 一套系统管理多个站点,站点间资源隔离、模板独立
  • 📄 多通道静态化 --- 一份内容同时发布 PC/H5/JSON/XML 等多种格式
  • 🔍 全文检索 --- ElasticSearch + IK 分词,支持扩展词库和搜索日志
  • 🧩 元数据模型扩展 --- 动态扩展站点、栏目、内容字段,无需改表
  • 🌍 多语言国际化 --- 菜单、模板、内容均支持多语言
  • 🔐 细粒度权限 --- 基于角色+用户的菜单/按钮/站点/栏目权限体系

开源协议: Apache-2.0,免费商用(需保留版权声明)

官方地址: Gitee | GitHub | 官网

当前版本: 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_pcxxx_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 搜索页模板开发

搜索页是典型的动态模板(不走静态化),需要前后端配合:

核心流程:

  1. 前端通过 URL 参数获取搜索词 q
  2. AJAX 调用 api/cms/search/query 接口
  3. 渲染结果列表,支持分页

关键词高亮实现:

复制代码
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       # 示例内容

常见导入失败原因:

  1. ZIP 顶层多了嵌套文件夹(如 lzdxuexiao/wwwroot/...),CMS 找不到正确的 wwwroot/ 路径
  2. 站点路径不匹配(ZIP 中是 lzdxuexiao,但 CMS 站点路径是 csld9
  3. 缺少 wwwroot/{站点}_pc/template/ 目录
  4. 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)

适合需要站群管理、内容静态化的企业官网 / 高校 / 政府项目,不适合需要复杂交互和电商逻辑的场景。

参考链接:

相关推荐
2601_9577867716 小时前
多平台矩阵运营的底层逻辑:当账号管理、内容生产与线索转化被一条链路串起来
java·数据库·矩阵·多平台管理
代码中介商16 小时前
排序算法完全指南(六):希尔排序深度详解
java·算法·排序算法
专注VB编程开发20年16 小时前
安桌15系统文件直接存到其他目录要权限吗?/storage/emulated/0/Downloa
git
布吉岛的石头16 小时前
Java 程序员第 22 阶段:Function Call 工具调用实战,Java 封装大模型外部能力
java·人工智能·python
阿维的博客日记17 小时前
线程任务执行报错后,线程会不会挂掉,Java线程池
java·线程池
Hwang25217 小时前
Spring 框架- 容器单例池的理解
java
yh弓长17 小时前
算法积累笔记
java·算法
LeocenaY17 小时前
C/C++ 面试题总结
java·c++·面试
alphageek817 小时前
FFmpeg:开源多媒体处理工具集合
其他·ffmpeg·开源