资讯详情

RuoYi前后端分离版CMS权限改造实战指南

📅 2026/10/11 11:57:38 | 华诺云谱 👁 阅读
RuoYi前后端分离版CMS权限改造实战指南
简介本资源是一套基于Ruoyi框架开发的前后端分离内容管理系统CMS完整源码包面向Java后端开发者、Vue前端工程师及全栈学习者解决中小型网站内容建站、多角色内容管理与快速二次开发等实际需求。压缩包为RAR格式共2个文件1个ZIP源码包含Ruoyi后端Vue前端工程、1个SQL数据库脚本支持MySQL等关系型数据库总大小17.47MB结构清晰开箱即用。目前已有159人学习下载适合中初级开发者通过真实项目理解前后端分离架构、RBAC权限控制、动态表单配置及CMS核心模块内容发布、栏目管理、模板渲染的协同实现。源码已集成基础安全防护防XSS/CSRF、RESTful API设计规范及标准数据库建模附带可直接导入的初始化数据大幅降低环境搭建与功能验证门槛。1. RuoYi 前后端分离版 CMS不是“套模板”而是可落地的权限驱动型内容管理骨架你拿到一套标着“基于 RuoYi 开发的前后端分离内容管理系统源码数据库”的压缩包别急着解压跑起来——它大概率不是开箱即用的成品站而是一套以 RuoYi 框架为基底、按典型 CMS 需求重构过的工程骨架。它的核心价值不在“有文章列表”而在“权限如何控制栏目增删改查”、“富文本内容如何与菜单/角色绑定”、“附件上传路径怎么隔离到栏目维度”。我接手过三个类似需求某高校实验室的课题成果发布平台、某制造企业的内部知识库、某设计工作室的案例展示后台。它们都没重写登录页但都在sys_menu表里加了menu_type C内容类标识在content_article表里硬塞了category_id和status字段并把 RuoYi 原生的SysUser扩展出user_level字段来控制编辑器功能开关。这不是炫技是真实业务倒逼出的改造逻辑。如果你正被“内容要分频道、编辑要分级、审核要留痕”卡住又不想从 Spring Boot Vue 一点点搭轮子这套源码就是你该拆解的最小可行权限 CMS 范本。它不解决 UI 美观度但能让你三天内跑通“张三只能发科技栏、李四能审但不能删、王五只看不编”的闭环。2. 拆包即运行本地环境搭建与数据库初始化实操2.1 环境依赖确认版本对齐比配置更重要RuoYi 前后端分离版对 JDK、Node.js、MySQL 版本有隐性要求。常见翻车点是 JDK 17 下ruoyi-admin启动报java.lang.UnsupportedClassVersionError或 Node.js 18 下npm run dev卡在webpack编译。我一般会先执行以下检查# 检查 JDK 版本必须为 8 或 1117 不兼容多数 RuoYi 4.x 分支 java -version # 输出应为 openjdk version 11.0.20 2023-07-18 # 检查 Node.js 版本推荐 16.19.118.x 会导致 element-ui 兼容问题 node -v # 输出应为 v16.19.1 # 检查 MySQL 版本5.7 是最稳选择8.0 需关闭 strict mode mysql --version # 输出应为 mysql Ver 14.14 Distrib 5.7.42提示若已装高版本 JDK不要卸载用 SDKMAN 切换更安全sdk install java 11.0.20-tem→sdk use java 11.0.20-tem。Node.js 同理nvm install 16.19.1→nvm use 16.19.1。2.2 数据库初始化从 SQL 文件到字符集校验源码包里的sql/目录通常含ry_2023xxxx.sql类似命名的文件。直接source会失败——因为 RuoYi 默认建库语句未指定字符集而你的 MySQL 可能默认utf8mb4。必须手动创建数据库并指定编码-- 在 MySQL 客户端中执行注意不是在命令行用 mysql -u root -p 进去后再输 CREATE DATABASE IF NOT EXISTS ry_cms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE ry_cms; SOURCE /path/to/your/ry_2023xxxx.sql;执行后务必验证关键表字符集SELECT table_name, table_collation FROM information_schema.tables WHERE table_schema ry_cms AND table_name IN (sys_user, sys_menu, content_article);正确结果应全为utf8mb4_unicode_ci。若出现utf8_general_ci说明 SQL 文件里建表语句漏了DEFAULT CHARSETutf8mb4需手动修改 SQL 文件中所有CREATE TABLE语句末尾补上ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;。2.3 后端启动跳过 Maven 仓库污染的三步法ruoyi-admin模块常因本地.m2仓库存在损坏的ruoyi-common快照包而编译失败。不要反复mvn clean install用这三步直击要害# 步骤1强制清理 ruoyi 相关快照-Dmaven.repo.local 指定临时仓库避免污染主仓库 mvn clean compile -Dmaven.repo.local/tmp/ruoyi-m2 -pl ruoyi-admin -am # 步骤2修改 ruoyi-admin/src/main/resources/application-druid.yml # 将 username: root 改为你的数据库用户名password: 改为明文密码开发期可接受 # 步骤3启动时显式指定配置文件绕过 profile 混乱 java -jar ruoyi-admin/target/ruoyi-admin.jar --spring.profiles.activedev启动成功标志控制台输出Started RuoyiAdminApplication in X.XXX seconds且无Failed to bind properties报错。此时访问http://localhost:8080应见登录页。2.4 前端启动Vue CLI 代理失效时的手动配置ruoyi-ui目录下npm run dev启动后登录请求常 404——因为vue.config.js中devServer.proxy默认指向http://localhost:8080但你的后端可能启在8081。必须手动改// ruoyi-ui/vue.config.js 第 32 行附近 devServer: { port: 80, proxy: { /prod-api: { target: http://localhost:8080, // ← 改为你后端实际端口 changeOrigin: true, logLevel: debug, pathRewrite: { ^/prod-api: } } } }改完重启前端。打开浏览器开发者工具 → Network 标签登录时观察/prod-api/login请求是否返回200及token字段。若仍 404检查后端ruoyi-framework/src/main/java/com/ruoyi/framework/config/WebMvcConfig.java中addCorsMappings是否允许http://localhost:80源。3. 权限模型改造从 RuoYi 原生菜单到 CMS 内容权限的映射3.1 理解 RuoYi 权限链菜单 → 角色 → 用户 → 接口RuoYi 的权限控制是四层嵌套菜单sys_menu定义前端路由和按钮如“内容管理”菜单下挂“文章列表”、“栏目管理”角色sys_role绑定菜单role_id 关联 sys_role_menu.role_id用户sys_user绑定角色user_id 关联 sys_user_role.user_id接口PreAuthorize后端方法用PreAuthorize(ss.hasPermi(content:article:list))校验CMS 的特殊性在于同一菜单如“文章列表”下不同角色看到的数据范围不同。例如编辑角色只能看自己发的文章审核角色能看到全站待审文章。这需要在ContentArticleController.list()方法里注入数据过滤逻辑而非仅靠PreAuthorize。3.2 扩展 sys_menu 表增加 content_type 字段标识内容类型原生 RuoYi 的sys_menu表无内容类型字段导致无法区分“系统设置”和“新闻栏目”。我们在sys_menu表中新增content_type字段ALTER TABLE sys_menu ADD COLUMN content_type VARCHAR(32) DEFAULT NULL COMMENT 内容类型news(新闻), product(产品), case(案例);然后在ruoyi-system/src/main/java/com/ruoyi/system/domain/SysMenu.java中添加/** 内容类型用于 CMS 分类 */ private String contentType; public String getContentType() { return contentType; } public void setContentType(String contentType) { this.contentType contentType; }参数说明content_type值必须为小写英文下划线禁止空格或中文。这是为后续 MyBatis 动态 SQL 做准备——比如在ContentArticleMapper.xml中写if testmenu.contentType ! null and menu.contentType ! AND a.content_type #{menu.contentType}/if。3.3 改造内容控制器在 list 接口中注入数据级权限以ContentArticleController.list()为例原生代码只做分页查询。CMS 需在此处插入“当前用户可见内容范围”逻辑GetMapping(/list) public TableDataInfo list(ContentArticle article) { // 1. 获取当前用户信息 SysUser currentUser SecurityUtils.getLoginUser().getUser(); // 2. 若用户角色含editor只查自己发布的文章 if (SecurityUtils.hasRole(editor)) { article.setCreateBy(currentUser.getUserId()); } // 3. 若角色含reviewer只查 status1待审核的文章 if (SecurityUtils.hasRole(reviewer)) { article.setStatus(1L); } // 4. 若角色含admin不做额外限制走原逻辑 startPage(); ListContentArticle list contentArticleService.selectContentArticleList(article); return getDataTable(list); }逻辑说明SecurityUtils.hasRole(xxx)是 RuoYi 封装的快捷方法本质是从SecurityContextHolder.getContext().getAuthentication().getAuthorities()中匹配ROLE_xxx。此处不建议用PreAuthorize(hasRole(editor))因为那是接口级拦截无法动态修改查询条件。3.4 前端菜单渲染根据 content_type 动态加载栏目树ruoyi-ui/src/api/menu.js中getRouters()接口返回的菜单数据需携带contentType。修改后端SysMenuController.getRouters()方法在组装RouterVo时加入routerVo.setContentType(menu.getContentType()); // 从 sys_menu 查询时取出前端ruoyi-ui/src/layout/components/Sidebar/index.vue中当点击“内容管理”菜单时触发栏目树加载// 在 methods 中添加 loadCategoryTree() { if (this.$route.meta.contentType) { categoryApi.list({ contentType: this.$route.meta.contentType }).then(res { this.categoryTree res.data; }); } }这样“新闻”菜单点击后加载新闻栏目“产品”菜单加载产品分类彻底解耦。4. 内容模块落地文章、栏目、附件的三件套实现4.1 栏目管理用树形结构支撑多级分类CMS 必须支持“科技 人工智能 机器学习”这类三级栏目。RuoYi 原生sys_dept是部门树我们复用其结构但新建content_category表CREATE TABLE content_category ( category_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 栏目ID, category_name varchar(50) NOT NULL COMMENT 栏目名称, parent_id bigint(20) DEFAULT 0 COMMENT 父栏目ID, order_num int(4) DEFAULT 0 COMMENT 显示顺序, status char(1) DEFAULT 0 COMMENT 状态0正常 1停用, create_by varchar(64) DEFAULT COMMENT 创建者, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_by varchar(64) DEFAULT COMMENT 更新者, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT内容栏目表;关键点parent_id允许为0顶级栏目order_num控制同级排序。前端树组件直接复用 RuoYi 的TreeSelect只需将api指向content/category/list即可。4.2 文章管理富文本 多图上传 SEO 字段content_article表需包含 CMS 核心字段CREATE TABLE content_article ( article_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 文章ID, category_id bigint(20) NOT NULL COMMENT 栏目ID, title varchar(200) NOT NULL COMMENT 标题, content longtext NOT NULL COMMENT 正文富文本HTML, summary varchar(500) DEFAULT NULL COMMENT 摘要, keywords varchar(200) DEFAULT NULL COMMENT SEO关键词, cover_image varchar(200) DEFAULT NULL COMMENT 封面图URL, status tinyint(1) DEFAULT 0 COMMENT 状态0草稿 1待审 2已发布 3已撤回, view_count int(11) DEFAULT 0 COMMENT 浏览量, create_by varchar(64) DEFAULT COMMENT 创建者, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (article_id), KEY idx_category_status (category_id,status) -- 联合索引加速列表查询 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT内容文章表;注意content字段用longtext而非text避免富文本超长截断idx_category_status索引是性能关键列表页WHERE category_id? AND status2会走此索引。4.3 附件管理独立于系统附件的 CMS 专属存储RuoYi 原生sys_file表用于系统文件如头像CMS 附件需隔离。新建content_attachment表CREATE TABLE content_attachment ( attachment_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 附件ID, article_id bigint(20) NOT NULL COMMENT 所属文章ID, file_name varchar(200) NOT NULL COMMENT 原始文件名, file_path varchar(500) NOT NULL COMMENT 存储路径相对路径, file_size bigint(20) DEFAULT 0 COMMENT 文件大小字节, upload_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 上传时间, PRIMARY KEY (attachment_id), KEY idx_article_id (article_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT内容附件表;后端上传接口ContentAttachmentController.upload()中file_path不存绝对路径而存article/2023/08/15/xxx.jpg由 Nginx 静态资源配置location /article/ { alias /data/www/ry-cms/article/; }映射。这样既安全不暴露服务器路径又便于 CDN 接入。5. 避坑指南五个让开发者凌晨三点还在查日志的致命问题5.1 现象登录成功后跳转到空白页Network 中getInfo返回 401原因前端ruoyi-ui/src/utils/auth.js中getToken()读取的是sessionStorage但后端LoginController.login()返回的 token 存在response.headers[Authorization]而前端未将其写入sessionStorage。解决在ruoyi-ui/src/api/login.js的login()方法中then回调里加sessionStorage.setItem(token, res.headers.authorization);并确保res.headers.authorization格式为Bearer xxx否则前端request.js中config.headers.Authorization Bearer getToken()会拼错。5.2 现象文章列表页点击“编辑”弹窗中富文本内容为空原因ContentArticleController.selectContentArticleById()方法返回的ContentArticle对象中content字段被 MyBatis 的resultMap忽略因字段名为content与ResultMap中columncontent冲突MyBatis 认为这是关键字。解决在ContentArticleMapper.xml的resultMap中将content字段的column改为反引号包裹result columncontent propertycontent /或更稳妥地在ContentArticle实体类中将字段名改为articleContent数据库字段别名也同步调整。5.3 现象MySQL 5.7 下执行ry_2023xxxx.sql报错Invalid default value for create_time原因SQL 文件中create_time datetime DEFAULT CURRENT_TIMESTAMP语句在 MySQL 5.7 严格模式下不被允许datetime类型不支持CURRENT_TIMESTAMP默认值。解决将 SQL 文件中所有datetime DEFAULT CURRENT_TIMESTAMP替换为datetime NOT NULL DEFAULT CURRENT_TIMESTAMP并在 MySQL 中关闭严格模式SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,));5.4 现象Nginx 反向代理后前端prod-api请求 502 Bad Gateway原因Nginx 配置中proxy_pass末尾多了/如proxy_pass http://backend/;导致请求路径从/prod-api/login变成/login丢失prod-api前缀。解决proxy_pass末尾不能加/location /prod-api { proxy_pass http://backend; # 注意这里没有斜杠 proxy_set_header Host $host; }5.5 现象上传图片后富文本中显示http://localhost:8080/profile/xxx.jpg但该地址 404原因RuoYi 默认将文件存到profile/目录但application.yml中profile配置项未指向 Web 可访问路径。解决在ruoyi-admin/src/main/resources/application.yml中修改# 文件路径配置 profile: # 上传路径Windows # path: D:/ruoyi/uploadPath/ # 上传路径Linux path: /home/www/ry-cms/upload/ # 访问路径对应 nginx location /profile/ location: /profile/并在 Nginx 中添加location /profile/ { alias /home/www/ry-cms/upload/; }6. 生产就绪技巧Nginx 静态资源分离与 Redis 缓存加速6.1 Nginx 静态资源分离让 CMS 图片不拖慢管理后台RuoYi 默认将所有静态资源JS/CSS/图片打包进ruoyi-ui/dist/由前端nginx.conf的location /服务。但 CMS 的文章封面图、附件等大文件若也走此路径会挤占带宽。正确做法是分离# ruoyi-ui 的 nginx.conf server { listen 80; server_name cms-admin.example.com; # 管理后台静态资源JS/CSS/HTML location / { root /home/www/ruoyi-ui/dist; try_files $uri $uri/ /index.html; } # CMS 内容图片封面、附件 location /content/ { alias /home/www/ry-cms/content/; expires 1h; } # 代理 API 请求 location /prod-api/ { proxy_pass http://backend:8080/; proxy_set_header Host $host; } }此时前端富文本中的图片 URL 应为https://cms-admin.example.com/content/article/2023/08/15/xxx.jpg而非http://localhost:8080/profile/xxx.jpg。后端ContentAttachmentController.upload()中生成file_path时需去掉profile/前缀直接存content/article/...。6.2 Redis 缓存热点数据给栏目树和文章列表加缓存CMS 中栏目树content_category和热门文章列表content_articlewhere status2 order by view_count desc limit 10是高频读、低频写的典型。用 Redis 缓存可降低数据库压力// 在 ContentCategoryService.list() 方法中 public ListContentCategory selectCategoryList(ContentCategory category) { String cacheKey category:tree: category.getContentType(); ListContentCategory list redisCache.getCacheList(cacheKey); if (CollUtil.isNotEmpty(list)) { return list; } list contentCategoryMapper.selectContentCategoryList(category); redisCache.setCacheList(cacheKey, list, 30L, TimeUnit.MINUTES); // 缓存30分钟 return list; }参数说明30L, TimeUnit.MINUTES是缓存时长不宜设过长栏目结构调整需及时生效30 分钟是平衡点。cacheKey中加入contentType确保“新闻栏目树”和“产品栏目树”互不干扰。6.3 文章详情页缓存用 Redis Hash 存储单篇文章文章详情selectContentArticleById是单条查询用 Redis String 缓存即可但为统一管理我倾向用 Hash// key: article:detail:12345, field: base, value: JSON字符串 String cacheKey article:detail: articleId; String json redisCache.getCacheObject(cacheKey :base); if (StrUtil.isNotBlank(json)) { return JSON.parseObject(json, ContentArticle.class); } ContentArticle article contentArticleMapper.selectContentArticleById(articleId); redisCache.setCacheObject(cacheKey :base, JSON.toJSONString(article), 1L, TimeUnit.HOURS); return article;这样当文章被编辑时只需redisCache.deleteObject(article:detail: articleId)即可清除缓存无需关心具体字段。我习惯在每次上线前用redis-cli --scan --pattern article:detail:* | wc -l统计文章缓存数量若远超实际文章数如 1000 条但只有 200 篇文章说明有脏缓存未清理——这时我会在ContentArticleController.edit()方法末尾加一行redisCache.deleteObject(article:detail: article.getArticleId())确保编辑即刷新。这个习惯救过我三次线上缓存雪崩。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑